/* ==========================================================================
   scroll-fx.css — scroll choreography
   --------------------------------------------------------------------------
   The effects mntra.co gets from GSAP + ScrollTrigger + SplitText, rebuilt on
   CSS transitions and one IntersectionObserver.

   Why not just load GSAP: gsap + ScrollTrigger + SplitText is roughly 120KB of
   JavaScript before a single frame of film plays. On a site whose whole point
   is showing footage, that budget is better spent on the footage. Everything
   below is transform/opacity only, which stays on the compositor.

   Three effects:
     1. word reveal   — words masked, rising into place, staggered
     2. mark handoff  — hero wordmark shrinks away as the nav wordmark arrives
     3. stagger pop   — items scale up in a scattered order
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Word reveal
   Each word gets an overflow-hidden mask; the inner span starts pushed below
   it and slides up. Descenders need the padding/negative-margin pair, or the
   mask clips the tails off g, y and p.
   -------------------------------------------------------------------------- */

[data-word-reveal] .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

[data-word-reveal] .word > i {
  display: inline-block;
  font-style: inherit;
  transform: translateY(115%);
  transition: transform 900ms var(--ease-reveal, cubic-bezier(.16, 1, .3, 1));
  transition-delay: calc(var(--wi, 0) * 42ms);
  will-change: transform;
}

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

/* Until the splitter has run, the heading must stay readable — a failed script
   should never leave a page of invisible headlines. */
[data-word-reveal]:not(.is-split) { opacity: 1; }

/* --------------------------------------------------------------------------
   2. Hero wordmark → nav wordmark handoff
   Driven by --p (0 → 1 scroll progress) set from JS.
   -------------------------------------------------------------------------- */

.hero-mark {
  --p: 0;
  display: block;
  font-family: var(--display);
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 0.86;
  font-size: clamp(2.75rem, 1.2rem + 7.5vw, 9rem);
  margin: 0 0 clamp(1.5rem, 3vw, 2.5rem);
  transform-origin: left top;
  transform: scale(calc(1 - 0.72 * var(--p)));
  opacity: calc(1 - 1.9 * var(--p));
  will-change: transform, opacity;
  text-wrap: balance;
}

/* The nav wordmark is absent until the hero one has gone, so the two are never
   on screen together. Defaults to visible — only a page that actually has a
   .hero-mark hides it, and only until the handoff. */
.js .site-header--over .wordmark { opacity: var(--mark-in, 1); transition: opacity 260ms ease; }
.js .site-header--over.is-stuck .wordmark { opacity: 1; }

/* --------------------------------------------------------------------------
   2b. Film index reveal
   The work leads the page, so the titles get the entrance rather than a
   headline. Each rises out of its own mask, one after the next.
   -------------------------------------------------------------------------- */

[data-index-reveal] li {
  overflow: hidden;
}
[data-index-reveal] a {
  transform: translateY(105%);
  transition: transform 850ms var(--ease-reveal, cubic-bezier(.16, 1, .3, 1)),
              opacity 300ms ease,
              padding-inline-start var(--dur-med, 450ms) var(--ease-out, ease);
  transition-delay: calc(var(--fi, 0) * 80ms), calc(var(--fi, 0) * 80ms), 0ms;
  will-change: transform;
}
[data-index-reveal].is-revealed a { transform: none; }

/* --------------------------------------------------------------------------
   2c. Loop → grid transition
   The stage becomes a scroll runway. As you move down it, the full-bleed loop
   contracts into a rounded card and the film list clears out — so the page
   hands off from one big moving image to a grid of still ones without a cut.

   Only applied once JS has measured the runway; without it the stage is a
   plain single screen.
   -------------------------------------------------------------------------- */

/* The image holds its size the whole way — it doesn't shrink. The runway is
   just enough hold for the grid to climb over a still-full-bleed frame, so
   it's shorter than it was when there was a contraction to play out. */
.index-stage.is-runway { height: 150svh; }

.index-stage.is-runway .index-stage__frame { --p: 0; }

/* The list rises and clears as the grid comes up, so the two never fight for
   the same space. */
.index-stage.is-runway .index-stage__ui {
  opacity: calc(1 - 1.9 * var(--p));
  transform: translateY(calc(var(--p) * -3rem));
  pointer-events: auto;
  will-change: transform, opacity;
}
.index-stage.is-runway[data-past] .index-stage__ui { pointer-events: none; }

/* --------------------------------------------------------------------------
   3. Stagger pop
   Random per-item delay is assigned in JS to match mntra's from:"random".
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .js [data-pop] > * {
    opacity: 0;
    transform: scale(.72);
    transition: opacity 620ms var(--ease-reveal, cubic-bezier(.16, 1, .3, 1)),
                transform 750ms var(--ease-reveal, cubic-bezier(.16, 1, .3, 1));
    transition-delay: calc(var(--pi, 0) * 1ms);
  }
  .js [data-pop].is-revealed > * { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   Reduced motion
   Everything resolves to its final state immediately. Nothing rises, scales,
   or waits — the page simply is where the animation would have ended.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  [data-word-reveal] .word { overflow: visible; padding-bottom: 0; margin-bottom: 0; }
  [data-word-reveal] .word > i { transform: none !important; transition: none !important; }
  .hero-mark { transform: none !important; opacity: 1 !important; }
  .js .site-header--over .wordmark { opacity: 1 !important; }
  .js [data-pop] > * { opacity: 1 !important; transform: none !important; }
  [data-index-reveal] li { overflow: visible; }
  [data-index-reveal] a { transform: none !important; transition: none !important; }
}
