/* The starter's motion layer. Every rule that moves something reads --p, the damped progress the
   conductor writes on each scene (0 to 1). Without script the page is html.no-js: no scene is
   pinned, nothing is hidden, and every rule below that moves anything is scoped to html.js, so
   the copy reads as a plain document. Under prefers-reduced-motion the conductor lands --p at
   once, the transforms and blur below are switched off and the choreographed type is opaque. */

/* The colour tokens sit on html, not in a second root block, so the page's own is the only one the
   handover scan reads (the scan counts the selector even inside a comment, so this one never names
   it). No font-family here: the faces are the page's, chosen by /design-craft
   (its "Fonts": never the default face, always self-hosted). */
html { --stage: #14110f; --paper: #efe6da; --signal: #c8643c; background: var(--stage); color: var(--paper); }
body { margin: 0; font-size: 1.0625rem; line-height: 1.55; }
/* A display value set below would beat the hidden attribute; this keeps [hidden] hidden (R2474). */
[hidden] { display: none !important; }
h1 { font-size: clamp(3rem, 9vw, 8.5rem); line-height: 0.92; letter-spacing: -0.02em; margin: 0 0 0.3em; }
h2 { font-size: clamp(2rem, 5vw, 4.5rem); line-height: 1; margin: 0; }
.chapter { min-height: 100vh; min-height: 100svh; display: grid; }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* One persistent canvas behind everything. */
.stage { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
main { position: relative; z-index: 1; }

/* A pinned scene: the section is --span viewports tall and its first child sticks. 100svh (the
   viewport with the phone's URL bar showing) with 100vh for browsers without it; the conductor
   reads the pin's own height, so pin and progress agree whichever applies. */
.js [data-span] { height: calc(var(--span) * 100vh); height: calc(var(--span) * 100svh); }
/* --span from the attribute before any script runs, so the first paint already has the scene's
   height (the conductor writes it inline too, for a span outside this list). */
[data-span="2"] { --span: 2; } [data-span="3"] { --span: 3; } [data-span="4"] { --span: 4; }
[data-span="5"] { --span: 5; } [data-span="6"] { --span: 6; } [data-span="7"] { --span: 7; } [data-span="8"] { --span: 8; }
.js [data-span] > .pin { position: sticky; top: 0; height: 100vh; height: 100svh; overflow: hidden; }
.pin { position: relative; display: grid; place-items: center; padding: 6vh 6vw; box-sizing: border-box; }
.no-js .layer[aria-hidden] { display: none; } /* decoration only; a layer carrying copy still shows */

/* Each choreographed element plays over a window of its scene's --p, from --from to --to; the
   defaults are below and an element's own style can move them. An entrance plays in the window; a
   .exit starts resolved and leaves in it. The first scene's type is always .exit, so the first
   viewport shows the headline before any scroll. */
.resolve { --from: 0; --to: 0.25; }
.resolve.exit { --from: 0.6; --to: 0.9; }
.reveal { --from: 0; --to: 0.5; }
.reveal.exit { --from: 0.55; --to: 0.95; }

/* A reader's window: the span of --p over which its effect moves, declared beside the effect from
   the same values. conductor.js reads it once (and on a resize) and leaves the reader alone while
   --p is outside it. Registered with inherits: false, so a window never reaches the elements under
   its reader. Every selector here whose rule reads var(--p) or var(--in) is named in conductor.js
   READERS, or the conductor never writes it. */
@property --lo { syntax: "<number>"; inherits: false; initial-value: -1; }
@property --hi { syntax: "<number>"; inherits: false; initial-value: -1; }
/* The numbers themselves, and the chapter's two derived from them, are not inherited either: the
   conductor writes each reader directly, so a write on the chapter's root re-styles the chapter and
   not every element in it. */
@property --p { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --in { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --arrive { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --leave { syntax: "<number>"; inherits: false; initial-value: 0; }
/* So the page's own tokens never take these six names: one named so reads 0 wherever it is used
   (District Dental's :root --in inset, 2026-10-22). tools/scroll-craft/token-clash.cjs finds one;
   the page's token is renamed, never these. */

/* Type choreography: a line sharpens out of blur as its scene starts (oryzo, moto), or blurs away
   as it ends. */
.js .resolve {
  --lo: var(--from); --hi: var(--to);
  --r: clamp(0, (var(--p) - var(--from)) / (var(--to) - var(--from)), 1);
  filter: blur(calc((1 - var(--r)) * 14px)); opacity: calc(0.25 + var(--r) * 0.75);
}
.js .resolve.exit { filter: blur(calc(var(--r) * 14px)); opacity: calc(1 - var(--r)); }

/* Word reveal: each word rises into its mask (or, on .exit, out of it), staggered across the
   window. The mask is padded past the line box and pulled back by the same negative margin, so
   ascenders, accents, i-dots and descenders sit inside it at rest while the line spacing stays the
   heading's own. A hidden word is moved 1.5em, past the padded mask, so no part of it shows. */
.word { display: inline-block; overflow: hidden; vertical-align: top; padding: 0.15em 0.06em 0.25em; margin: -0.15em -0.06em -0.25em; }
.word > span { display: inline-block; }
.js .reveal .word > span {
  --len: calc(var(--to) - var(--from));
  --start: calc(var(--from) + var(--i) / var(--n) * var(--len) * 0.6);
  --r: clamp(0, (var(--p) - var(--start)) / (var(--len) * 0.4), 1);
  --lo: var(--start); --hi: calc(var(--start) + var(--len) * 0.4);
  transform: translateY(calc((1 - var(--r)) * 1.5em));
}
.js .reveal.exit .word > span { transform: translateY(calc(var(--r) * -1.5em)); }

/* Layered depth (lane b): each layer moves by its own --depth as the scene passes. On a pinned
   scene the drift starts on the approach (--in, -1 to 0 while the scene rises into view), so the
   scene is already moving when it arrives rather than waiting, still, for the pin. */
.js .layer { transform: translate3d(0, calc((0.5 - var(--p) - var(--in, 0)) * var(--depth) * 1vh), 0); }
.layer { position: relative; } /* copy paints above the decoration; a decorative layer sets its own absolute position */

/* A chapter that wears its own colours: the stage colour hands over to the chapter's --own as the
   chapter arrives and hands back as it leaves. The stage stays neutral; the exhibit is itself. */
.js .chapter {
  /* Not --in: the conductor writes --in inline on a pinned scene, which would beat a rule here. */
  --arrive: clamp(0, var(--p) * 4, 1);
  --leave: clamp(0, (1 - var(--p)) * 4, 1);
  background: color-mix(in oklab, var(--own) calc(min(var(--arrive), var(--leave)) * 100%), var(--stage));
}
.no-js .chapter { background: var(--own); }

/* Under reduced motion nothing travels on --p, so no scene is pinned, at every width: a pin that
   kept its one viewport and its overflow: hidden clipped a work section to one screen at desktop
   width while a phone-only rule had lifted it (design-studio flagship, 2026-10-10, 1 of 6 pieces
   whole). The pin keeps a viewport as its least height and grows to its content. */
@media (prefers-reduced-motion: reduce) {
  .js [data-span] { height: auto; }
  .js [data-span] > .pin { position: relative; height: auto; min-height: 100vh; min-height: 100svh; overflow: visible; }
  .js .resolve, .js .resolve.exit { filter: none; opacity: 1; }
  .js .reveal .word > span, .js .reveal.exit .word > span, .js .layer { transform: none; }
}
/* The same, while the preview frame's Highlights tour travels (<html data-preview-still>,
   preview.schema.md "Still mode"): the tour scrolls to each feature with nothing sliding, blurring or
   fading on the way, and the frame reloads the page when the tour ends, so motion is back as at load. */
[data-preview-still].js [data-span] { height: auto; }
[data-preview-still].js [data-span] > .pin { position: relative; height: auto; min-height: 100vh; min-height: 100svh; overflow: visible; }
[data-preview-still].js .resolve, [data-preview-still].js .resolve.exit { filter: none; opacity: 1; }
[data-preview-still].js .reveal .word > span, [data-preview-still].js .reveal.exit .word > span, [data-preview-still].js .layer { transform: none; }
