@font-face{
  font-family:"Quinfinity";
  src:url("Quinfinity-VF.woff2") format("woff2-variations"),
      url("Quinfinity-VF.woff2") format("woff2");
  font-weight:100 900;      /* the axis survived the proportion pass */
  font-style:normal;
  font-display:swap;
}
/* Metric matched fallback, so the Webfont:off control swaps without reflowing the page.
   Overrides derived from the built font: cap 697/1000, x 506/1000. */
@font-face{
  font-family:"Quinfinity Fallback";
  src:local("Helvetica Neue"),local("Arial"),local("Liberation Sans");
  ascent-override:100%;
  descent-override:30.3%;
  size-adjust:99%;
}

/* ------------------------------------------------------------------ tokens */
:root{
  color-scheme:light;
  --ink:#111111;
  --ink-2:#3a3a3a;   /* must clear 7:1 at label size on --g4, not just on white */
  --line:#c4c4c4;
  --g0:#ffffff;
  --g1:#f6f6f6;
  --g2:#ededed;
  --g3:#e3e3e3;
  --g4:#d8d8d8;
  /* The owner's accent, used sparingly. 6.30:1 on white, so it is text legal here. */
  --accent:#9c27b0;
  --ground:var(--g0);
  /* Load state stays monochrome except when something is actually wrong, which is the
     only time the accent earns its place. */
  --hold:#111111;
  --near:#6b6b6b;
  --over:#9c27b0;
  --state:var(--hold);
  --focus:var(--accent);

  --s1:.5rem; --s2:1rem; --s3:1.5rem; --s4:2.5rem; --s5:4rem; --s6:6rem; --s7:9rem;
  --gut:clamp(1.25rem, 4.5vw, 4.5rem);
  --measure:62ch;
  --ease:cubic-bezier(.22,.61,.36,1);

  --t-hero:clamp(1.875rem, 4.6vw, 4rem);   /* 30px to 64px, the evidence ceiling */
  --t-lede:clamp(1.0625rem, 1.5vw, 1.25rem);
  --t-body:1.0625rem;
  --t-num:clamp(2rem, 3.4vw, 3rem);   /* 32 to 48px, deliberately under the 64px hero */
  /* 13px, not 11. Every label, value and readout caption in the controls
     measured 11px, which is below anything a reader should have to work at,
     and the uppercase letterspacing makes it read smaller still. */
  --t-tech:.8125rem;

  /* Driven by the load. Narrow braces heavier, wide relaxes lighter, which is what a
     typographer does by hand when the measure changes. Set by the scene each frame. */
  --wght:400;
  /* Paper tooth. A real luminance delta, verified on composited pixels rather than
     trusted from an opacity value: a previous grain measured 0.0013 because the blend
     mode throttled it long before the opacity did. No blend mode here on purpose. */
  --grain:radial-gradient(circle at 30% 30%, rgba(0,0,0,.035) 0 1px, transparent 1.4px),
          radial-gradient(circle at 70% 65%, rgba(0,0,0,.028) 0 1px, transparent 1.4px);
}
:root[data-theme="dark"]{
  color-scheme:dark;
  --grain:radial-gradient(circle at 30% 30%, rgba(255,255,255,.05) 0 1px, transparent 1.4px),
          radial-gradient(circle at 70% 65%, rgba(255,255,255,.04) 0 1px, transparent 1.4px);
  --ink:#f2f2f2;
  --ink-2:#bcbcbc;   /* 6.83:1 at label size was one notch short of the 7:1 small text floor */
  --line:#454545;
  --g0:#0f0f0f;
  --g1:#151515;
  --g2:#1c1c1c;
  --g3:#232323;
  --g4:#2b2b2b;
  /* #9c27b0 measures only 3.02:1 on this ground, which is text illegal. Same hue,
     lifted until it clears: 5.52:1. An accent is designed twice or it is broken once. */
  --accent:#c264d6;
  --hold:#f2f2f2;
  --near:#8f8f8f;
  --over:#c264d6;
  --focus:var(--accent);
}

*,*::before,*::after{box-sizing:border-box}
html{
  -webkit-text-size-adjust:100%;
  /* Must sit on the ROOT. On <body> alone it does not reach the viewport scroll
     container, and the deliberate bleed (the rig, the last specimen) becomes a real
     121px horizontal scrollbar. `clip` not `hidden`, so sticky and anchors still work. */
  overflow-x:clip;
}
body{
  margin:0;
  /* The ground shifts continuously across the whole scroll, so there is never a frame
     where nothing is changing. Robin recolours the room per project; this is the
     monochrome version of that, and it stays inside the tones already measured safe. */
  background:color-mix(in oklab, var(--g0), var(--g3) var(--tone, 0%));
  /* The bleed off the right edge must CLIP, not scroll. Without this the rig and the
     last specimen push the document 121px wide and the responsive gate fails on its
     own most basic assertion. `clip` rather than `hidden` so position:sticky and the
     skip link still work. */
  overflow-x:clip;
  color:var(--ink);
  font-family:"Quinfinity","Quinfinity Fallback",system-ui,sans-serif;
  font-size:var(--t-body);
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
html[data-font="off"] body{font-family:"Quinfinity Fallback",system-ui,sans-serif}

.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:-9999px;top:0;background:var(--ink);color:var(--ground);
      padding:var(--s2);z-index:20}
.skip:focus{left:0}

a{color:inherit}
:where(a,button,input,[tabindex]):focus-visible{
  outline:3px solid var(--focus);outline-offset:3px;border-radius:2px;
}

/* ------------------------------------------------------------------- nav */
.nav{display:flex;justify-content:space-between;align-items:center;
     padding-block:var(--s3);gap:var(--s3)}
/* The owner's own mark, from the 2025 identity. The tonal split (solid + grey) is part
   of the drawing, so it is NOT recoloured, only inverted for the dark theme, which
   preserves the internal relationship instead of flattening it. */
.mark{display:block;height:1.75rem}
.mark img{display:block;height:100%;width:auto}
:root[data-theme="dark"] .mark img{filter:invert(1)}
.nav-links{display:flex;align-items:center;gap:var(--s3)}
.nav-links a{font-size:var(--t-tech);letter-spacing:.1em;text-transform:uppercase;
             text-decoration:none;padding:.4rem 0}
.nav-links a:hover{text-decoration:underline;text-underline-offset:.35em}
.theme{font:inherit;font-size:var(--t-tech);letter-spacing:.1em;text-transform:uppercase;
       background:none;color:inherit;border:1px solid var(--ink);padding:.35rem .6rem;
       cursor:pointer;transition:background-color .2s var(--ease),color .2s var(--ease)}
.theme:hover{background:var(--ink);color:var(--ground)}

/* ---------------------------------------------------- the page under load */
.page{
  width:100%;
  max-width:var(--load-width, none);
  margin-inline:auto;
  container-type:inline-size;
  container-name:page;
  font-size:calc(1rem * var(--load-scale, 1));
}

/* ---------------------------------------------------------------- record */
.record{padding-block:var(--s6) var(--s5)}
/* A sentence, not a technical label. It carried class="k" in the markup, which
   set it uppercase at 11px, so the one line explaining what the table is read
   smaller than the table. Sized here and nowhere else. */
.record-lead{margin:0 0 var(--s3);color:var(--ink-2);max-width:60ch;
  font-size:var(--t-body);line-height:1.5}
.record table{width:100%;border-collapse:collapse;font-size:max(.8125rem, 11px)}
.record th{text-align:left;font-size:var(--t-tech);letter-spacing:.11em;text-transform:uppercase;
           padding:.6rem .75rem .6rem 0;border-bottom:2px solid var(--ink);font-weight:400}
.record td{padding:.7rem .75rem .7rem 0;border-bottom:1px solid var(--line);vertical-align:top;
           font-variant-numeric:tabular-nums}
.record .n{text-align:right;padding-right:1rem}
/* Pass and fail were the same dot in two colours, which is colour alone. They
   are now different SHAPES as well: a filled disc and a hollow ring. */
.pass,.fail{display:inline-block;width:.6rem;height:.6rem;vertical-align:middle}
.pass{border-radius:50%;background:var(--hold)}
.fail{border-radius:0;background:none;border:2px solid var(--over);transform:rotate(45deg)}

/* ------------------------------------------------------------------- who */
.who{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s4);
     padding:0 var(--gut) var(--s6);align-items:center}
.who-shot{margin:0;min-width:0;margin-left:calc(var(--gut) * -1 - 4vw)}
.who-shot img{display:block;width:100%;height:auto;filter:grayscale(1)}
.who .out{margin-top:var(--s3)}
.lede{font-size:var(--t-lede);max-width:44ch;margin:0;line-height:1.45}
.out{display:inline-flex;align-items:baseline;gap:.6rem;text-decoration:none;
     border-bottom:2px solid var(--ink);padding-bottom:.35rem;font-size:var(--t-lede)}
.out-a{transition:transform .25s var(--ease)}
.out:hover .out-a{transform:translateX(.35rem)}

/* ------------------------------------------------------------------ foot */
.foot{display:flex;justify-content:space-between;gap:var(--s4);flex-wrap:wrap;
      padding:var(--s4) var(--gut) var(--s5);border-top:2px solid var(--ink)}
.verify{list-style:none;margin:var(--s2) 0 0;padding:0;display:grid;gap:.4rem}
.verify a{font-size:.8125rem;text-underline-offset:.3em}
.foot-r{text-align:right;display:grid;gap:var(--s2);justify-items:end}
.foot-r>a{font-size:var(--t-tech);letter-spacing:.1em;text-transform:uppercase;
          text-decoration:none;border-bottom:2px solid var(--ink);padding-bottom:.3rem}
.lic{max-width:26ch;color:var(--ink-2);text-transform:none;letter-spacing:0;line-height:1.5}

/* ------------------------------------------------------------ responsive */
@container page (max-width:60rem){
  .rig{grid-template-columns:1fr}
  .rig-reads{margin-right:0;padding-right:var(--gut);border-left:0;border-top:1px solid var(--line)}
  .who{grid-template-columns:1fr}
}
@container page (max-width:40rem){
  .band-in,.band:nth-child(even) .band-in{flex-direction:column}
  .band:last-child .cell{margin-right:0}
  .reads{gap:var(--s3) var(--s2)}
}
@media (max-width:72rem){
  .statement{grid-template-columns:1fr}
    .band-img,.band-img.rev{grid-template-columns:1fr}
  .shot-l,.shot-r{margin-inline:calc(var(--gut) * -1)}
  .who{grid-template-columns:1fr}
  .who-shot{margin-left:calc(var(--gut) * -1)}
}
@media (max-width:60rem){
  .rig{grid-template-columns:1fr}
  .rig-reads{margin-right:0;padding-right:var(--gut);border-left:0;border-top:1px solid var(--line)}
  .who{grid-template-columns:1fr}
}
@media (max-width:40rem){
  .band-in,.band:nth-child(even) .band-in{flex-direction:column}
  .band:last-child .cell{margin-right:0}
  .nav-links{gap:var(--s2)}
}

/* Degrades, never deletes. The load becomes stepped; every readout stays live. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important}
}
/* A .01ms duration does NOT stop an infinite animation, it strobes it at frame
   rate. The control is supposed to stop motion, so it stops it. */
html[data-motion="reduced"] *,
html[data-motion="reduced"] *::before,
html[data-motion="reduced"] *::after{
  transition:none !important;
  animation:none !important;
}

/* =====================================================================
   ONE SCENE. Scroll is a TIMELINE, not a page position.
   The track supplies the distance. The stage is sticky and never scrolls.
   Every element below is a STATE of one value, which is what makes the
   whole page feel continuous instead of eight sections stopping and
   starting. Native scrolling is kept (no wheel hijack) so keyboard,
   trackpad, touch and screen readers all still behave.
   ===================================================================== */
.track{height:900vh;position:relative}
/* TWO ROWS, not one. The readouts used to be absolutely positioned over the
   bottom of the stage, which meant the reading column had the whole height to
   grow into and simply grew THROUGH them: from 1920px wide up, the specimen
   sheet sat on top of the numbers. In the flow they cannot collide, whatever
   the type does. */
.stage{
  position:sticky;top:0;height:100vh;overflow:hidden;
  display:grid;grid-template-columns:minmax(0,1fr) clamp(15rem,26vw,25rem);
  grid-template-rows:minmax(0,1fr) auto;
  column-gap:clamp(1.5rem,4vw,4rem);
  row-gap:clamp(.9rem,2.2vh,1.8rem);
  padding:clamp(4.5rem,9vh,7rem) 0 clamp(3.5rem,7vh,5rem) var(--edge);
}

/* ---- the film roll: one continuous strip, always moving ---------------- */
.roll{grid-area:1 / 2 / 3 / 3;position:relative;overflow:hidden;
      margin-right:calc(var(--gut) * -1 - 4vw)}
.roll-strip{position:absolute;inset:0 0 auto 0;display:grid;gap:1.2vh;
            transform:translate3d(0, var(--roll, 0px), 0);will-change:transform}
.roll-strip figure{margin:0;overflow:hidden;background:var(--g2)}
.roll-strip img{display:block;width:100%;height:auto;filter:grayscale(1);
                transform:scale(var(--fs, 1.06));will-change:transform}
/* the gate: a fixed aperture the strip runs behind, like a film gate */
.roll-gate{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(var(--ground) 0%, transparent 9%, transparent 91%, var(--ground) 100%);}

/* ---- the reading column: changes in place, never scrolls away ---------- */
/* --col-fit is measured by the engine: 1 when the column fits its one screen of
   height, less when it does not. It scales the DISPLAY type and the gaps only.
   Body copy is never scaled, because the one thing that must stay at the size it
   was set is the text you actually read. */
.col{grid-area:1 / 1 / 2 / 2;display:flex;flex-direction:column;justify-content:center;
     gap:calc(clamp(1rem,2.2vh,1.6rem) * var(--col-fit,1));min-width:0;min-height:0}
.chapter{display:flex;align-items:baseline;gap:.9rem;margin:0;
         font-size:var(--t-tech);letter-spacing:.14em;text-transform:uppercase;color:var(--ink)}
.chapter .idx{color:var(--ink);font-variant-numeric:tabular-nums}
.spec{margin:0;letter-spacing:-.022em;line-height:1.06;
      font-weight:var(--wght);font-variation-settings:"wght" var(--wght)}
/* The measure is divided by the fit factor on purpose. `ch` is a unit of the
   font's own size, so a measure of 17ch shrinks exactly as fast as the type
   does and the headline keeps the SAME number of lines however small it gets,
   which makes the fit pass useless. Divided through, the measure stays the same
   physical width, so shrinking the type drops lines, which is the whole point. */
.spec-hero{font-size:calc(clamp(1.75rem,4.1vw,3.6rem) * var(--col-fit,1));
           max-width:min(calc(17ch / var(--col-fit,1)), 100%)}
.spec .w{overflow-wrap:anywhere}
/* The reserve that stops the column jumping as chapter copy changes length has
   to live on the TEXT, not on its wrapper. On the wrapper it was 6.2em of the
   root size, so it never grew with the type: in a height-constrained flex
   column the wrapper was crushed back to that fixed 99px while the copy inside
   it needed 151px, and the overflow landed on top of the specimen sheet. In em
   of the copy's own size it scales with every breakpoint by itself. */
.says{position:relative}
.says .lede{margin:0;font-size:var(--t-lede);max-width:46ch;line-height:1.45;
            min-height:var(--say-min, 7.4em);
            /* floored at .72. It used to bottom out at .25, which is 1.74:1 on
               this ground: unreadable for the length of every transition. */
            opacity:max(var(--say-o, 1), .72);transform:translate3d(0, var(--say-y, 0px), 0)}
/* nothing in the column may be shrunk by the column: a crushed child overflows
   onto its neighbour instead of getting smaller */
.col > *{flex:0 0 auto}

/* ---- the specimen sheet, folded into the same timeline ----------------- */
.sheet{position:relative;border:1px solid var(--line);background:var(--g0);
       padding:clamp(.8rem,1.6vh,1.2rem) clamp(1rem,2vw,1.6rem);
       align-self:flex-start;max-width:100%;
       box-shadow:0 calc(1px + var(--lift,0) * 10px) calc(4px + var(--lift,0) * 24px) rgba(0,0,0,calc(.05 + var(--lift,0) * .1))}
.sheet .spec{font-size:var(--sheet-t, 1.1rem)}
.sheet-trim{display:block;margin-top:.55rem;font-size:max(.625rem, 10px);letter-spacing:.16em;
            text-transform:uppercase;color:var(--ink)}

/* ---- the instrument, always on ----------------------------------------- */
.hud{grid-area:2 / 1 / 3 / 2;align-self:end;
     display:flex;align-items:flex-end;gap:clamp(1rem,3vw,2.4rem);flex-wrap:wrap}
.hud-reads{display:flex;gap:clamp(1rem,2.6vw,2rem)}
/* The home page readouts. Named .readout, NOT .read: the four reading pages
   use <main class="read">, so an unscoped .read rule set applies to every span
   inside them. It did: the on-this-page index and the call to action were
   forced to 11px uppercase blocks, and the whole page lifted 2px on hover. */
.readout b{display:block;font-size:clamp(1.4rem,2.6vw,2.1rem);line-height:.95;letter-spacing:-.03em;
        font-weight:400;color:var(--ink);font-variant-numeric:tabular-nums}
.readout span{display:block;font-size:var(--t-tech);letter-spacing:.11em;text-transform:uppercase;
           color:var(--ink);margin-top:.4rem}
.state{display:flex;align-items:center;gap:.55rem;margin:0;
       font-size:var(--t-tech);letter-spacing:.11em;text-transform:uppercase;color:var(--ink)}
.dot{width:.7rem;height:.7rem;border-radius:50%;background:var(--state);flex:0 0 auto;
     transition:background-color .3s var(--ease)}

.ticks{position:absolute;left:var(--gut);right:calc(var(--gut) + 2vw);bottom:1.3rem;
       list-style:none;margin:0;padding:0;display:flex;justify-content:space-between;
       font-size:max(.625rem, 10px);letter-spacing:.12em;color:var(--ink)}
/* Was 11px at opacity .55, which measures 4.17:1: under the 7:1 floor this site
   holds small text to, and set with opacity rather than a chosen colour. Both
   states are now real tokens that clear the floor on every ground. */
.ticks li{color:var(--ink-2);transition:color .2s var(--ease)}
.ticks li.on{color:var(--ink)}
.progress{position:absolute;left:0;right:0;top:0;height:2px;background:var(--line)}
.progress i{display:block;height:100%;width:var(--p, 0%);background:var(--accent)}

/* ---- the controls, after the scene -------------------------------------
   The section used to open with a screen-reader-only <h2>, so a sighted
   reader arrived at six unlabelled inputs with no statement of what they
   were for. The heading is visible now and the lead is a sentence rather
   than an 11px uppercase technical label. */
.controls{padding-block:clamp(3rem,7vh,5rem) 0;border-top:2px solid var(--ink)}
.controls-in{display:grid;gap:var(--s3);max-width:34rem;margin-bottom:var(--s5)}
.ctl-title{font-size:clamp(1.375rem,2.2vw,1.875rem);line-height:1.15;letter-spacing:-.015em;
  margin:0;color:var(--ink)}
.rig-lead{margin:0 0 var(--s2);color:var(--ink);max-width:46ch;
  font-size:var(--t-body);line-height:1.5}
.ctl-h{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s2);margin-bottom:.4rem}

@media (max-width:56rem){
  .stage{grid-template-columns:1fr;padding-right:var(--gut)}
  .roll{grid-area:auto;position:absolute;inset:0;margin:0;opacity:.16;z-index:-1}
  .col{justify-content:flex-start}
  .hud{position:static;margin-top:auto}
  .ticks{display:none}
}
@media (prefers-reduced-motion:reduce){
  .roll-strip,.roll-strip img,.says .lede{transform:none}
}

/* ---- the controls, restored -------------------------------------------
   These were collateral damage when the section-era CSS was stripped, which
   left the segmented buttons rendering as browser defaults: black on light
   grey even in the dark theme. Unstyled controls fail contrast in a way no
   token audit catches, because the tokens were never applied. */
.k{font-size:var(--t-tech);letter-spacing:.11em;text-transform:uppercase;margin:0;color:var(--ink)}
.v{font-size:var(--t-tech);letter-spacing:.06em;font-variant-numeric:tabular-nums;color:var(--ink)}

input[type="range"]{-webkit-appearance:none;appearance:none;width:100%;height:1.6rem;
  background:none;cursor:ew-resize}
input[type="range"]::-webkit-slider-runnable-track{height:1.6rem;background:var(--g0);border:1px solid var(--ink)}
input[type="range"]::-moz-range-track{height:1.6rem;background:var(--g0);border:1px solid var(--ink)}
input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:1rem;height:1.6rem;background:var(--ink);border:1px solid var(--ink);margin-top:-1px}
input[type="range"]::-moz-range-thumb{width:1rem;height:1.6rem;background:var(--ink);
  border:1px solid var(--ink);border-radius:0}

.seg{display:flex;border:1px solid var(--ink);max-width:26rem}
.seg button{flex:1;font:inherit;font-size:var(--t-tech);letter-spacing:.08em;
  text-transform:uppercase;background:var(--g0);color:var(--ink);
  border:0;border-right:1px solid var(--ink);padding:.5rem .4rem;cursor:pointer;min-height:2.75rem}
.seg button:last-child{border-right:0}
.seg button.on{background:var(--ink);color:var(--g0)}

/* scoped: this is the scene's word control, not a site-wide input style. Left
   unscoped it capped the contact form's text fields at 26rem while the email
   field, which the selector never matched, ran full width beside them. */
.controls input[type="text"]{font:inherit;font-size:var(--t-body);width:100%;max-width:26rem;
  background:var(--g0);color:var(--ink);border:1px solid var(--ink);
  padding:.6rem .75rem;min-height:2.75rem}

/* =====================================================================
   RESPONSIVE. Every width from 320 to 3440, and every browser zoom.
   Measured, not assumed: at 360 the five column record table pushed the
   document 3px wide, which is a horizontal scrollbar on a phone.
   ===================================================================== */

/* ---- the record collapses instead of overflowing --------------------- */
@media (max-width:48rem){
  .record thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  .record table,.record tbody,.record tr,.record td{display:block;width:100%}
  .record tr{border-bottom:1px solid var(--line);padding:.9rem 0}
  .record td{border:0;padding:0}
  .record td:nth-child(1){font-size:1rem;margin-bottom:.25rem}
  .record td:nth-child(2){color:var(--ink);opacity:.8;margin-bottom:.5rem}
  .record td:nth-child(3)::before{content:"measured ";font-size:max(.625rem, 10px);letter-spacing:.12em;
    text-transform:uppercase;opacity:.7}
  .record td:nth-child(4)::before{content:"budget ";font-size:max(.625rem, 10px);letter-spacing:.12em;
    text-transform:uppercase;opacity:.7}
  .record td.n{text-align:left;padding:0;display:inline-block;width:auto;margin-right:1.5rem}
  .record td:nth-child(5){display:inline-block;width:auto;vertical-align:middle}
}
/* the table never causes a scrollbar at any width */
.record{overflow-x:auto}
.record table{min-width:0}

/* ---- line height is a scale, not a per element guess ------------------ */
:root{--lh-tight:1.06;--lh-snug:1.3;--lh-body:1.6;--lh-loose:1.7}
.spec{line-height:var(--lh-tight)}
.lede{line-height:var(--lh-body)}
.k,.v,.readout span,.ticks li,.sheet-trim{line-height:var(--lh-snug)}
.record td,.record th{line-height:var(--lh-snug)}
body{line-height:var(--lh-body)}

/* ---- text never runs long or short ----------------------------------- */
.lede{max-width:min(46ch,100%)}
.spec-hero{max-width:min(calc(17ch / var(--col-fit,1)),100%)}
.sheet .spec{max-width:none}

/* ---- short viewports: the sticky stage must not clip its own content -- */
@media (max-height:44rem){
  .stage{padding-top:clamp(3.2rem,7vh,4.5rem);padding-bottom:clamp(2.2rem,5vh,3rem);
         gap:clamp(.8rem,2vw,1.6rem)}
  .col{gap:clamp(.5rem,1.4vh,.9rem)}
  .spec-hero{font-size:calc(clamp(1.35rem,3.2vw,2.4rem) * var(--col-fit,1))}
  .sheet{padding:.6rem .8rem}
  .readout b{font-size:clamp(1.1rem,2vw,1.6rem)}
}
@media (max-height:34rem){
  .sheet{display:none !important}          /* the sheet is the first thing to go, not the headline */
  .hud-reads .readout:nth-child(n+3){display:none}
}

/* ---- very small screens ---------------------------------------------- */
@media (max-width:24rem){
  :root{--gut:1rem}
  .spec-hero{font-size:calc(clamp(1.4rem,7.5vw,1.9rem) * var(--col-fit,1))}
  .nav-links{gap:.75rem}
  :root{--nav-t:.625rem}
  .hud-reads{gap:1rem}
}

/* ---- very large screens: fluid container, never a lonely column ------- */
@media (min-width:100rem){
  .stage{grid-template-columns:minmax(0,1fr) clamp(22rem,24vw,34rem)}
  .spec-hero{font-size:calc(clamp(3.4rem,3.6vw,5rem) * var(--col-fit,1));
             max-width:min(calc(16ch / var(--col-fit,1)),100%)}
  .says .lede{font-size:clamp(1.2rem,min(1.2vw,2.4vh),1.6rem)}
  .readout b{font-size:clamp(2rem,min(2vw,4vh),2.8rem)}
  .record{max-width:min(120rem,100%)}
}

/* ---- wide screens: the controls stop being a lonely left column --------
   Measured at 1440: the block used 544px and left 831px of empty page to
   its right, which is most of the screen, and is why the section read as a
   stray form rather than part of the piece. Two columns from 68rem up. */
@media (min-width:68rem){
  .controls-in{max-width:min(76rem,100%);
    grid-template-columns:repeat(2,minmax(0,1fr));
    column-gap:var(--s5);align-items:start}
  .ctl-title,.rig-lead{grid-column:1 / -1}
}
/* One step wider, and no second rule anywhere else setting this: the pair
   that used to live in the 100rem block cancelled each other, 120rem then
   46rem, so the wider value never applied. */
@media (min-width:100rem){
  .controls-in{max-width:min(96rem,100%)}
}

/* ---- honour user zoom: nothing is a fixed pixel island ----------------
   This was written as a floor and behaved as a lock. max(0.6875rem, 11px)
   evaluates to 11px at EVERY root size, because 0.6875rem IS 11px: the two
   halves were the same number, so the rem half could never win and these
   labels sat at 11px whatever the reader asked for, including through the
   page's own Text size control.
   The shape was right, the values were not. --t-tech is 13px now, so the rem
   half grows with the root while the px half still catches the bottom: at the
   Text size floor of 12 these would fall to 9.75px, and 11 is as small as any
   label in here is allowed to get. */
.theme,.k,.v,.readout span,.ticks li{font-size:max(var(--t-tech), 11px)}
/* The nav keeps its own narrow-screen reduction: --nav-t drops to .625rem
   below 30rem and must still win there, or the links wrap the header. */
.nav-links a{font-size:max(var(--nav-t, .75rem), 10px)}

/* ---- small and short screens: the scene UNSTICKS ----------------------
   A 100vh sticky stage with overflow hidden was clipping up to 183px of its
   own content on a 568px tall phone, measured. Below these thresholds the
   stage stops being sticky and simply flows, so nothing is cut off. The roll stays as a quiet ground, the
   readouts stay live, and everything is reachable. */
@media (max-width:48rem), (max-height:34rem){
  .track{height:auto}
  .stage{position:static;height:auto;min-height:auto;overflow:visible;
         display:block;padding:clamp(2.5rem,7vh,4rem) var(--gut) clamp(2rem,5vh,3rem)}
  .col{display:flex;gap:clamp(.9rem,2.4vh,1.4rem)}
  .roll{position:relative;inset:auto;opacity:1;z-index:0;margin:1.6rem 0 0;
        height:min(52vh,26rem);overflow:hidden}
  .roll-strip{position:absolute}
  .hud{position:static;margin-top:1.6rem;gap:1rem}
  .ticks{display:none}
  .progress{position:fixed;top:0;left:0;right:0;z-index:20}
  .sheet{display:block;width:auto !important;max-width:100%}
  .hud-reads .readout:nth-child(n+3){display:block}
}

/* ---- the record can never widen the document ------------------------- */
.record{overflow-x:auto;-webkit-overflow-scrolling:touch}
.record table{width:100%;table-layout:fixed}
/* Five equal columns handed the status dot the same width as the description,
   forcing four line cells while a fifth of the table sat empty. */
.record th:nth-child(1){width:19%}
.record th:nth-child(2){width:45%}
.record th:nth-child(3){width:14%}
.record th:nth-child(4){width:14%}
.record th:nth-child(5){width:8%}
.record td,.record th{overflow-wrap:anywhere}
@media (max-width:48rem){
  .record table{table-layout:auto}
  .record{overflow-x:visible}
}

/* ---- ultrawide: a fluid container, not a stretched strip ---------------
   At 3440 the reading column measured 2745px with the text capped near
   1000px, leaving about 1700px of dead space beside it. The rule is a fluid
   container, never a small fixed max-width and never a lonely column. */
@media (min-width:110rem){
  .stage{
    padding-left:0;padding-right:0;
    grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);
    gap:clamp(3rem,4vw,6rem);
  }
  .col,.hud{padding-left:var(--edge)}
  .roll{margin-right:calc((100vw - min(92vw,148rem)) / -2 - 2vw)}
  /* Body copy grows on a large monitor; the display type does not. A 64px
     headline on a 27 inch screen is already loud enough, and the text you
     actually read is the thing that was too small. */
  /* The HERO stays where it was; the growth goes into the copy. Both are sized
     on BOTH axes so a wide but short window cannot produce a line that will not
     fit the column it has to live in. */
  /* a 96px headline inside a 15ch measure is a six line tower that does not fit
     the column it lives in. Wider measure, same size, four lines. */
  .spec-hero{font-size:calc(clamp(4rem,4.4vw,6rem) * var(--col-fit,1));
             max-width:min(calc(20ch / var(--col-fit,1)), 100%)}
  .says .lede{font-size:clamp(1.35rem,min(1.35vw,2.7vh),2.1rem);max-width:min(50ch,100%)}
  .readout b{font-size:clamp(2.4rem,min(2.4vw,4.8vh),3.4rem)}
  .sheet .spec{font-size:clamp(1.4rem,min(1.55vw,3.1vh),2.3rem)}
  .chapter,.state,.readout span{font-size:clamp(.75rem,.75vw,1.05rem)}
  .ticks li{font-size:clamp(.7rem,.68vw,.95rem)}
}


/* =====================================================================
   ALIVE AT REST, AND RESPONSIVE TO THE HAND.
   The page had no pulse when nobody scrolled, and the only pointer
   response was a text cursor. Everything below is subtle on purpose: at
   rest it should read as breathing, not as animation playing at you.
   ===================================================================== */

/* the strip carries a slow drift on top of the scroll position, and the
   pointer shifts it in both axes for depth */
.roll-strip{transform:translate3d(var(--roll-x, 0px), var(--roll, 0px), 0)}
/* each frame breathes on its own phase, so they never move in lockstep */
.roll-strip figure{position:relative;overflow:hidden;transition:flex-basis .5s var(--ease)}
.roll-strip img{
  transform:scale(var(--fs, 1.13)) translate3d(var(--bx, 0px), var(--by, 0px), 0);
  transition:filter .5s var(--ease), opacity .5s var(--ease);
  will-change:transform;
}
/* the film gate: the frame under the pointer sharpens, its neighbours recede */
.roll-strip figure[data-near="1"] img{filter:grayscale(1) contrast(1.08) brightness(1.04)}
.roll-strip figure[data-near="0"] img{filter:grayscale(1) brightness(.82);opacity:.72}

/* the sheet leans toward the pointer, and lifts as it gets close */
.sheet{
  transform:perspective(1400px)
            rotateX(calc(var(--tilt-y, 0) * -1deg))
            rotateY(calc(var(--tilt-x, 0) * 1deg))
            translate3d(0, calc(var(--near-lift, 0) * -4px), 0);
  transition:transform .45s var(--ease);
  will-change:transform;
}

/* every word of the headline answers the pointer through the weight axis,
   which is the one thing on this page that only exists because the
   typeface is variable and is genuinely his */
.wd{display:inline-block;font-style:normal;font-variation-settings:"wght" var(--wd, 400);
    transition:font-variation-settings .28s var(--ease)}

/* the state dot has a heartbeat, so the instrument reads as running */
.dot{animation:beat 3.4s var(--ease) infinite}
@keyframes beat{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(.72);opacity:.55}}

/* controls answer the hand */
.readout{transition:transform .3s var(--ease)}
.readout:hover{transform:translateY(-2px)}
.nav-links a{position:relative}
.nav-links a::after{content:"";position:absolute;left:0;right:0;bottom:-2px;height:1px;
  background:currentColor;transform:scaleX(var(--u, 0));transform-origin:left;
  transition:transform .3s var(--ease)}
.nav-links a:hover::after{--u:1}
.nav-links a:hover{text-decoration:none}
.seg button,.theme{transition:background-color .2s var(--ease),color .2s var(--ease),
                              transform .2s var(--ease)}
.seg button:hover{background:var(--g2)}
.seg button.on:hover{background:var(--ink)}
input[type="range"]::-webkit-slider-thumb{transition:transform .18s var(--ease)}
input[type="range"]:hover::-webkit-slider-thumb{transform:scaleY(1.12)}
.record tr{transition:background-color .2s var(--ease)}
.record tr:hover{background:var(--g1)}

@media (prefers-reduced-motion:reduce){
  .roll-strip,.roll-strip img,.sheet{transform:none !important}
  .dot{animation:none}
  .wd{transition:none}
}

/* =====================================================================
   ONE LEFT EDGE.
   Measured at 300px: the chapter label, headline, body copy, readouts and
   the hud all sat at left 16, while the specimen sheet's TEXT sat at 33,
   pushed right by the sheet's own 16px padding and 1px border. A bordered
   box aligned on its box is misaligned on its content, and content is what
   the eye reads down.

   Desktop: pull the sheet left by exactly its padding plus border, so its
   text lands on the same edge as everything else and only the box bleeds.
   Small screens: drop the side padding and the side borders entirely, so
   the text aligns with nothing to compensate for.
   ===================================================================== */
/* THE SPECIMEN SITS ON THE LINE, BOX AND ALL.
   It used to be a bordered box pulled left by its own padding so that its TEXT
   landed on the shared edge. That aligned the text and put the BOX out on its
   own, one edge out of line with everything above it.
   A box whose edge and whose text cannot both sit
   on the line is the wrong object. So it loses its left and right sides and
   becomes a band between two rules: edge, text and everything above it now land
   on exactly one line, with nothing to compensate for. */
:root{--sheet-pad:0px}

.sheet{
  width:auto;
  margin-left:0;
  padding:clamp(.8rem,1.6vh,1.2rem) 0;
  border-left:0;border-right:0;
  border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);
  background:transparent;
  box-shadow:none;
}
.sheet .spec,.sheet-trim{margin-left:0}

/* =====================================================================
   ONE EDGE, DEFINED ONCE.
   Two different gutters were in play above 110rem: the stage centred itself
   and gave the column its own padding, while the progress rule and the tick
   row kept the old page gutter. That is 15px of daylight between the rule and
   the "00" underneath it, which is exactly what it looked like. Everything on
   the left of the scene now reads ONE value.
   ===================================================================== */
/* --frame is the page's own width, --edge the inset from it to the text.
   Every top level block on every page uses BOTH, so there is exactly one left
   edge from the logo at the top to the licence line at the bottom. Before this
   the nav had no frame at all and ran the full viewport, the scene and the
   controls used different insets above 110rem, and the reading pages had a
   third box again: on a 3440 screen the logo sat 528px left of the headline. */
:root{--frame:100%; --edge:var(--gut)}
@media (min-width:110rem){ :root{--frame:min(92vw,148rem); --edge:clamp(2rem,3vw,4rem)} }

.nav,.stage,.controls,.foot,.read{
  max-width:var(--frame);
  margin-inline:auto;
}
/* .record is nested inside .controls, so it must NOT take the inset again or
   the table sits at twice the edge. It inherits the frame from its parent. */
.nav,.controls,.foot{padding-left:var(--edge);padding-right:var(--edge)}
.ticks,.progress{left:var(--edge);right:var(--edge)}

/* =====================================================================
   NOTHING GETS CUT OFF, AT ANY WIDTH.
   Measured at 220px: the nav links reached x=276, the theme button 276 and
   the four readouts 259, all against a 220px viewport. The document did not
   scroll sideways because the root clips, which is worse: the controls were
   simply amputated instead of announcing themselves.
   ===================================================================== */
.nav{flex-wrap:wrap;row-gap:.6rem}
.nav-links{flex-wrap:wrap;row-gap:.5rem;min-width:0}
.nav-links a,.theme{white-space:nowrap}

/* four readouts in a row stop fitting long before the text does */
.hud{flex-wrap:wrap;row-gap:1rem}
.hud-reads{flex-wrap:wrap;row-gap:1rem;min-width:0}
.readout{min-width:0}

@media (max-width:30rem){
  .hud-reads{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem 1.25rem;width:100%}
  .hud{width:100%}
}
@media (max-width:22rem){
  .nav-links{gap:.6rem}
  :root{--nav-t:.625rem}
  .theme{padding:.3rem .45rem}
}

/* a very long word must break rather than push the layout */
.spec,.lede,.record td{overflow-wrap:break-word}
.spec-hero{hyphens:none}

/* When the column cannot hold everything on one screen, the specimen is what
   goes. Set by the engine after it measures, never by a breakpoint guess. */
.col.no-sheet .sheet{display:none}


/* Held for the few milliseconds the record table samples colour. A value caught
   mid fade is not a value that is on the page, and that number gets published. */
:root.sampling *,
:root.sampling *::before,
:root.sampling *::after{transition:none !important}
