/* ---------- Scroll reveal ---------- */
.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 600ms var(--ease-standard),
    transform 600ms var(--ease-standard);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* stagger children of a revealed group */
.reveal-group.is-visible .reveal-child {
  opacity: 1;
  transform: translateY(0);
}

.reveal-child {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 500ms var(--ease-standard),
    transform 500ms var(--ease-standard);
}

.reveal-child:nth-child(2) {
  transition-delay: 80ms;
}
.reveal-child:nth-child(3) {
  transition-delay: 160ms;
}
.reveal-child:nth-child(4) {
  transition-delay: 240ms;
}
.reveal-child:nth-child(5) {
  transition-delay: 320ms;
}
.reveal-child:nth-child(6) {
  transition-delay: 400ms;
}

/* ---------- Hero network: heartbeat ----------
   One organic loop drives the whole diagram. The core beats lub-dub at ~65 BPM; every second
   beat the hub emits a pulse down each of its own wires, and each pulse that reaches an
   intermediate node re-emits down that node's branches a beat later, staggered so siblings never
   fire in unison. Everything is expressed against two variables so the rhythm stays coherent:
   change --beat and the whole network re-times itself.

   Only transform/opacity/offset-distance animate (all compositor-friendly, no layout), and the
   per-element phase lives in inline custom properties next to the geometry it belongs to. */
/* On .hero-network, not on the SVG: the logos are siblings of the SVG, not children, so a
   variable scoped to the SVG never reaches them -- and an animation shorthand whose duration
   resolves to nothing is dropped whole, silently. */
.hero-network {
  --beat: 920ms;
  /* One segment of travel, constant regardless of how long the wire is. main.js reads both of
     these off the element, so the rhythm is defined in exactly one place. */
  --pulse-leg: 790ms;
  /* Long on purpose: the blue has to drain back out slowly. At 460ms the return read as a
     blink -- the eye caught the chip changing rather than just registering that it had. */
  --react-time: 1100ms;
}

.net__heart,
.net__halo,
.net__shock {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}

.net__heart {
  animation: net-heartbeat var(--beat) infinite;
}

/* Contraction is fast and the release is slow -- a symmetric ease is what makes a "pulse" read
   as a throb instead of a heartbeat. */
@keyframes net-heartbeat {
  0% {
    transform: scale(1);
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1);
  }
  7% {
    transform: scale(1.06);
    animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1);
  }
  19% {
    transform: scale(1.012);
    animation-timing-function: linear;
  }
  /* ~120ms of hold: the gap between "lub" and "dub". */
  32% {
    transform: scale(1.012);
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1);
  }
  40% {
    transform: scale(1.03);
    animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
  }
  100% {
    transform: scale(1);
  }
}

.net__halo {
  opacity: 0;
  animation: net-halo var(--beat) ease-out infinite;
}

@keyframes net-halo {
  0% {
    transform: scale(0.92);
    opacity: 0;
  }
  7% {
    transform: scale(1);
    opacity: 0.55;
  }
  58% {
    transform: scale(1.5);
    opacity: 0;
  }
  100% {
    transform: scale(1.5);
    opacity: 0;
  }
}

/* Dissipates at ~60% of the ambient glow's radius (r250 -> ~150) rather than running to the edge,
   so the wave suggests reach without drawing a hard ring across the logos. */
.net__shock {
  opacity: 0;
  animation: net-shock var(--beat) cubic-bezier(0.2, 0.7, 0.3, 1) infinite;
}

@keyframes net-shock {
  0% {
    transform: scale(1);
    opacity: 0.32;
  }
  100% {
    transform: scale(2.42);
    opacity: 0;
  }
}

/* ---------- Pulses travelling the wires ----------
   These are one-shots, not loops: main.js decides on every beat which branches actually fire, so
   the network never repeats the same pattern twice. The class is added to start a run and removed
   on animationend. */
.net__pulse {
  offset-path: none; /* the real path is set inline, per wire */
  offset-distance: 0%;
  offset-rotate: auto;
  transform-box: fill-box;
  /* The head of the comet -- not its centre -- is what rides the path. */
  transform-origin: 100% 50%;
  opacity: 0;
}

.net__pulse.is-firing {
  animation: net-pulse var(--pulse-leg) linear 1 both;
}

.net__pulse-glow {
  fill: url(#pulseHalo);
}

.net__pulse-core {
  fill: url(#pulseCore);
}

/* The comet leaves the mark it starts from as a spark and opens up as it runs: scaling about
   its own head (see transform-origin above) means the head stays on the wire and the tail grows
   out behind it, so the light reads as gathering along the wire rather than as a fixed dash
   being slid from one end to the other. */
@keyframes net-pulse {
  0% {
    offset-distance: 0%;
    opacity: 0;
    transform: scale(0.28);
  }
  10% {
    opacity: 1;
    transform: scale(0.42);
  }
  62% {
    transform: scale(1);
  }
  88% {
    opacity: 1;
    transform: scale(1.04);
  }
  100% {
    offset-distance: 100%;
    opacity: 0;
    transform: scale(1.08);
  }
}

/* The wire lifts while its own pulse is on it and settles straight back to the base opacity
   underneath. */
.net__wire-lit {
  opacity: 0;
}

.net__wire-lit.is-lit {
  animation: net-wire-lit var(--pulse-leg) ease-out 1;
}

@keyframes net-wire-lit {
  0% {
    opacity: 0;
  }
  18% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

/* ---------- Logos reacting as the pulse lands ----------
   The chips rest white and only take a blue cast while the pulse is on them. The peak is
   deliberately shallow -- a hint of blue and a hair of scale; anything stronger turned the
   reaction into a blink that pulled the eye away from the headline. */
.hero-network__logo.is-hit {
  animation: net-logo-react var(--react-time) linear 1;
}

/* The halo reacts with scale alone. Its resting filter now differs per depth tier (each tier
   carries its own blur), and a keyframe that restated the filter would have to exist once per
   tier -- and would flatten every tier to whichever blur it hardcoded. Scale is tier-agnostic,
   stays on the compositor, and at this distance is all the reaction needs to read. */
.hero-network__logo--back.is-hit {
  animation-name: net-logo-react-halo;
}

@keyframes net-logo-react {
  0% {
    transform: translate(-50%, -50%) scale(1);
    filter: grayscale(1) brightness(1) sepia(1) hue-rotate(180deg) saturate(1.7) contrast(1.15);
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1);
  }
  11% {
    transform: translate(-50%, -50%) scale(1.025);
    filter: grayscale(1) brightness(1.02) sepia(1) hue-rotate(180deg) saturate(3) contrast(1.12);
    animation-timing-function: cubic-bezier(0.33, 0, 0.5, 1);
  }
  100% {
    transform: translate(-50%, -50%) scale(1);
    filter: grayscale(1) brightness(1) sepia(1) hue-rotate(180deg) saturate(1.7) contrast(1.15);
  }
}

/* ---------- Reduced motion: hard override ---------- */
@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal-child {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  /* The network holds its resting frame: no beat, no pulses in flight, no logo reactions. The
     core keeps one very slow breath so it doesn't read as a broken static image. main.js checks
     the same query and never starts the emitter at all, so these are belt and braces. */
  .net__heart,
  .net__shock,
  .net__pulse,
  .net__wire-lit,
  .hero-network__logo {
    animation: none !important;
  }

  .net__pulse,
  .net__wire-lit,
  .net__shock {
    opacity: 0 !important;
  }

  .net__halo {
    animation: net-halo-calm 7s ease-in-out infinite !important;
  }

  @keyframes net-halo-calm {
    0%,
    100% {
      opacity: 0.16;
    }
    50% {
      opacity: 0.34;
    }
  }

  * {
    scroll-behavior: auto !important;
  }
}

@keyframes net-logo-react-halo {
  0% {
    transform: translate(-50%, -50%) scale(1);
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1);
  }
  11% {
    transform: translate(-50%, -50%) scale(1.07);
    animation-timing-function: cubic-bezier(0.33, 0, 0.5, 1);
  }
  100% {
    transform: translate(-50%, -50%) scale(1);
  }
}
