/* ==========================================================================
   Hero — scroll-driven sequence.

   Structure, and why:

     .hero          tall spacer. Its height IS the scroll budget. The visitor
                    scrolls through it natively — no preventDefault anywhere.
     .hero__stage   position:sticky child, pinned for the spacer's full travel.
                    This is what produces the "locked" feeling. When the spacer
                    ends, the pin releases and the page continues normally.

   JS writes exactly one value into this file: --hero-progress (0 → 1) on the
   stage. Everything that reacts to scroll below is derived from it in CSS.
   ========================================================================== */

.hero {
  position: relative;
  height: calc(var(--hero-scroll-vh) * 1vh);
  background-color: var(--color-bg-inverse);
}

/* svh = "small viewport height": stable while mobile browser chrome collapses.
   Using it for the spacer keeps the scroll budget from resizing mid-gesture. */
@supports (height: 1svh) {
  .hero {
    height: calc(var(--hero-scroll-vh) * 1svh);
  }
}

.hero__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: clip;
  isolation: isolate;
}

@supports (height: 1svh) {
  .hero__stage {
    height: 100svh;
  }
}


/* --- Media -------------------------------------------------------------- */

.hero__media {
  position: absolute;
  inset: 0;
  background-color: var(--color-ink-950);
}

.hero__video,
.hero__canvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Fade in once the renderer has painted its first real frame, so the
     visitor never sees a flash of empty black. */
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-out);
}

/* Only the selected renderer is present in the layer tree. */
.hero[data-renderer="video"] .hero__canvas,
.hero[data-renderer="sequence"] .hero__video {
  display: none;
}

.hero[data-state="ready"] .hero__video,
.hero[data-state="ready"] .hero__canvas {
  opacity: 1;
}

/* Legibility scrim.
   Two layers rather than one flat wash. The footage is bright and the copy is
   left-anchored, so a horizontal falloff buys contrast exactly where the type
   sits and leaves the subject on the right at full luminance. A uniform
   overlay strong enough to fix the type would grey out the whole shot. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(
      to right,
      rgb(8 9 11 / 0.78) 0%,
      rgb(8 9 11 / 0.45) 28%,
      rgb(8 9 11 / 0.08) 55%,
      rgb(8 9 11 / 0) 72%
    ),
    linear-gradient(
      to bottom,
      rgb(8 9 11 / 0.5) 0%,
      rgb(8 9 11 / 0.06) 28%,
      rgb(8 9 11 / 0.12) 62%,
      rgb(8 9 11 / 0.66) 100%
    );
}

/*
   Second scrim layer, faded in by scroll position.

   The footage changes exposure completely across its run: it opens on a dark
   sunrise field and ends on a bright aerial resistivity map inside a pale
   GCAGRI interface. A single fixed scrim cannot serve both — tuned for the
   opening it leaves the closing captions unreadable, tuned for the closing it
   greys out the opening.

   So this layer is absent for the first third and ramps in as the imagery
   brightens. It still falls to zero before the right edge, so the map itself
   stays vivid where no text sits over it.
*/
.hero__scrim::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    rgb(8 9 11 / 0.88) 0%,
    rgb(8 9 11 / 0.6) 30%,
    rgb(8 9 11 / 0.12) 60%,
    rgb(8 9 11 / 0) 78%
  );
  opacity: clamp(0, calc((var(--hero-progress, 0) - 0.34) * 3), 1);
}

/* Narrow viewports: the copy spans the full width, so a horizontal falloff
   would darken one edge for no reason. Anchor from the bottom instead. */
@media (max-width: 48rem) {
  .hero__scrim {
    background: linear-gradient(
      to bottom,
      rgb(8 9 11 / 0.45) 0%,
      rgb(8 9 11 / 0.05) 24%,
      rgb(8 9 11 / 0.4) 58%,
      rgb(8 9 11 / 0.82) 100%
    );
  }

  .hero__scrim::after {
    background: linear-gradient(
      to bottom,
      rgb(8 9 11 / 0.3) 0%,
      rgb(8 9 11 / 0.15) 30%,
      rgb(8 9 11 / 0.7) 70%,
      rgb(8 9 11 / 0.9) 100%
    );
  }
}

/* --- Scene track --------------------------------------------------------- */

/*
   All eight captions occupy one grid cell so they cross-fade in place.

   They are aligned to the END of that cell rather than the start: the closing
   scene carries two buttons and is much taller than the others, and bottom
   alignment guarantees it can never push past the foot of the stage on a short
   viewport. Scenes 1-7 are near-identical in height, so their headlines still
   land within a few pixels of each other.

   Opacity and transform are written by HeroScenes.js. Nothing here transitions
   — the scroll position *is* the timeline, so a CSS transition would fight it
   and lag behind the footage.
*/
.hero__scenes {
  position: absolute;
  inset-inline: 0;
  bottom: clamp(5.5rem, 15vh, 10rem);
  z-index: 2;
  display: grid;
  align-items: end;
  padding-inline: var(--gutter);
  color: var(--color-fg-inverse);
  pointer-events: none;
}

.scene {
  grid-area: 1 / 1;
  max-width: 42rem;
  opacity: 0;
  /* No will-change: promoting all eight would cost eight layers. The active
     scene is promoted by the translate3d that JS writes to it. */
}

.scene__headline {
  /* Deliberately smaller than a conventional hero title: French headlines here
     run to five words and must hold one or two lines at every width. */
  font-size: clamp(2rem, 1.15rem + 3.4vw, 4.25rem);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

.scene__body {
  max-width: 40ch;
  margin-top: var(--space-5, 1.25rem);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--color-fg-inverse-muted);
  text-wrap: pretty;
}

.scene__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-8);
}

/* --- Buttons ------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.875rem;
  padding: 0 var(--space-6);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-tight);
  border-radius: 999px;
  border: 1px solid transparent;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.btn--primary {
  color: var(--color-ink-950);
  background-color: var(--color-paper-050);
}

.btn--primary:hover,
.btn--primary:focus-visible {
  background-color: var(--color-paper-000);
}

.btn--ghost {
  color: var(--color-fg-inverse);
  border-color: rgb(246 246 244 / 0.35);
}

.btn--ghost:hover,
.btn--ghost:focus-visible {
  border-color: rgb(246 246 244 / 0.8);
  background-color: rgb(246 246 244 / 0.08);
}

/* --- Scroll cue --------------------------------------------------------- */

.hero__cue {
  position: absolute;
  inset-inline: 0;
  bottom: var(--space-8);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  pointer-events: none;

  /* Disappears almost immediately — its job is done the moment they scroll. */
  opacity: clamp(0, calc(1 - var(--hero-progress, 0) * 22), 1);
}

.hero__cue-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--color-fg-inverse-muted);
}

.hero__cue-line {
  width: 1px;
  height: var(--space-8);
  background: linear-gradient(
    to bottom,
    rgb(246 246 244 / 0.5),
    rgb(246 246 244 / 0)
  );
}

/* --- Preload indicator -------------------------------------------------- */

/* A single hairline across the foot of the stage. No spinner, no percentage. */
.hero__progress {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 3;
  height: 2px;
  opacity: 1;
  transition: opacity var(--duration-base) var(--ease-out);
}

.hero[data-state="ready"] .hero__progress {
  opacity: 0;
}

.hero__progress-bar {
  height: 100%;
  width: 100%;
  background-color: var(--color-accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--duration-base) var(--ease-out);
}

/* ==========================================================================
   Reduced motion — no scroll choreography at all.

   The spacer collapses to a single viewport, the pin becomes a no-op, and the
   hero renders as a still frame. ScrollScene detects the same query and never
   attaches its listeners.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .hero {
    height: 100vh;
  }

  @supports (height: 1svh) {
    .hero {
      height: 100svh;
    }
  }

  /* HeroScenes.showStatic() reveals the opening and closing scenes; the rest
     stay at opacity 0. Nothing moves. */
  .hero__cue {
    display: none;
  }

  .scene {
    position: static;
  }

  .hero__video,
  .hero__canvas {
    transition: none;
  }
}
