/* ============================================================
   10 — SCENES
   The timeline layer.

   09 animates on arrival. This one animates on SCRUB: every shot
   owns a progress value --p that runs 0 to 1 as it crosses the
   viewport, and everything inside reads from it. That is the
   difference between a page that animates and a page that is
   edited — you can scroll backwards and the motion runs backwards,
   because there is no "played" state to get stuck in.

   --p is set once per frame per visible shot, by scenes.js, in a
   single rAF loop. CSS does the rest with calc(). Nothing here
   listens to scroll on its own.

   Rules, unchanged from every other layer:
     · transform, opacity, filter, clip-path, stroke-dashoffset
     · delete the file and the homepage is a normal stacked page
     · the whole layer flattens under prefers-reduced-motion
   ============================================================ */

/*  Defaults to 1 — the FINISHED state. If scenes.js never runs, every scrubbed
    element is simply complete rather than invisible. The engine writes the real
    value on its first frame, within about 16ms. */
@property --p{syntax:"<number>";initial-value:1;inherits:true}

/* ---------- SHOT : the unit of the edit ----------------------- */
/*  --p is now driven by TIME, not scroll position. scenes.js runs each shot
    through 0 -> 1, holds, rewinds and repeats, and pauses while the shot is
    off screen. Default 1 so a shot is complete before the engine touches it. */
.shot{--p:1;position:relative}

/*  The shots used to pin: a 300vh track with a sticky stage inside, scrubbed
    by the scrollbar. That made the scrollbar the transport control, and it
    meant a visitor had to scroll THROUGH a section to see what it did.

    They are ordinary sections now. Nothing is pinned, nothing is held
    hostage by the scroll position, and every graphic plays itself the moment
    the section is on screen. The page is also about 900vh shorter. */
.pin-track{position:relative}
.pin-track.len-2,.pin-track.len-3,.pin-track.len-4{height:auto}
.pin-stage{
  position:relative;height:auto;display:grid;align-content:center;
  padding:76px 0;overflow:hidden;
}

/* ---------- SCRUBBED PRIMITIVES ------------------------------- */
/*  Each one reads --p and nothing else. They compose freely. */

.scrub-x    {transform:translate3d(calc(var(--p) * var(--dx,-60%)),0,0)}
.scrub-y    {transform:translate3d(0,calc(var(--p) * var(--dy,-20vh)),0)}
.scrub-scale{transform:scale(calc(1 + var(--p) * var(--ds,.25)))}
.scrub-rot  {transform:rotate(calc(var(--p) * var(--dr,12deg)))}
.scrub-fade {opacity:calc(1 - var(--p))}
.scrub-in   {opacity:var(--p)}
.scrub-blur {filter:blur(calc(var(--p) * var(--db,8px)))}

/*  a bar that fills with the scrub — the Split, scrubbed */
.scrub-fill{transform:scaleX(var(--p));transform-origin:0 50%}

/*  a mask that wipes right as the scrub advances */
.scrub-wipe{clip-path:inset(0 calc((1 - var(--p)) * 100%) 0 0)}

/*  text that types itself: the same wipe, but per line so the
    caret lands where a caret should */
.scrub-type{
  clip-path:inset(0 calc((1 - var(--p)) * 100%) 0 0);
  position:relative;
}
.scrub-type::after{
  content:"";position:absolute;top:.1em;bottom:.1em;
  left:calc(var(--p) * 100%);width:2px;background:var(--cyan);
  opacity:calc(1 - var(--p));
}

/*  an SVG path that draws itself. scenes.js measures the length
    and writes --len, so the markup never has to know it. */
.scrub-draw{
  stroke-dasharray:var(--len,1000);
  stroke-dashoffset:calc(var(--len,1000) * (1 - var(--p)));
}

/*  cards that fan out of a stack. --i is the card index. */
.scrub-fan{
  transform:
    translate3d(calc(var(--p) * var(--i) * 8%), calc(var(--p) * var(--i) * -6px), 0)
    rotate(calc(var(--p) * var(--i) * 2.2deg));
}

/*  a counter that scrubs. scenes.js writes the text. */
.scrub-count{font-variant-numeric:tabular-nums}

/* ---------- 3D PARALLAX ON SCRUB ------------------------------ */
.paradepth{perspective:1000px;perspective-origin:50% 50%}
/*  scrubbed on the compositor already; a standing will-change on every
    layered child just holds textures open */
.paradepth [data-depth]{
  transform:translate3d(0, calc(var(--p) * var(--depth,1) * -9vh), 0);
}

/* ---------- BLINDS : venetian wipe ---------------------------- */
.blinds{position:relative;overflow:hidden}
.blinds i{
  position:absolute;left:0;right:0;height:12.5%;background:var(--void);
  transform-origin:50% 0;
}
.blinds i:nth-child(1){top:0%;   animation:blind .8s var(--ease-out-expo) .00s both}
.blinds i:nth-child(2){top:12.5%;animation:blind .8s var(--ease-out-expo) .06s both}
.blinds i:nth-child(3){top:25%;  animation:blind .8s var(--ease-out-expo) .12s both}
.blinds i:nth-child(4){top:37.5%;animation:blind .8s var(--ease-out-expo) .18s both}
.blinds i:nth-child(5){top:50%;  animation:blind .8s var(--ease-out-expo) .24s both}
.blinds i:nth-child(6){top:62.5%;animation:blind .8s var(--ease-out-expo) .30s both}
.blinds i:nth-child(7){top:75%;  animation:blind .8s var(--ease-out-expo) .36s both}
.blinds i:nth-child(8){top:87.5%;animation:blind .8s var(--ease-out-expo) .42s both}
@keyframes blind{from{transform:scaleY(1)}to{transform:scaleY(0)}}

/* ---------- SLIT : horizontal slit-scan reveal ---------------- */
.slit{
  clip-path:polygon(0 48%,100% 48%,100% 52%,0 52%);
  animation:slit-open 1s var(--ease-out-expo) both;
}
@keyframes slit-open{
  0%  {clip-path:polygon(0 48%,100% 48%,100% 52%,0 52%);opacity:.4}
  45% {opacity:1}
  100%{clip-path:polygon(0 0,100% 0,100% 100%,0 100%);opacity:1}
}

/* ---------- FLIP : a card turns over -------------------------- */
.flip3d{perspective:1200px}
.flip3d .face{
  transform-style:preserve-3d;
  transform:rotateY(calc(var(--p) * 180deg));
  transition:none;
}
.flip3d .front,.flip3d .back{backface-visibility:hidden;position:absolute;inset:0}
.flip3d .back{transform:rotateY(180deg)}

/* ---------- MORPH : one shape becomes another ----------------- */
.morph{
  animation:morph-shape 9s var(--ease-in-out-quint) infinite alternate;
}
@keyframes morph-shape{
  0%  {clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%)}
  50% {clip-path:polygon(50% 4%,96% 30%,88% 82%,50% 96%,12% 82%,4% 30%)}
  100%{clip-path:polygon(50% 0%,86% 14%,100% 50%,86% 86%,50% 100%,14% 86%)}
}

/* ---------- EXPOSURE : a flash frame between shots ------------ */
.exposure{
  position:absolute;inset:0;pointer-events:none;z-index:4;background:#fff;
  opacity:0;                                  /* the value that must survive */
  opacity:calc(max(0, 1 - abs(var(--p) - .5) * 14) * .18);
}

/* ---------- STREAK : speed lines on scroll velocity ----------- */
.streaks{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:1}
.streaks i{
  position:absolute;left:0;right:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(34,224,255,.5),transparent);
  opacity:0;                                  /* survives an unsupported abs() */
  opacity:calc(min(1, abs(var(--vel,0)) * .12));
  transform:scaleY(calc(1 + abs(var(--vel,0)) * .4));
}
.streaks i:nth-child(1){top:18%}  .streaks i:nth-child(2){top:34%}
.streaks i:nth-child(3){top:52%}  .streaks i:nth-child(4){top:68%}
.streaks i:nth-child(5){top:83%}

/* ---------- SHOT LABEL : the edit shows its own workings ------ */
.shot-mark{
  position:absolute;top:22px;left:22px;z-index:3;display:flex;align-items:center;gap:10px;
  font:500 10.5px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:var(--faint);
}
.shot-mark b{
  color:var(--cyan);font-weight:500;
  font-variant-numeric:tabular-nums;
}
.shot-mark .bar{
  width:52px;height:2px;background:rgba(255,255,255,.12);border-radius:2px;overflow:hidden;
}
.shot-mark .bar i{
  display:block;height:100%;background:var(--spectrum);
  transform:scaleX(var(--p));transform-origin:0 50%;
}

/* ---------- SCRUB STAGE FURNITURE ----------------------------- */
/*  Both stages must be positioned. .scene::before is a z-index 1 overlay and
    unpositioned content would paint underneath it. */
.stage-grid{
  position:relative;z-index:2;
  display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center;
  max-width:var(--maxw);margin:0 auto;padding:0 24px;width:100%;
}
.stage-solo{
  position:relative;z-index:2;
  max-width:920px;margin:0 auto;padding:0 24px;width:100%;text-align:center;
}
.pin-stage > div{position:relative;z-index:2}
.stage-h{
  font:700 clamp(26px,4.4vw,54px)/1.06 var(--display);letter-spacing:-.035em;margin:0 0 18px;
}
.stage-p{font-size:16px;color:var(--dim);max-width:52ch;margin:0}
.stage-solo .stage-p{margin:0 auto}

/*  This was a horizontal filmstrip that slid past as you scrolled. That only
    works while the scrollbar is the transport: on a timer it slides cards out
    of view before they can be read. It is a grid now — everything on screen,
    all the time. */
.htrack{
  display:grid;grid-template-columns:repeat(4,1fr);gap:20px;
  max-width:var(--maxw);margin:0 auto;padding:0 24px;width:100%;
}
@media(max-width:1100px){.htrack{grid-template-columns:repeat(2,1fr)}}
@media(max-width:620px){.htrack{grid-template-columns:1fr}}

/* ---------- LEGEND SPACING ----------------------------------- */
.split-legend b{margin-left:7px;color:var(--text)}
@media(max-width:560px){
  .split-legend{display:grid;grid-template-columns:1fr 1fr;gap:10px 14px}
}

/* ---------- LATENCY CURVE ------------------------------------- */
.curve{width:100%;height:auto;overflow:visible}
.curve .grid-line{stroke:var(--line);stroke-width:1}
.curve .band{fill:url(#curveFill)}
.curve .line{fill:none;stroke:url(#curveStroke);stroke-width:2.5;stroke-linecap:round}
.curve .dot{fill:var(--cyan)}
.curve text{fill:var(--faint);font:400 11px var(--mono)}

/* ============================================================
   Responsive: a pinned scrub is wrong on a phone
   ============================================================ */
@media(max-width:900px){
  .pin-stage{padding:48px 0;overflow:visible}
  /*  the horizontal track and the depth parallax both need room a phone does
      not have; everything else keeps animating */
  .scrub-x,.scrub-fan,
  .paradepth [data-depth]{transform:none !important}
  .scrub-blur{filter:none !important}
  .scrub-fade{opacity:1 !important}
  .exposure,.streaks,.shot-mark{display:none}
  .stage-grid{grid-template-columns:1fr;gap:28px}
  .flip3d .face{transform:none}
  .flip3d .back{display:none}
}
@media(max-width:560px){
  .stage-grid,.stage-solo{padding:0 16px}
}

@media(prefers-reduced-motion:reduce){
  .shot{--p:1 !important}
  .pin-stage{padding:48px 0}
  .blinds i,.slit,.morph{animation:none !important}
  .blinds i{display:none}
  .slit{clip-path:none !important;opacity:1 !important}
  .scrub-x,.scrub-y,.scrub-scale,.scrub-rot,.scrub-fan,.paradepth [data-depth]{transform:none !important}
  .scrub-blur{filter:none !important}
  .scrub-fade{opacity:1 !important}
  .scrub-type::after{display:none}
  .exposure,.streaks{display:none}
}

html[data-fx-off] .shot{--p:1 !important}
html[data-fx-off] .exposure,
html[data-fx-off] .streaks,
html[data-fx-off] .blinds i{display:none}
html[data-fx-off] .slit{clip-path:none}
