/* story.css — the primitives story.js drives. Vendor alongside it.
   Everything here is authored so that with motion off, the page still reads. */

:root{
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-io:cubic-bezier(.65,.05,.36,1);
  --ease-soft:cubic-bezier(.25,.8,.25,1);
  --rv-dur:.9s;
  --vel:0; --velAbs:0;          /* published every frame by the rig */
}

/* ---------------------------------------------------------------- pinning */
/* A pinned section is (100 + pin)% of the viewport tall; its inner sticks for
   the whole run. `svh` — not `vh` — so a mobile URL bar cannot resize the
   scene mid-scrub and jump the timeline. */
.pin{ position:relative; height:calc((100 + var(--pin,100)) * 1svh); }
.pin > .pin-inner{
  position:sticky; top:0; height:100svh; overflow:clip;
  /* minmax(0,1fr), never `auto`: an auto grid column sizes to its widest child,
     so one wide flex rail inside a pin silently stretches the whole scene —
     headlines stop wrapping and everything lays out off-screen. */
  display:grid; grid-template-columns:minmax(0,1fr);
  align-content:center; justify-items:center;
}
@supports not (height:100svh){
  .pin{ height:calc((100 + var(--pin,100)) * 1vh); }
  .pin > .pin-inner{ height:100vh; }
}

/* ---------------------------------------------------------------- reveals */
.rv{ opacity:0; transition:opacity var(--rv-dur) var(--ease-out),
                          transform calc(var(--rv-dur) + .15s) var(--ease-out),
                          clip-path calc(var(--rv-dur) + .25s) var(--ease-out),
                          filter var(--rv-dur) var(--ease-out); }
.rv.in{ opacity:1; transform:none; clip-path:inset(0 0 0 0); filter:none; }

[data-reveal="up"]      { transform:translateY(38px) }
[data-reveal="down"]    { transform:translateY(-24px) }
[data-reveal="left"]    { transform:translateX(40px) }
[data-reveal="right"]   { transform:translateX(-40px) }
[data-reveal="scale"]   { transform:scale(1.06) }
[data-reveal="blur"]    { filter:blur(14px); transform:translateY(20px) }
[data-reveal="clip"]    { clip-path:inset(0 0 100% 0); opacity:1 }
[data-reveal="clip-x"]  { clip-path:inset(0 100% 0 0); opacity:1 }
[data-reveal="iris"]    { clip-path:circle(0% at 50% 50%); opacity:1 }
[data-reveal="iris"].in { clip-path:circle(75% at 50% 50%) }

.d1{transition-delay:.07s}.d2{transition-delay:.14s}.d3{transition-delay:.21s}
.d4{transition-delay:.28s}.d5{transition-delay:.35s}.d6{transition-delay:.42s}

/* ------------------------------------------------------------ split text */
/* The mask is the whole trick: a line that slides up from behind a hard edge
   reads as typeset, where a fading line reads as a web page. */
.split{ --sp-stag:.055s; --sp-dur:1.05s }
.sp-mask{ display:inline-block; overflow:hidden; vertical-align:top;
          padding-block:.06em; margin-block:-.06em }
.sp-line{ display:block }
.sp-u{ display:inline-block; transform:translateY(105%);
       transition:transform var(--sp-dur) var(--ease-out);
       transition-delay:calc(var(--i) * var(--sp-stag)) }
.split.in .sp-u, .in .split .sp-u{ transform:none }

.split-chars{ --sp-stag:.022s }
.split[data-split-from="down"] .sp-u{ transform:translateY(-105%) }

/* ---------------------------------------------------------------- strokes */
.draw{ stroke-dasharray:var(--len,1000); stroke-dashoffset:var(--len,1000);
       transition:stroke-dashoffset 1.9s var(--ease-io) }
.in .draw, .draw.in{ stroke-dashoffset:0 }
/* Progress arcs stop short: set --off in JS, never stroke-dashoffset inline. */
.in .draw[style*="--off"]{ stroke-dashoffset:var(--off) }

/* ------------------------------------------------------- parallax & depth */
[data-parallax]{ will-change:transform; transform:translate3d(0,var(--py,0),0) }
[data-depth]{ transform:translate3d(var(--mx,0),var(--my,0),0);
              transition:transform .5s cubic-bezier(.2,.8,.2,1) }
[data-stage]{ perspective:1200px }

/* Velocity-reactive shear: the page leans into a fast scroll and settles.
   Keep the multiplier small — past ~4deg it stops reading as weight and
   starts reading as a bug. */
.shear{ transform:skewY(calc(var(--vel) * -2.2deg))
                  scaleY(calc(1 + var(--velAbs) * .035)) }

/* ---------------------------------------------------------------- the nav */
[data-nav]{ position:fixed; inset:0 0 auto 0; z-index:60;
            transition:height .45s var(--ease-io), background .45s ease,
                       color .45s ease, backdrop-filter .45s ease }
[data-nav].on-dark{ color:#fff }

/* --------------------------------------------------------- chapter grading */
/* The director sets one attribute; the whole page changes temperature. Give
   every colour a token and transition the tokens, not the rules. */
html{ transition:background-color 1.1s var(--ease-soft), color 1.1s var(--ease-soft) }

/* ---------------------------------------------------------------- degrade */
[data-tier="low"] *{ backdrop-filter:none !important }
[data-tier="low"] .grain, [data-tier="low"] [data-heavy]{ display:none !important }

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.001ms !important; animation-iteration-count:1 !important;
                        transition-duration:.001ms !important; scroll-behavior:auto !important }
  .rv,.sp-u{ opacity:1 !important; transform:none !important; clip-path:none !important; filter:none !important }
  .draw{ stroke-dashoffset:0 !important }
  .pin{ height:auto !important }
  .pin > .pin-inner{ position:static !important; height:auto !important; overflow:clip }
  .shear{ transform:none !important }
}
