/* ===========================================================================
   Animations — keyframes, scroll-reveal classes, and the reduced-motion
   contract.

   Everything here animates only `transform`, `opacity`, `filter` or
   `stroke-dashoffset`, so nothing in this file triggers layout.
   =========================================================================== */

/* ------------------------------------------------------------- keyframes */

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Expanding ring used by live dots and map pins. */
@keyframes ping {
  0% {
    transform: scale(0.85);
    opacity: 0.9;
  }
  70%,
  100% {
    transform: scale(1.9);
    opacity: 0;
  }
}

/* The live end of the journey timeline: a gold halo swelling under the marker,
   in step with the ring pinging out of it. */
@keyframes now-glow {
  0%,
  100% {
    box-shadow: 0 0 0 0 rgba(255, 198, 46, 0.34);
  }
  50% {
    box-shadow: 0 0 26px 6px rgba(255, 198, 46, 0.28);
  }
}

/* Same breath as now-glow, for the light bands, and teal to match the filled
   disc it sits under. The hairline base shadow rides along so the disc keeps
   its lift through the trough of the cycle. */
@keyframes now-glow-light {
  0%,
  100% {
    box-shadow: 0 1px 2px rgba(6, 32, 28, 0.06), 0 0 0 0 rgba(10, 178, 151, 0.4);
  }
  50% {
    box-shadow: 0 1px 2px rgba(6, 32, 28, 0.06), 0 0 26px 7px rgba(10, 178, 151, 0.42);
  }
}

@keyframes float-y {
  0%,
  100% {
    transform: translateZ(70px) translateY(0);
  }
  50% {
    transform: translateZ(70px) translateY(-9px);
  }
}

/* A short segment running the length of a hero network link. */
@keyframes net-travel {
  from {
    stroke-dashoffset: 0;
  }
  to {
    stroke-dashoffset: -1005;
  }
}

@keyframes net-pulse {
  0%,
  100% {
    opacity: 0.45;
    scale: 0.8;
  }
  50% {
    opacity: 1;
    scale: 1.25;
  }
}

@keyframes marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

@keyframes bar-grow {
  0%,
  100% {
    transform: scaleY(0.65);
  }
  50% {
    transform: scaleY(1);
  }
}

/* The network band's growth lines. scaleX from a left origin rather than an
   animated width, so it composites like everything else in this file. It runs
   once and holds — `forwards` is the whole point. */
@keyframes netstat-fill {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

/* Slow drift for ambient washes — long enough to read as light, not motion. */
@keyframes drift {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  50% {
    transform: translate3d(3%, -4%, 0) scale(1.08);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.ambient::before {
  animation: drift 22s var(--ease-in-out) infinite;
}

.ambient::after {
  animation: drift 28s var(--ease-in-out) infinite reverse;
}

/* -------------------------------------------------------- scroll reveal */
/* animations.js adds .is-revealed via IntersectionObserver. The initial state
   is applied only when the observer is actually running (html.js-reveal), so
   a visitor with JS disabled sees fully-rendered content rather than a blank
   page. */

.js-reveal .reveal,
.js-reveal .reveal-up,
.js-reveal .reveal-scale,
.js-reveal .reveal-left,
.js-reveal .reveal-right {
  opacity: 0;
  transition: opacity var(--dur-reveal) var(--ease-out), transform var(--dur-reveal) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: transform, opacity;
}

.js-reveal .reveal-up {
  transform: translateY(26px);
}

.js-reveal .reveal-scale {
  transform: scale(0.965);
}

.js-reveal .reveal-left {
  transform: translateX(-26px);
}

.js-reveal .reveal-right {
  transform: translateX(26px);
}

.js-reveal .is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Stagger helper — set --reveal-delay per child from markup. */
.stagger > * {
  --reveal-delay: calc(var(--n, 0) * 80ms);
}

/* Hero entrance. Runs once on load, independent of the scroll observer, so
   the fold is never blank waiting for an intersection. */
.hero__copy > * {
  animation: hero-in 900ms var(--ease-out) backwards;
  animation-delay: calc(var(--n, 0) * 90ms + 120ms);
}

/* The badge and the headline get their own treatments; the rest of the column
   keeps the plain fade-up above. */
.hero__copy > .pill {
  animation: badge-in 700ms var(--ease-spring) 80ms backwards;
}

.hero__visual {
  animation: hero-in 1100ms var(--ease-out) backwards;
  animation-delay: 320ms;
}

/* The headline wipes up from its own baseline instead of just fading — the
   clip is what makes it read as type being set rather than a block appearing. */
@keyframes headline-in {
  from {
    opacity: 0;
    transform: translateY(26px);
    clip-path: inset(0 0 110% 0);
  }
  to {
    opacity: 1;
    transform: none;
    clip-path: inset(0 0 -20% 0);
  }
}

/* The hero badge arrives slightly scaled-down so it pops rather than slides. */
@keyframes badge-in {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.9);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes hero-in {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ------------------------------------------------------- reduced motion */
/* Two levels: kill decorative loops entirely, and collapse transitions to a
   near-instant crossfade so state changes are still perceivable. */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* Reveal targets must end up visible even though their transition is gone. */
  .js-reveal .reveal,
  .js-reveal .reveal-up,
  .js-reveal .reveal-scale,
  .js-reveal .reveal-left,
  .js-reveal .reveal-right {
    opacity: 1;
    transform: none;
  }

  /* Continuous loops are removed rather than sped up. */
  .marquee__track,
  .hero-flow__pulse path,
  .hero-flow__orbit,
  .ambient::before,
  .ambient::after,
  .pill__dot--live::after,
  .masked-call__dial::after,
  .reach__pin::after,
  .journey__item--now .journey__marker,
  .journey__item--now .journey__marker::after,
  .hero-flow__sweep,
  .hero-flow__bars i,
  .bento__shield {
    animation: none !important;
  }

  /* The ping ring would otherwise sit frozen at its start scale on the marker. */
  .journey__item--now .journey__marker::after {
    display: none;
  }

  /* The hero entrance is decoration; land everything in its final state. */
  .hero__copy > *,
  .hero__copy > .pill,
  .hero__visual,
  .hero__title {
    animation: none !important;
  }

  .hero__title {
    clip-path: none;
  }

  .mv-card:hover .mv-card__icon {
    transform: none;
  }

  .steps__rail::after {
    transform: scaleX(1);
  }
}
