/* ==========================================================================
   NEXISTEK — Motion
   Keyframes, reveal states and the reduced-motion contract.

   Reveal contract: hidden states are scoped to .js, which boot.js sets before
   first paint. If JavaScript or GSAP never arrives, .js is removed and every
   element renders in its natural, fully visible state. Content is never
   trapped behind an animation that failed to run.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Reveal primitives (driven by animation.js)
   -------------------------------------------------------------------------- */
.js [data-reveal] {
  opacity: 0;
  will-change: transform, opacity;
}

.js [data-reveal="up"] {
  transform: translate3d(0, 2.5rem, 0);
}

.js [data-reveal="fade"] {
  transform: none;
}

.js [data-reveal="scale"] {
  transform: scale(0.94);
}

.js [data-reveal="left"] {
  transform: translate3d(-2.5rem, 0, 0);
}

.js [data-reveal="right"] {
  transform: translate3d(2.5rem, 0, 0);
}

/* Masked line reveal for display type */
.js [data-reveal-lines] .hero__line > span {
  transform: translate3d(0, 105%, 0);
  will-change: transform;
}

/* GSAP clears inline styles when finished; this drops the compositor hint. */
[data-reveal].is-revealed {
  will-change: auto;
}

/* --------------------------------------------------------------------------
   2. Ambient keyframes
   -------------------------------------------------------------------------- */

/* Slow vertical drift for floating hardware */
@keyframes drift {
  0%,
  100% {
    transform: translate3d(0, 0, 0);
  }
  50% {
    transform: translate3d(0, -0.75rem, 0);
  }
}

/* Breathing glow for energised nodes */
@keyframes pulse-glow {
  0%,
  100% {
    opacity: 0.35;
    transform: scale(1);
  }
  50% {
    opacity: 1;
    transform: scale(1.35);
  }
}

/* Current travelling along a circuit trace */
@keyframes trace-flow {
  to {
    stroke-dashoffset: -84;
  }
}

/* Slow rotation for holographic rings */
@keyframes orbit {
  to {
    transform: rotate(360deg);
  }
}

/* --------------------------------------------------------------------------
   3. Applied ambience
   -------------------------------------------------------------------------- */
.wb-float {
  animation: drift 7s ease-in-out infinite;
}

.wb-float--slow {
  animation-duration: 11s;
  animation-delay: -3s;
}

.wb-float--fast {
  animation-duration: 5.5s;
  animation-delay: -1.5s;
}

.wb-pulse {
  animation: pulse-glow 3s ease-in-out infinite;
  transform-box: fill-box;
  transform-origin: center;
}

.wb-pulse--b {
  animation-delay: -1s;
}

.wb-pulse--c {
  animation-delay: -2s;
}

.wb-trace {
  stroke-dasharray: 6 78;
  animation: trace-flow 4s linear infinite;
}

.wb-trace--b {
  animation-delay: -1.3s;
}

.wb-trace--c {
  animation-delay: -2.6s;
}

.wb-orbit {
  transform-box: fill-box;
  transform-origin: center;
  animation: orbit 26s linear infinite;
}

.wb-orbit--rev {
  animation-duration: 34s;
  animation-direction: reverse;
}

/* Circuit flow inside a technology object, once the card is engaged */
.domain-card:hover .to-flow,
.domain-card:focus-within .to-flow {
  animation: trace-flow 2.4s linear infinite;
}

/* Technologies We Teach — the idle orbit.
 *
 * The ring of nodes turns slowly on its own while the inner wrapper runs the
 * same keyframe in reverse, so each icon and label stays upright while its
 * position travels around the centre. Both must share a duration, or the
 * cancellation drifts and the labels slowly tip over.
 *
 * It lives here rather than in responsive.css because it now runs at every
 * width — on a phone the diagram is the section, and a still one looked
 * broken. 70s is one turn: at phone radius that is about 11px a second, slow
 * enough that a node is still an easy tap target.
 */
.tech-network__nodes {
  animation: orbit 70s linear infinite;
}

.tech-network__node-spin {
  animation: orbit 70s linear infinite reverse;
}

/* Freeze while it is being read or used. :active covers touch, where there is
   no hover to stop it with. */
.tech-network__stage:hover .tech-network__nodes,
.tech-network__stage:focus-within .tech-network__nodes,
.tech-network__stage:active .tech-network__nodes,
.tech-network__stage:hover .tech-network__node-spin,
.tech-network__stage:focus-within .tech-network__node-spin,
.tech-network__stage:active .tech-network__node-spin {
  animation-play-state: paused;
}

/* --------------------------------------------------------------------------
   4. Reduced motion
   Applies to the OS setting and to the in-page toggle alike. Ambient loops
   stop entirely; state changes still resolve, just without travel.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  .js [data-reveal-lines] .hero__line > span {
    opacity: 1;
    transform: none;
  }

  .wb-float,
  .wb-pulse,
  .wb-trace,
  .wb-orbit,
  .hero__badge-dot,
  .tech-network__nodes,
  .tech-network__node-spin,
  .domain-card:hover .to-flow {
    animation: none;
  }

}

:root.is-low-motion [data-reveal],
:root.is-low-motion [data-reveal-lines] .hero__line > span {
  opacity: 1;
  transform: none;
}

:root.is-low-motion .wb-float,
:root.is-low-motion .wb-pulse,
:root.is-low-motion .wb-trace,
:root.is-low-motion .wb-orbit,
:root.is-low-motion .hero__badge-dot,
:root.is-low-motion .tech-network__nodes,
:root.is-low-motion .tech-network__node-spin,
:root.is-low-motion .domain-card:hover .to-flow {
  animation: none;
}

