/* ==========================================================================
   KodeshWay — Motion & scroll storytelling (web)
   CANONICAL SOURCE — sync via build/sync_design.sh. Do not hand-edit copies.

   Apple-marketing-grade scroll choreography in pure CSS: reveal-on-view,
   parallax, pinned "stages" with cross-fading steps, kinetic display type,
   line-by-line verse wipes, and fanning glass card stacks.

   GATING IDIOM (mirrors KodeshWayOrg/teaching.css): every scroll-driven rule
   lives inside
       @media (prefers-reduced-motion: no-preference) {
         @supports (animation-timeline: view()) { … }
       }
   so it is a clean no-op under reduced-motion and in browsers without
   scroll-driven timelines (Safari today, Firefox flagged-off). The RESTING
   (un-animated) state authored outside that block IS the static fallback, so
   nothing is ever left hidden. Animate ONLY opacity + transform (compositor
   safe); never width/height/top/left/filter in keyframes.

   For .kw-reveal, js/reveal.js adds a JS IntersectionObserver floor in
   non-supporting browsers (Safari/Firefox) — see that file. NEVER put both
   .kw-reveal and [data-reveal] on the same element.
   ========================================================================== */

/* ── Reveal on view ───────────────────────────────────────────────────────
   Resting state = visible. Scroll-driven browsers animate it in; reveal.js
   gives non-supporting browsers the same entrance via .is-revealed. */
.kw-reveal { /* visible by default — this is the fallback */ }

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .kw-reveal {
      animation: kw-reveal-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
      will-change: opacity, transform;
    }
    .kw-reveal--scale { animation-name: kw-reveal-scale; }
    .kw-reveal--left  { animation-name: kw-reveal-left;  }
    .kw-reveal--right { animation-name: kw-reveal-right; }
  }
}

/* JS-floor (Safari/Firefox): reveal.js toggles .is-revealed. Hidden only once
   body.reveal-ready is set AND the browser lacks scroll timelines (reveal.js
   adds body.kw-no-scroll-tl in that case), so supporting browsers never
   double-hide. */
body.reveal-ready.kw-no-scroll-tl .kw-reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity var(--kw-dur-slow, 0.5s) var(--kw-ease, ease),
              transform var(--kw-dur-slow, 0.5s) var(--kw-ease, ease);
}
body.reveal-ready.kw-no-scroll-tl .kw-reveal--scale { transform: scale(0.94); }
body.reveal-ready.kw-no-scroll-tl .kw-reveal--left  { transform: translateX(-32px); }
body.reveal-ready.kw-no-scroll-tl .kw-reveal--right { transform: translateX(32px); }
body.reveal-ready.kw-no-scroll-tl .kw-reveal.is-revealed {
  opacity: 1;
  transform: none;
}

@keyframes kw-reveal-in    { from { opacity: 0; transform: translateY(28px); }  to { opacity: 1; transform: none; } }
@keyframes kw-reveal-scale { from { opacity: 0; transform: scale(0.94); }        to { opacity: 1; transform: none; } }
@keyframes kw-reveal-left  { from { opacity: 0; transform: translateX(-32px); }  to { opacity: 1; transform: none; } }
@keyframes kw-reveal-right { from { opacity: 0; transform: translateX(32px); }   to { opacity: 1; transform: none; } }

/* Stagger helper — apply to a container; children enter in sequence. Works in
   scroll-driven browsers via per-child animation-range offsets. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .kw-reveal-stagger > * {
      animation: kw-reveal-in linear both;
      animation-timeline: view();
      will-change: opacity, transform;
    }
    .kw-reveal-stagger > *:nth-child(1) { animation-range: entry 0%  entry 50%; }
    .kw-reveal-stagger > *:nth-child(2) { animation-range: entry 8%  entry 58%; }
    .kw-reveal-stagger > *:nth-child(3) { animation-range: entry 16% entry 66%; }
    .kw-reveal-stagger > *:nth-child(4) { animation-range: entry 24% entry 74%; }
    .kw-reveal-stagger > *:nth-child(5) { animation-range: entry 32% entry 82%; }
    .kw-reveal-stagger > *:nth-child(6) { animation-range: entry 40% entry 90%; }
  }
}

/* ── Parallax ──────────────────────────────────────────────────────────────
   Compositor-only translate keyed to the element's pass through the viewport
   (view() so each layer is self-contained). Resting = no transform. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .kw-parallax {
      animation: kw-parallax-y linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
      will-change: transform;
    }
    .kw-parallax--slow { --kw-px-shift: 5%; }
    .kw-parallax--fast { --kw-px-shift: 14%; }
  }
}
@keyframes kw-parallax-y {
  from { transform: translate3d(0, calc(var(--kw-px-shift, 9%) * -1), 0); }
  to   { transform: translate3d(0, var(--kw-px-shift, 9%), 0); }
}

/* ── Pinned stage ──────────────────────────────────────────────────────────
   A tall scroll track (.kw-stage) hosts a sticky 100vh pin (.kw-stage__pin).
   Step layers (.kw-stage__step) overlap in one grid cell and cross-fade across
   the track's named view-timeline as you scroll. Set --kw-stage-steps to the
   number of steps (drives track height + you set per-step ranges, see below).

   Fallback (no scroll timelines): the @supports-not block collapses the tall
   track, un-pins, and stacks the steps as normal static sections. */
/* RESTING / FALLBACK state = a calm stacked poster (no pin, all steps shown).
   This is what reduced-motion AND non-supporting browsers (Safari/Firefox) get,
   so nothing is ever hidden or overlapped illegibly. */
.kw-stage {
  position: relative;
  isolation: isolate;
  overflow: clip;
  --kw-stage-steps: 3;
  padding-block: clamp(3rem, 12vh, 8rem);
}
.kw-stage__pin {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(1.5rem, 6vh, 4rem);
  text-align: center;
}
.kw-stage__copy { position: relative; z-index: 2; }

/* PINNED, cross-fading mode — only where scroll timelines exist and motion is OK.
   Switches the poster into a tall track + sticky pin + overlapping steps. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .kw-stage {
      min-height: calc(var(--kw-stage-steps) * 100vh);
      padding-block: 0;
      view-timeline-name: --kw-stage-tl;
      view-timeline-axis: block;
    }
    .kw-stage__pin {
      position: sticky;
      top: 0;
      height: 100vh;
      display: grid;
      place-items: center;
      gap: 0;
      overflow: clip;
      isolation: isolate;
    }
    .kw-stage__step {
      grid-area: 1 / 1;
      opacity: 0;
      animation: kw-step-fade linear both;
      animation-timeline: --kw-stage-tl;
      will-change: opacity, transform;
    }
    /* Default = 3 ADJACENT (non-overlapping) steps — only one word is ever
       visible; each fully fades out before the next fades in, so centred words
       never collide into a blob. Uses :nth-of-type (not :nth-child) so a sibling
       .kw-aurora <div> inside the pin doesn't offset the count — steps are
       <span>, the aurora is a <div>. Pages with a different step count override
       these ranges inline or with a modifier. */
    .kw-stage__step:nth-of-type(1) { animation-range: cover 0%   cover 33%; }
    .kw-stage__step:nth-of-type(2) { animation-range: cover 33%  cover 67%; }
    .kw-stage__step:nth-of-type(3) { animation-range: cover 67%  cover 100%; }
  }
}
@keyframes kw-step-fade {
  0%   { opacity: 0; transform: translateY(16px) scale(0.99); }
  12%  { opacity: 1; transform: none; }
  88%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-16px) scale(1.01); }
}

/* ── Kinetic display type (typographic "media", no photos) ─────────────────
   An oversized set-apart word used as the visual. Decorative — mark aria-hidden
   and provide the accessible name nearby (.kw-stage__copy / .sr-only). */
.kw-bigtype {
  font-family: var(--kw-font-serif);
  font-weight: 400;
  font-size: clamp(3rem, 20vw, 15rem);
  line-height: 0.9;
  letter-spacing: -0.02em;
  color: var(--kw-label, #F2F2F2);
  text-align: center;
}
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .kw-bigtype--kinetic {
      animation: kw-bigtype-scale linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
      will-change: transform, letter-spacing, opacity;
    }
  }
}
@keyframes kw-bigtype-scale {
  from { transform: scale(0.82); letter-spacing: 0.04em; opacity: 0.65; }
  35%  { transform: scale(1);    letter-spacing: -0.02em; opacity: 1; }
  70%  { transform: scale(1);    letter-spacing: -0.02em; opacity: 1; }
  to   { transform: scale(1.08); letter-spacing: -0.03em; opacity: 0.55; }
}

/* ── Verse wipe — line-by-line reveal ──────────────────────────────────────
   Each .kw-wipe__line clips in left-to-right in sequence. Resting = fully
   shown (clip base is open), so non-supporting browsers see the whole verse. */
.kw-wipe { display: block; }
.kw-wipe__line { display: block; }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .kw-wipe__line {
      animation: kw-wipe-in linear both;
      animation-timeline: view();
      will-change: clip-path, opacity;
    }
    .kw-wipe__line:nth-child(1) { animation-range: entry 5%  entry 35%; }
    .kw-wipe__line:nth-child(2) { animation-range: entry 15% entry 45%; }
    .kw-wipe__line:nth-child(3) { animation-range: entry 25% entry 55%; }
    .kw-wipe__line:nth-child(4) { animation-range: entry 35% entry 65%; }
    .kw-wipe__line:nth-child(5) { animation-range: entry 45% entry 75%; }
  }
}
@keyframes kw-wipe-in {
  from { clip-path: inset(0 100% 0 0); opacity: 0.2; }
  to   { clip-path: inset(0 0 0 0);    opacity: 1; }
}

/* ── Card grid reveal ──────────────────────────────────────────────────────
   A row/grid of cards that rises and fades in, lightly staggered, as it enters
   — a calm Apple-style settle (NO rotation/tilt). Resting = the final flat
   layout, so non-supporting browsers see the normal grid. */
.kw-fan { /* layout (grid/flex row) is provided by the page */ }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .kw-fan__card {
      animation: kw-fan-settle linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 60%;
      will-change: transform, opacity;
    }
    /* gentle stagger so the cards don't all rise in perfect lockstep */
    .kw-fan__card:nth-child(2) { animation-range: entry 6%  entry 66%; }
    .kw-fan__card:nth-child(3) { animation-range: entry 12% entry 72%; }
    .kw-fan__card:nth-child(4) { animation-range: entry 18% entry 78%; }
    .kw-fan__card:nth-child(n+5) { animation-range: entry 24% entry 84%; }
  }
}
@keyframes kw-fan-settle {
  from { opacity: 0; transform: translateY(28px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
