/* HYPER RED — cinematic
   The one scroll-driven sequence in the app: the signup hero pinning
   briefly while it transitions from full cinematic campaign into the
   race-operations page beneath it. Everything here is transform/opacity,
   driven by a single CSS custom property (--hero-progress, 0→1) that
   js/cinematic.js updates via rAF. See js/cinematic.js for the full
   explanation of why the pin/progress math lives in JS while the actual
   visual response lives here in CSS.

   Structural (not just duration=0) reduced-motion/mobile handling: the
   pin and the extra scroll room it needs (.hero-stage-spacer) only exist
   at all inside the min-width/no-preference media query below. Below
   that breakpoint, or with motion reduced, .hero-stage-spacer collapses
   to zero height and .race-brief is a normal static-flow element — there
   is no blank scroll zone left behind either way. */

.hero-stage {
  position: relative;
}

.hero-stage-spacer {
  height: 0;
}

@media (min-width: 768px) and (prefers-reduced-motion: no-preference) {
  html:not([data-reduce-motion="true"]) .hero-stage-spacer {
    /* Shortened from clamp(420px, 55vh, 620px) — that range made the
       transition feel like "I'm scrolling but nothing is moving" before
       the hero visibly responded (Section 7, item 17). Still a real
       scroll-scrubbed transition, just a noticeably more responsive one. */
    height: clamp(260px, 34vh, 440px);
  }

  html:not([data-reduce-motion="true"]) .hero-stage .race-brief {
    position: sticky;
    top: var(--header-height);
  }
}

/* ---- Progress-driven visual response ----
   --hero-progress is set on #heroStage by js/cinematic.js, 0 at rest and
   1 once the pin range is fully scrolled. Read here via var() with a 0
   fallback so nothing moves at all if the JS module fails to load for
   any reason (see the dynamic import + catch in js/main.js pattern —
   cinematic.js follows the same "never take the page down" rule). */
@media (min-width: 768px) and (prefers-reduced-motion: no-preference) {
  html:not([data-reduce-motion="true"]) .race-brief-visual-img {
    transform: scale(calc(1 + var(--hero-progress, 0) * 0.05)) translate3d(0, calc(var(--hero-progress, 0) * -2%), 0);
  }

  html:not([data-reduce-motion="true"]) .race-brief-visual-scrim {
    opacity: calc(var(--hero-progress, 0) * 0.5);
  }

  html:not([data-reduce-motion="true"]) .race-brief-bignum {
    opacity: calc(1 - var(--hero-progress, 0) * 0.85);
    transform: translateY(calc(0.06em - var(--hero-progress, 0) * 18px));
  }

  html:not([data-reduce-motion="true"]) .race-brief-title {
    transform-origin: left top;
    transform: scale(calc(1 - var(--hero-progress, 0) * 0.22)) translateY(calc(var(--hero-progress, 0) * -10px));
  }

  html:not([data-reduce-motion="true"]) .race-brief-top,
  html:not([data-reduce-motion="true"]) .race-brief-bottom {
    opacity: calc(1 - var(--hero-progress, 0) * 1.4);
    transform: translateY(calc(var(--hero-progress, 0) * 10px));
  }
}

/* ---- Reveal for the content that follows the hero ----
   Not tied to --hero-progress at all (see js/cinematic.js for why an
   overlap/slide-over-the-pinned-hero approach was deliberately avoided).
   A standard IntersectionObserver-triggered reveal on the normal-flow
   content below the spacer — the "campaign → operations" feel comes from
   the pin above it, this is just a quiet confirmation as it arrives. */
.hero-reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--dur-slow) var(--ease-cinematic), transform var(--dur-slow) var(--ease-cinematic);
}

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

@media (prefers-reduced-motion: reduce) {
  .hero-reveal { opacity: 1; transform: none; }
}

html[data-reduce-motion="true"] .hero-reveal {
  opacity: 1;
  transform: none;
}
