/*
 * motion.css -- GlowPilots animation engine.
 * Source of truth for [data-reveal] baseline, staggered children, count-up
 * stat styling, scroll-progress bar, animated-SVG draw-in, and hover helpers.
 *
 * Load order: LAST (tokens -> base -> typography -> components -> sections ->
 * visuals -> motion) so reduced-motion and reveal rules win the cascade.
 *
 * Token law: only --gp-* custom properties. No raw hex (all color/shadow/space
 * comes from tokens.css). Transition tokens (--gp-transition-*) already zero to
 * 0ms under prefers-reduced-motion; this sheet adds explicit @media guards for
 * keyframes, transforms, and draw-ins that tokens alone cannot neutralize.
 *
 * Reduced-motion contract: transform / scale / draw-in motion is disabled.
 * Opacity-only fades are retained (no vestibular risk). Nothing is hidden or
 * unreachable when motion is off.
 */

/* === REVEAL ON SCROLL (baseline) === */

/*
 * Baseline hidden state. JS adds .is-revealed when the element enters view.
 * If JS never runs (disabled / error), a .no-js-fallback safety net below
 * keeps content visible.
 */
[data-reveal] {
  opacity: 0;
  transform: translateY(var(--gp-space-5)); /* 24px */
  transition:
    opacity var(--gp-transition-slow),
    transform var(--gp-transition-slow);
  will-change: opacity, transform;
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* === STAGGERED CHILDREN === */

/*
 * On a parent marked [data-reveal-stagger], JS sets --i (zero-based index) on
 * each direct child. Children cascade in via transition-delay derived from --i.
 * The parent reveals first; children follow once the parent is .is-revealed.
 */
[data-reveal-stagger] > * {
  --i: 0;
  --gp-stagger-step: 70ms;
  opacity: 0;
  transform: translateY(var(--gp-space-4)); /* 16px */
  transition:
    opacity var(--gp-transition-slow),
    transform var(--gp-transition-slow);
  transition-delay: calc(var(--i) * var(--gp-stagger-step));
}

[data-reveal-stagger].is-revealed > * {
  opacity: 1;
  transform: translateY(0);
}

/* === COUNT-UP STATS === */

/*
 * Stat values animated by JS (data-count-to / data-count-suffix). The number
 * sits in a fixed-width, tabular-figure container so the layout does not jitter
 * while digits tick up.
 */
.gp-count {
  font-family: var(--gp-font-display);
  font-size: var(--gp-text-h1);
  line-height: var(--gp-leading-h1);
  font-weight: var(--gp-weight-bold);
  color: var(--gp-primary);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  letter-spacing: -0.01em;
  display: inline-block;
}

.gp-count__suffix {
  font-size: 0.6em;
  font-weight: var(--gp-weight-semibold);
  color: var(--gp-slate-700);
  margin-left: var(--gp-space-1);
  vertical-align: baseline;
}

.gp-count.is-counting {
  color: var(--gp-primary);
}

/* === SCROLL PROGRESS BAR === */

/*
 * Slim Plasma bar pinned under the sticky header. JS sets --gp-scroll-progress
 * (0 to 1); the inner fill scales horizontally from the left. Sits just below
 * --gp-nav-height so it never overlaps nav content.
 */
.gp-scroll-progress {
  position: fixed;
  top: var(--gp-nav-height);
  left: 0;
  right: 0;
  height: 3px;
  z-index: var(--gp-z-sticky);
  pointer-events: none;
  background: transparent;
}

.gp-scroll-progress__fill {
  height: 100%;
  width: 100%;
  transform: scaleX(var(--gp-scroll-progress, 0));
  transform-origin: left center;
  background: linear-gradient(
    90deg,
    var(--gp-primary) 0%,
    var(--gp-secondary) 100%
  );
  /* No transition: the fill tracks scroll position in real time. */
}

/* === ANIMATED SVG DRAW-IN === */

/*
 * Diagram lines draw themselves in (stroke-dashoffset) and nodes fade up in
 * stages once the host element gains .is-drawn (added by JS on reveal). The
 * SVG must declare its path length so dasharray math is stable; partials set
 * pathLength="1" on animated <path> elements and rely on these rules.
 */
.gp-draw path,
.gp-draw line,
.gp-draw polyline {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.gp-draw.is-drawn path,
.gp-draw.is-drawn line,
.gp-draw.is-drawn polyline {
  animation: gp-draw-line var(--gp-svg-draw-duration, 900ms) ease forwards;
}

.gp-draw [data-draw-stage] {
  opacity: 0;
}

.gp-draw.is-drawn [data-draw-stage] {
  animation: gp-stage-fade 500ms ease forwards;
}

.gp-draw.is-drawn [data-draw-stage='1'] { animation-delay: 200ms; }
.gp-draw.is-drawn [data-draw-stage='2'] { animation-delay: 450ms; }
.gp-draw.is-drawn [data-draw-stage='3'] { animation-delay: 700ms; }
.gp-draw.is-drawn [data-draw-stage='4'] { animation-delay: 950ms; }

@keyframes gp-draw-line {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}

@keyframes gp-stage-fade {
  from { opacity: 0; transform: translateY(var(--gp-space-2)); }
  to   { opacity: 1; transform: translateY(0); }
}

/* === HOVER MICRO-INTERACTIONS === */

/*
 * Reusable hover helpers. Compose onto cards, links, and buttons. All driven by
 * --gp-transition-base so the reduced-motion token override zeros them, and the
 * @media block below removes the transform deltas entirely.
 */
.gp-hover-lift {
  transition:
    transform var(--gp-transition-base),
    box-shadow var(--gp-transition-base);
}

.gp-hover-lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--gp-shadow-md);
}

.gp-hover-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--gp-space-2);
}

.gp-hover-arrow__icon {
  transition: transform var(--gp-transition-base);
}

.gp-hover-arrow:hover .gp-hover-arrow__icon,
.gp-hover-arrow:focus-visible .gp-hover-arrow__icon {
  transform: translateX(var(--gp-space-1));
}

.gp-press {
  transition: transform var(--gp-transition-base);
}

.gp-press:active {
  transform: scale(var(--gp-btn-primary-scale-press));
}

/* === REDUCED MOTION GUARDS === */

/*
 * Tokens already zero --gp-transition-* under reduced motion, which neutralizes
 * the transition-driven helpers above. These explicit rules additionally:
 *   - cancel keyframe animations (draw-in, staged fade) that tokens cannot touch
 *   - strip residual transform offsets so reveal/stagger become opacity-only
 *   - guarantee nothing stays hidden when JS or motion is off
 */
@media (prefers-reduced-motion: reduce) {

  [data-reveal] {
    transform: none;
  }

  [data-reveal].is-revealed {
    transform: none;
  }

  [data-reveal-stagger] > * {
    transform: none;
    transition-delay: 0ms;
  }

  [data-reveal-stagger].is-revealed > * {
    transform: none;
  }

  /* Draw-in becomes a static, fully visible diagram. */
  .gp-draw path,
  .gp-draw line,
  .gp-draw polyline,
  .gp-draw.is-drawn path,
  .gp-draw.is-drawn line,
  .gp-draw.is-drawn polyline {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    animation: none;
  }

  .gp-draw [data-draw-stage],
  .gp-draw.is-drawn [data-draw-stage] {
    opacity: 1;
    transform: none;
    animation: none;
  }

  /* Hover helpers keep their states reachable but lose the motion deltas. */
  .gp-hover-lift:hover {
    transform: none;
  }

  .gp-hover-arrow:hover .gp-hover-arrow__icon,
  .gp-hover-arrow:focus-visible .gp-hover-arrow__icon {
    transform: none;
  }

  .gp-press:active {
    transform: none;
  }

  /* Scroll bar still fills, just without any easing artifacts. */
  .gp-scroll-progress__fill {
    transition: none;
  }
}

/* === NO-JS SAFETY NET === */

/*
 * If JavaScript never executes, IntersectionObserver never adds .is-revealed,
 * which would leave [data-reveal] content permanently at opacity:0. The theme
 * sets a `js` class on <html> via the inline head script; in its absence we
 * force everything visible so content is never trapped behind motion.
 */
html:not(.js) [data-reveal],
html:not(.js) [data-reveal-stagger] > * {
  opacity: 1;
  transform: none;
}
