/* Scroll-immersion layer: section-to-section scene transitions, plus the
   parallax the video bands were missing. Pairs with js/scroll-scene.js.

   Nothing here claims a property another sheet already owns:
   - the scene zoom sits on section.section, which no other sheet transforms;
   - video parallax sits on the <video> itself, while media.css owns the
     transform/clip-path on the .showcase__media wrapper above it and
     motion.js owns the <img> inside the picture bands. Nesting composes the
     two transforms instead of one overwriting the other. */

/* ================= 1. section scenes ================= */

/* The page reads as stacked scenes rather than one flat ribbon: a section
   recedes a few percent as it leaves and advances as it arrives.

   The whole effect is deliberately confined to the SEAMS. Both animations use
   fill-mode:none, so outside their range no value is contributed at all and
   the section falls back to its base style — scale 1, opacity 1, filter none —
   for every pixel of scroll it spends occupying the viewport. That also means
   no filter layer is rasterized while you are reading, and nothing is pinned
   in a transformed state on page load. */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .section {
      /* linear, not --ease: the scroll position IS the progress. An eased
         curve on top of it would make a constant-speed scroll produce
         non-constant motion. Shape lives in the keyframes instead. */
      animation-name: ls-scene-advance, ls-scene-recede, ls-scene-resolve, ls-scene-dissolve;
      animation-timeline: view(), view(), view(), view();
      animation-range:
        entry 0% entry 75%,
        exit 25% exit 100%,
        entry 0% entry 40%,
        exit 60% exit 100%;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: none;
      transform-origin: 50% 50%;
    }
  }
}

/* entry/exit are each capped at min(section height, viewport height) of
   scroll, and `contain` — the stretch where the section fills the screen —
   sits between them carrying no animation. Ending the zoom at entry 75% and
   starting it again at exit 25% widens that resting gap further, so even a
   section shorter than the viewport is fully at rest while it is centred. */

/* 0.965 is a 3.5% delta: ~48px of edge travel on a 1400px viewport, plainly
   readable as depth, while a 16px body line changes by half a pixel — below
   the threshold where scaled text goes soft. A 1.0->1.2 zoom would move a
   92px display heading by 18px and make the copy unreadable mid-seam. */
@keyframes ls-scene-advance {
  from { transform: scale(.965); }
  to   { transform: scale(1); }
}

@keyframes ls-scene-recede {
  from { transform: scale(1); }
  to   { transform: scale(.965); }
}

/* Opacity and blur run on a much tighter range than the zoom (the first 40%
   of entry, the last 40% of exit) and resolve at 55% of even that — by which
   point the section covers barely a fifth of the screen edge. Text is never
   dimmed or blurred anywhere a reader could be looking.

   3px is affordable precisely because of where it lives: `filter: none` at
   55% means the property is genuinely absent — not blur(0px), which would
   still force the section into a rasterized layer — for the rest of the range
   and for the entire time the section is on screen. The blur exists only
   while the section is a sliver at the viewport edge. It is there to bridge
   the seam so two sections read as one depth move rather than two panels
   swapping. */
@keyframes ls-scene-resolve {
  from { opacity: .4; filter: blur(3px); }
  55%  { opacity: 1; filter: none; }
  to   { opacity: 1; filter: none; }
}

@keyframes ls-scene-dissolve {
  from { opacity: 1; filter: none; }
  45%  { opacity: 1; filter: none; }
  to   { opacity: .4; filter: blur(3px); }
}

/* ---------- fallback: no scroll-driven animations ---------- */

/* js/scroll-scene.js arms this only when CSS.supports() says view() is
   missing AND IntersectionObserver exists, so the path is deterministic and
   the two never both run.

   The rest state is the DEFAULT and .is-scene-out is the added state, not the
   other way round: a section can never flash dimmed for the frame between
   first paint and the observer's first callback. No blur here — an
   unlinked 900ms blur transition across a full section is the expensive,
   low-information case the scroll-driven path avoids by construction. */
:root.js-scene .section {
  transition: transform .9s var(--ease), opacity .5s var(--ease);
}

:root.js-scene .section.is-scene-out {
  transform: scale(.965);
  opacity: .4;
}

/* ================= 2. video parallax ================= */

/* motion.js drifts .showcase__media img by writing --parallax on the <img>.
   The two video bands had none, so a photo band and a video band moved at
   different rates through the same scroll. scroll-scene.js writes the same
   property, with the same curve and the same -23% travel, onto the <video>
   elements only — no element is ever written by both files. */

/* A parallaxed element has to be oversized on the axis it moves or it drags
   an empty edge into frame. 130% matches .showcase__media img exactly: at the
   -23%-of-own-height extreme the element sits at -29.9%..100.1% of its box,
   so the crop stays covered end to end. Both selectors are two classes deep
   to out-specify media.css's height:100% regardless of link order. */
.showcase--video .showcase__video,
.stats .stats__video {
  height: 130%;
  transform: translate3d(0, var(--parallax, 0), 0);
}

/* will-change is bound to .is-scrolling, which js/interactions.js already
   adds to <html> for the duration of a scroll and drops when the damped
   velocity reaches zero. The transform only changes while scrolling, so this
   promotes the layer exactly when it earns one and releases it otherwise —
   no timer, no second bookkeeping path. */
:root.is-scrolling .showcase--video .showcase__video,
:root.is-scrolling .stats .stats__video {
  will-change: transform;
}

/* ================= 3. reduced motion ================= */

/* The scroll-driven path is pure CSS, so JS opting out is not enough — this
   has to switch it off from the sheet. Page stays fully usable and static. */
@media (prefers-reduced-motion: reduce) {
  .section {
    animation: none !important;
    transition: none !important;
    transform: none !important;
    opacity: 1 !important;
    filter: none !important;
  }

  .showcase--video .showcase__video,
  .stats .stats__video {
    height: 100% !important;
    transform: none !important;
    will-change: auto !important;
  }
}
