/* ============================================================================
   Product surfaces. Everything below renders real bundle 1.1.0 data — real
   session ids, real FENs, real solution lines, real coach notes. If the site
   shows it, a coach can teach it.
   ========================================================================== */

/* ========================================================= ACT 5 — console
   Act 4 is authored in annot.css, which loads after this file: the two-column
   composition, the plate, its three rails, the board face and the six callouts are
   all there, in one place, because every one of them is measured against the other
   five. What used to be here — .session-head, .cs-*, .flow, .routine, .notes, .hw,
   .mv-*, .prompt, .solve — belonged to the four-pane console that act 4 stopped
   being on 2026-09-04. The selectors are gone; the tiers further down this file
   still name a few of them and now match nothing, which is cheaper than editing a
   tier that also carries three other acts.
   ======================================================================== */

/* ========================================================== ACT 6 — the system
   Act 6 is authored in system.css, which loads after this file, for the same reason act
   4 moved to annot.css: the plate, the six figures, their leaders, the ream and the
   pathway control are all measured against each other and belong in one place. What used
   to be here — .system, .system-head, .bento, the eight .b-* areas, .cell and its .c-*
   furniture, .drill and .stack — was the eight-card bento the founder replaced on
   2026-09-05. The selectors are gone; .flow, .gate-names, .tracks and .drill-foot went
   with them further down, and `.cell` came out of the two paper-lift lists below.
   ======================================================================== */

/* ============================================================ paper lift
   --lift is continuous (0 dark room, 1 ivory room), so the page changes tone
   with the 3D room instead of cutting to a different identity halfway down. */
/* These six were color-mix() over var(--lift), which meant every element on the
   page recomputed an uncacheable colour whenever the paper lift moved — 12ms of
   style recalc per frame in a trace. The director now resolves them in JS and
   writes six concrete rgb() strings, so nothing here mixes at style time. */
body {
  color: var(--fg);
}
.lead, .sp-theme, .cmp-verdict { color: var(--fg-dim); }
.mono, .coord, .kicker, .ticker span, .facts dt, .scraps li,
.sp-units li, .gates li { color: var(--fg-faint); }
.cmp-card, .pane, .stage-panel, .scrub { background: var(--glass); border-color: var(--line); }
.ticker, .facts > div, .proof { border-color: var(--line); }
.cta { background: var(--fg); color: var(--fg-inv); border: 1px solid var(--fg); }
.cta.ghost { color: var(--fg); border-color: var(--line-2); }
.mark { background: var(--fg); }
.board2d { border-color: var(--line-2); }

/* ======================================================== the milled plate
   Every .cta on the site — #nav, act 0's pair, act 6's rate pair, act 7's promo
   pair. Shipped, a .cta was a flat fill with a 2px lift, and its hover was the
   worst measured contrast on the page: `background: var(--hue)` under `#fff` ink
   reads 3.05:1 over the pawn green and 2.42:1 over act 6's gold, an SC 1.4.3
   failure in seven places. That hover is gone (acts.css:75). The hue now arrives
   on the EDGE, the plate stays var(--fg) under var(--fg-inv), and the label holds
   the same ~15:1 in every state.

   TWO TOKENS DO ALL THE WORK, AND NEITHER OF THEM RAMPS. --ivory (#f2ece0) and
   --void (#06060a) are laid in PAIRS at every position, and --fg's own two ends
   ARE those values. So on acts 0-4 and 7 the plate is already at ivory and only
   the void half of each pair draws; on acts 5-6 the plate is ink and only the
   ivory half draws. Both halves put the light ABOVE the button, so one stack is a
   polished cream plate catching a dark room AND a dark plate catching a key light
   — with no body.is-paper switch, no second treatment, and no value that is not
   already a token. It also gives the button a shape through act 6's tonal
   crossover (t 0.86-0.99), where --fg and --fg-inv meet at grey and the shipped
   chip is a flat rectangle.

   WHAT MAKES IT LIQUID. --mx/--my are the pointer, written on the hovered element
   by chrome.js — on the ELEMENT, never on :root, because a custom property on the
   root restyles the whole document (acts.css:118). They move the light source of
   the face gradient, so the plate is brightest exactly where the cursor is and
   rolls into its own chamfer away from it. There is no keyframe anywhere in this
   block and nothing animates off-hover: the listener does not exist until the
   pointer is inside the button, and the only moving declaration is a background
   position that repaints one ~168x49 box on the main thread.

   The bevel is four zero-blur zero-spread inset hairlines, so it paints 1px at
   each edge and never tints the interior — which matters on the ghost, where the
   GL wash is doing the legibility work. Same list length in every state, so
   rest -> hover -> active interpolate instead of switching. */
.cta {
  /* Three declarations, no metric. The plate is now the containing block for its
     own sheen and its own press ripple, both of which are `z-index: -1` children:
     `isolation` traps them so they paint ABOVE the face gradient and BELOW the
     label instead of escaping behind the plate, and `overflow` clips the ripple
     to the box. None of the three can move a box, which matters because every
     .cta on the site is measured — #nav's bar height IS this button's 37.17px
     (acts.css:64), the hero's row is asserted to the pixel and the terms act's
     copy is asserted against the fold (gate ids hero-fits-at-rest,
     terms-cta-on-stage). An element's own outline is not clipped by its own
     overflow, so base.css:125's focus ring is untouched; and every .cta on the
     page is a FLEX ITEM (#nav and .cta-row are both flex), so it is blockified
     and `overflow` cannot move an inline baseline here either. */
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* 6px, and it is not a new value: terms.css:405 already draws the licence card's
     own plate at 6px, from the founder's pricing mock. So every button on the site
     is now on one radius, taken from the newest mock rather than invented here.
     `overflow: hidden` above clips to this curve, so the press wave and the
     secondary's growing pane round themselves off with no second declaration; the
     four inset bevel hairlines follow it too. STATE.md decision 12 is the open
     question about radius on the rest of the page — the panels are still square. */
  border-radius: 6px;
  /* the milled face. The light sits above the box and tracks the pointer; the
     ivory stop lights the ink plate, the void stops chamfer the ivory one. */
  background-image: radial-gradient(150% 210% at var(--mx, 50%) var(--my, -28%),
      rgb(242 236 224 / 0.20) 0%,
      rgb(242 236 224 / 0) 27%,
      rgb(6 6 10 / 0.05) 58%,
      rgb(6 6 10 / 0.15) 100%);
  /* THE PLATE NOW SITS ON SOMETHING. Four insets, then three outer shadows, and
     the list is seven long in EVERY state below so rest -> hover -> active still
     interpolate instead of switching. The last entry is the pair that makes one
     stack work in both rooms: a void shadow is what an ink plate casts on act
     6-7's paper, and it is invisible under an ivory plate on the dark board — so
     the seventh shadow is an ivory BLOOM, off at rest, lit while the plate is
     held up off the board. Same two tokens, laid in a pair, as the face. */
  box-shadow:
    inset  0  1px 0 0 rgb(242 236 224 / 0.50),
    inset  0 -1px 0 0 rgb(6 6 10 / 0.34),
    inset  1px 0 0 0 rgb(242 236 224 / 0.16),
    inset -1px 0 0 0 rgb(6 6 10 / 0.16),
    0  1px  2px rgb(6 6 10 / 0.30),
    0  4px 12px rgb(6 6 10 / 0.16),
    0     0  0 rgb(242 236 224 / 0);
  /* acts.css:73's shorthand set five properties and five durations. background-image
     is listed for the day an engine interpolates it, but MEASURED in Chrome 141 it is
     still discrete: mid-flight the face is already at its hover alphas while
     border-*-color, box-shadow and transform are the six transitions actually running.
     That is fine here and worth saying rather than implying otherwise — the step is
     0.0076 of plate luminance (p80 0.8575 -> 0.8651), what the eye reads easing is the
     bevel and the 2px lift, and what actually flows is the pointer: --mx/--my are
     rewritten every frame the cursor moves, so the highlight slides continuously
     rather than transitioning between two states. */
  transition-property: transform, background-color, color, border-color, box-shadow,
                       background-image, background-size, background-position;
  /* The durations were a five-long list on acts.css:107 that product.css's sixth
     property cycled back into. Two more properties would have gone on cycling and
     nobody reading either file could say what they resolved to, so the list is
     written out: the first six are exactly what shipped, and the ghost's fill —
     background-size and -position — takes 380ms, the box-shadow's own value,
     because the fill and the bevel are one gesture. */
  transition-duration: 260ms, 260ms, 260ms, 300ms, 380ms, 260ms, 380ms, 380ms;
}
/* the pointer arrives: the plate lifts under the cursor and the chamfer deepens */
.cta:hover {
  background-image: radial-gradient(150% 210% at var(--mx, 50%) var(--my, -28%),
      rgb(242 236 224 / 0.30) 0%,
      rgb(242 236 224 / 0) 31%,
      rgb(6 6 10 / 0.06) 60%,
      rgb(6 6 10 / 0.19) 100%);
  box-shadow:
    inset  0  2px 0 0 rgb(242 236 224 / 0.72),
    inset  0 -2px 0 0 rgb(6 6 10 / 0.46),
    inset  1px 0 0 0 rgb(242 236 224 / 0.22),
    inset -1px 0 0 0 rgb(6 6 10 / 0.22),
    0  3px  4px rgb(6 6 10 / 0.26),
    0 10px 26px rgb(6 6 10 / 0.24),
    0     0 22px rgb(242 236 224 / 0.10);
}
/* pressed: the plate sits back down out of the lift and the bevel inverts —
   chamfer on top, land at the bottom, which is what a pressed plate does. */
.cta:active {
  transform: translateY(0);
  transition-duration: 90ms;
  box-shadow:
    inset  0  2px 0 0 rgb(6 6 10 / 0.40),
    inset  0 -1px 0 0 rgb(242 236 224 / 0.34),
    inset  1px 0 0 0 rgb(6 6 10 / 0.18),
    inset -1px 0 0 0 rgb(242 236 224 / 0.14),
    0  1px  1px rgb(6 6 10 / 0.34),
    0  2px  5px rgb(6 6 10 / 0.12),
    0     0  0 rgb(242 236 224 / 0);
}
/* the ghost stays secondary and stays SECOND: no fill of its own, a film instead
   of a plate, and a bevel pulled back to a third of the filled button's. The film
   is var(--glass), which ramps with the room, so it is a dark scrim on the lit
   board and a paper wash on acts 5-6 — the one thing the ghost was missing, since
   its label sat straight on the board. The stop starts off-box so only its tail
   renders; --glass is 0.88 alpha and a full-strength film would read as a fill. */
.cta.ghost {
  /* THE DOT IS THE FIRST LAYER, so when it grows it covers the film and the face
     and the chip becomes a pane. --glass is the site's own pane fill and the
     director ramps it (director.js:21), so the grown chip is near-black on the
     board and paper on acts 6-7 while the label stays var(--fg) — the fill is the
     inverse of its own ink in both rooms, and the label's contrast goes UP on
     hover rather than down. It is a SQUARE: the page has one radius on it
     (STATE.md decision 12) and .kicker .dot sets `border-radius: 0` on purpose,
     so a growing square is this site's version of the reference's growing dot. */
  background-image:
    linear-gradient(0deg, var(--glass), var(--glass)),
    linear-gradient(180deg, var(--glass) -68%, rgb(0 0 0 / 0) 56%),
    radial-gradient(150% 210% at var(--mx, 50%) var(--my, -28%),
      rgb(242 236 224 / 0.10) 0%,
      rgb(242 236 224 / 0) 34%,
      rgb(6 6 10 / 0) 100%);
  background-repeat: no-repeat;
  background-size: 9px 9px, 100% 100%, 100% 100%;
  background-position: 17% 42%, 0 0, 0 0;
  box-shadow:
    inset  0  1px 0 0 rgb(242 236 224 / 0.20),
    inset  0 -1px 0 0 rgb(6 6 10 / 0.14),
    inset  1px 0 0 0 rgb(242 236 224 / 0.08),
    inset -1px 0 0 0 rgb(6 6 10 / 0.08),
    0  1px 2px rgb(6 6 10 / 0.16),
    0  2px 8px rgb(6 6 10 / 0.10),
    0     0 0 rgb(242 236 224 / 0);
}
.cta.ghost:hover {
  background-image:
    linear-gradient(0deg, var(--glass), var(--glass)),
    linear-gradient(180deg, var(--glass) -52%, rgb(0 0 0 / 0) 62%),
    radial-gradient(150% 210% at var(--mx, 50%) var(--my, -28%),
      rgb(242 236 224 / 0.18) 0%,
      rgb(242 236 224 / 0) 38%,
      rgb(6 6 10 / 0) 100%);
  background-size: 140% 140%, 100% 100%, 100% 100%;
  background-position: 50% 50%, 0 0, 0 0;
  box-shadow:
    inset  0  2px 0 0 rgb(242 236 224 / 0.34),
    inset  0 -1px 0 0 rgb(6 6 10 / 0.20),
    inset  1px 0 0 0 rgb(242 236 224 / 0.12),
    inset -1px 0 0 0 rgb(6 6 10 / 0.12),
    0  3px  4px rgb(6 6 10 / 0.22),
    0  9px 22px rgb(6 6 10 / 0.20),
    0     0 18px rgb(242 236 224 / 0.07);
}
/* base.css:125's 2px --hue outline is untouched and not duplicated: the edge
   simply lights the way it does under a pointer. */
.cta:focus-visible { border-color: var(--hue); }

/* ==================================================== the plate is never still
   The pointer-lit face above answers "is this material?" only while a cursor is
   inside the button. Away from the cursor the plate was a still rectangle, which
   is the one surface on the page with no life of its own — the room breathes, the
   pieces move, the bands ramp, and the thing a buyer is meant to press did not.

   THIS IS THE SAME LIGHT, PANNING. Not a second treatment: `--mx` is the face
   gradient's own horizontal light position, the one the pointer writes, and at
   rest it now drifts 34% -> 66% -> 34% on an 11s cycle. So the plate's highlight
   slides and its chamfer shading deepens on the far side, exactly as it does under
   a cursor, because it IS the cursor's mechanism with the room driving it instead.
   No new gradient, no new colour, no mask, and nothing to reconcile between the
   ivory plate of acts 0-4 and the ink plate of acts 5-7 — the paired stops that
   already work in both rooms are the ones moving.

   FOUR THINGS THAT WERE TRIED AND MEASURED FIRST, since they look obvious:
    - A travelling band across the whole face. On the ivory plate the only lobe
      that reads is the DARK one, and darkening the paper under dark ink took the
      label's rendered p10 from 4.68:1 to 4.04:1 with tools/contrast.cjs — under
      SC 1.4.3 for 14.4px type.
    - The same band masked to the chamfer, away from the glyphs. Contrast came back
      (4.57:1) but at 4x the dark lobe on cream reads as a thumbprint, not a light.
    - Ivory-only, so the polarity is always "light arriving". Sings on the ink
      plate, invisible on the ivory one — ivory over ivory is nothing.
    - Two lights, a drifting layer under the pointer's. Correct, and it needs a
      fourth background layer in five rules that must stay list-length-matched to
      interpolate. This does the same thing with none.

   Cost: one background-position's worth of repaint on the same ~168x49 box the
   pointer already repaints — product.css:139's measured and accepted cost, now
   also running at rest. base.css:91 pauses every animation inside an act that is
   not live, so the plates cost nothing until their act is on screen; #nav's is the
   only one that always runs and it is 141x37.

   `@property` is what makes it a MOTION rather than two jumps: an unregistered
   custom property animates discretely. Where it is unsupported the highlight steps
   between three positions on an 11s cycle instead of sliding, which is a
   degradation and not a break.

   The pointer wins by REMOVING the animation, not by pausing it: a paused
   animation still supplies its own value, and animations outrank the inline style
   chrome.js writes. On the way out the highlight snaps to the cycle's phase — the
   shipped build already snapped it back to 50% there, so it is the same class of
   artifact at a different destination. */
@property --mx {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 50%;
}
.cta { animation: cta-pan 13s ease-in-out infinite; }
.cta:hover, .cta:active { animation: none; }
/* Edge to edge, and measured to be edge to edge for a reason: the face is a
   150% x 210% ellipse centred above the box, so its falloff is broad and sliding
   its centre a third of the way across moves almost nothing. Profiled on the hero
   plate as rendered pixels, 34%->66% swings a column mean by +/-5.8 and 6%->94%
   swings it by +/-14.7. The second is a slow tilt a reader can see; the first is a
   rumour. 6 and 94 rather than 0 and 100 because chrome.js's own pointer range is
   the width of the box and the light should stay inside the plate it is lighting. */
@keyframes cta-pan {
  0% { --mx: 6%; }
  50% { --mx: 94%; }
  100% { --mx: 6%; }
}

/* THE PRESS. chrome.js appends one of these at the point that was actually
   pressed and removes it when it is done. The ink is `currentColor` — on a .cta
   that is var(--fg-inv), the label's own colour, which is by definition the
   opposite of the plate it is sitting on, so the same rule is a dark bloom on the
   ivory plate and a light one on the ink plate with no second declaration. */
.cta-rip {
  position: absolute; z-index: -1; pointer-events: none;
  left: var(--rx, 50%); top: var(--ry, 50%);
  width: 22px; height: 22px; margin: -11px 0 0 -11px;
  border-radius: 50%;
  /* A WAVE, not a bloom. A filled disc of ink at 0.30 on the cream plate reads as a
     thumbprint — parked at its own peak frame and looked at, it was a smudge. The
     stops make it a soft ring instead, and because the ring is scaled rather than
     redrawn its width grows with it, which is what a wave crossing a surface does.
     Compositor-only either way: one transform and one opacity. */
  background-image: radial-gradient(circle,
      rgb(0 0 0 / 0) 0%, currentColor 54%, rgb(0 0 0 / 0) 80%);
  /* Linear, and shaped in the keyframes instead. --ease is a front-loaded ease-out,
     and driving BOTH scale and opacity through it spent the whole fade in the first
     third: measured on the ivory plate, the disc was already 150px wide at 0.05
     alpha by 200ms and nothing could be seen at any frame. So the impulse rises to
     its own peak in the first 130ms and spends the remaining 490ms opening and
     fading, which is the shape of a press being absorbed. */
  animation: cta-rip 620ms linear forwards;
}
@keyframes cta-rip {
  0% { transform: scale(0.2); opacity: 0.06; }
  22% { transform: scale(2.3); opacity: 0.24; }
  100% { transform: scale(7); opacity: 0; }
}

/* ==================================================== the secondary TRAVELS
   The filled plate and the ghost were separated by fill-versus-outline, and the
   note above records how weak that turned out to be. They are now separated by
   what happens when a pointer arrives, which is a difference nobody can miss:
   the PRIMARY is a plate you press — it lifts, it catches the light, it takes a
   ripple where you pressed it. The SECONDARY is a link you follow — the label
   steps aside and an arrow arrives in its place, over a pane that grows out of
   the dot at its left edge.

   That is the reference component's gesture with its geometry inverted: it slid a
   duplicate label in from the right of a 128px pill, and this box is 125.05px
   with 27.6px of slack around a 97.45px label, so the arriving layer is
   absolutely positioned across the WHOLE box — padding included — and centres
   itself there. Out of flow, so the flex box still sizes to .cta-l alone and the
   chip is the same 125.05x37.17 it was measured at.

   The arrow is the page's own glyph, built the way .ses-arrow's head is built
   (annot.css:161) and without its box, because at 12.8px the box is 19.2px of a
   27.6px allowance and the pair reads crammed. */
.cta-l {
  display: block;
  /* The two labels are the SAME WORDS, so a crossfade puts two copies of "See the
     ten levels" on the chip at once and reads as a double exposure — measured at
     70ms into the gesture, both were legible. So it is a HANDOVER, not a
     crossfade: this one is gone in 150ms while it is still travelling, the
     arriving layer starts fully outside the box and is held back 70ms, and the
     pane has already arrived under both of them. */
  transition: transform 420ms var(--ease-io), opacity 150ms linear;
}
.cta-go {
  position: absolute; inset: 0; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  gap: 0.35em; padding-inline: 0.2em;
  /* The label is one flex item and the arrow another, and without this the label
     wraps to two lines the moment the pair is a pixel wider than the box: 97.45 +
     4.48 + 11.78 + 5.12 = 118.83 of the chip's 125.05 at 1440x900, which is 6.2px
     of slack. Nowrap spends that slack on clipping instead of reflowing, so a font
     fallback with wider metrics shortens the duplicate rather than stacking it. */
  white-space: nowrap;
  opacity: 0; transform: translateX(100%);
  transition: transform 420ms var(--ease-io), opacity 200ms linear;
}
.cta:hover .cta-l { transform: translateX(30%); opacity: 0; }
.cta:hover .cta-go { transform: none; opacity: 1; transition-delay: 70ms; }
.cta-arrow {
  position: relative; width: 0.92em; height: 0.8em; flex: none;
}
.cta-arrow::before, .cta-arrow::after { content: ''; position: absolute; }
.cta-arrow::before {
  left: 0; right: 12%; top: calc(50% - 0.5px); height: 1px; background: currentColor;
}
.cta-arrow::after {
  right: 6%; top: 50%; width: 4.5px; height: 4.5px;
  border-right: 1px solid currentColor; border-top: 1px solid currentColor;
  transform: translateY(-50%) rotate(45deg); transform-origin: 100% 0;
}

/* ================================================================== mobile
   Not a shrink. The camera path stays, the copy stacks, and the surfaces that
   need width (console, bento) become deliberate single-column compositions. */
@media (max-width: 900px) {
  :root { --gut: clamp(16px, 5vw, 30px); }
  body { font-size: 16px; }
  #ladder { left: 5px; gap: 5px; }
  #ladder .rungs li { width: 7px; height: 7px; }
  #readout { display: none; }
  .nav-links { display: none; }
  /* The bar's height on a phone is set by the CTA, which WRAPS at 320px and takes
     the bar to 94px against a kicker pinned at top: 62px — a collision that is the
     wrapped CTA's, not the mark's. But a 36px mark centred in that 94px bar puts its
     own ink 3px past the kicker's first line, so the phone keeps a 30px mark: still
     +15% on the shipped 26, and its bottom lands flush at 62.05px instead of over. */
  #nav .mark { width: 30px; height: 30px; }
  .d1 { font-size: clamp(2.5rem, 12.4vw, 4.4rem); }
  .d2 { font-size: clamp(1.9rem, 9vw, 3.2rem); }
  .d3 { font-size: clamp(1.45rem, 6.6vw, 2.3rem); }
  .pad { padding: clamp(62px, 10vh, 88px) var(--gut) clamp(18px, 3vh, 30px); }
  .lead { font-size: 0.95rem; max-width: none; }

  /* hero: the piece owns the top half, the words own the bottom half */
  .hero { align-content: end; grid-template-rows: auto auto auto; }
  /* 0.86rem is a three-line paragraph in 351px of phone measure, and an eyebrow
     that wraps three times has stopped being a label. 0.74rem holds the two lines
     the shipped 0.66rem held while keeping the colour lift, which is most of what
     the size was buying. */
  .hero .kicker { position: absolute; top: clamp(62px, 10vh, 88px); left: var(--gut); right: var(--gut);
    font-size: 0.74rem; letter-spacing: 0.16em; }
  .hero .d1 { max-width: none; }
  .hero-base { grid-template-columns: 1fr; gap: 14px; }
  .hero-foot { gap: 14px; }
  .ticker { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; text-align: left; }
  .ticker li { justify-items: start; }
  .cta-row { gap: 8px; }
  .cta { padding: 0.78em 1.1em; font-size: 0.86rem; }
  .cta-row .tiny { flex-basis: 100%; }
  .hero-rule { display: none; }
  .proof { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 18px; }

  .spine { grid-template-columns: 1fr; gap: 14px; align-content: end; }
  .spine .col-left { gap: 12px; }
  .lead-swap { min-height: 6.4em; }
  .scraps { display: none; }

  /* Three columns at 390px is 118px each — the beat strips stop being readable and
     the rhyme stops reading. Stacked, it becomes three different hours and then the
     one they are all handed, which is the same argument in the shape a phone has. */
  .chaos { grid-template-rows: auto minmax(0, 1fr); gap: 10px; }
  .chaos-head { grid-template-columns: 1fr; gap: 6px; }
  .chaos .d2 { font-size: clamp(1.6rem, 8vw, 2.6rem); }
  .chaos .lead-swap { min-height: 5.6em; }
  .cmp-row { grid-template-columns: 1fr; gap: 5px; }
  .cmp-row-a .cmp-card { grid-template-columns: 8.4em 1fr; grid-template-areas:
      'who beats' 'min beats' 'verdict verdict'; align-items: center; }
  .cmp-row-a .cmp-who { grid-area: who; }
  .cmp-row-a .cmp-min { grid-area: min; }
  .cmp-row-a .beats { grid-area: beats; }
  .cmp-row-a .cmp-verdict { grid-area: verdict; }
  .cmp-method { display: none; }
  .cmp-row-b .cmp-card:nth-child(n+2) { display: none; }
  .cmp-who b { font-size: 0.94rem; }
  .cmp-verdict { font-size: 0.72rem; }
  .cmp-punch { font-size: 0.82rem; }
  .beat { font-size: 0.4rem; }
  .beat { font-size: 0.4rem; padding: 0.42em 0.1em; }
  .improv-cost { font-size: 0.76rem; }
  .facts { grid-template-columns: repeat(2, 1fr); max-width: none; }
  .scrub { width: 100%; }

  .stages { align-content: start; }
  .stage-sub { display: none; }
  .stage-line { min-height: 0; }
  .stages-head { max-width: none; }
  .stage-panels { position: absolute; left: var(--gut); right: var(--gut);
    bottom: clamp(56px, 9.5vh, 84px); width: auto; height: 1px; }
  .gates { position: absolute; left: var(--gut); right: var(--gut);
    bottom: clamp(20px, 3.4vh, 34px); top: auto; margin: 0; }
  .gates { position: static; display: flex; gap: 0; margin: 10px 0 0; }
  .gates li { grid-template-columns: 1fr; min-width: 0; flex: 1; border-top: 2px solid var(--line);
    border-bottom: none; padding: 5px 0 0; font-size: 0.44rem; letter-spacing: 0.06em; }
  .gates li.on { border-top-color: var(--hue); }
  .gates li .g-name { display: none; }
  .sp-name { font-size: clamp(1.7rem, 9vw, 2.6rem); }
  .sp-units { display: none; }
  .stages-coord, .coord { font-size: 0.5rem; }

  /* act 4's own stack is annot.css's: the console's rails, which pane it drops and
     what the controls rail is capped at are one decision measured together there. */
  .pane { padding: 11px; gap: 7px; }

  .system-head { grid-template-columns: 1fr; }
  
  
  
  
  
  
  
  
  
  /* P6 — ONE LINE, NOT THREE, AND THE REASON IS ARITHMETIC. The phone lattice is
     `repeat(2, minmax(0, 1fr))` by six rows, so each cell gets the stage's height / 6 — 94px at
     390x844, 84.7px at 390x780 — and after the kicker, the title, the figure, the padding and the
     gaps the body is left 11-30px against a 15.23px line box. A `-webkit-line-clamp: 3` on a box
     that holds one line does not draw its ellipsis inside the box: the reader gets the first line
     sliced through the middle and the next word broken off, which is what the screenshots at
     390x844 show ("...0 errors. No", "Earned on positions the child"). Clamping to the line the row
     actually gives puts the ellipsis back on the page. Measured with `node .audit5/clamped.cjs 390
     844 5 0.5 'p.c-b'`, which reports the box, the line-height, the clamp and whether the ellipsis
     lands inside: six of the eight cells were CUT WITHOUT ELLIPSIS before and ZERO are after,
     measured five times across both review attempts.
     What is NOT fixed here, and it is a content decision rather than arithmetic: below about 665 of
     height at every phone width, and at 320-375 wide, the row holds ZERO lines - at 390x664 six of
     the eight boxes are 10.3-10.6px against a 15.23px line - so no clamp value reaches them. That
     band is PASS2's P25, with the reviewer's ruling (hide the bodies there, and do it with P17,
     whose 601-900 short band is the same rows failing the same way). Note what is NOT in it:
     320x844, 360x844 and 360x915 all measure 0 cut, so this is not "320-360 wide at every
     height". */
  
  
  
  
  
  
  
  
  
  .promo { max-width: none; }
  /* 0.42rem was 6.72px - the smallest type on the site, and below legibility whatever
     colour it is. The block keeps the base 0.56rem (8.96px) on a phone too; measured, it
     grows the block from 85px to 113px and the pad re-centres with nothing escaping.
     Size and colour together take this block from 1.95:1 to 4.05:1 at 390x844 and clear
     AA at every wider window - but not here, because at 390 the promotion camera puts the
     board's lit near edge directly under the credits (backdrop relative luminance median
     0.0542, max 0.1183, against 0.0017 at 1440). The licences require these attributions
     to be perceivable, so the phone gets a ground of its own: a soft vertical wash, the
     same device `#nav::before` (acts.css:35-42) already uses for chrome over the lit
     board. It reaches the gutters so it has no vertical edge to show, and it fades at top
     and bottom so it has no horizontal one. NOT the GL wash at world.js:250-251: that is
     one shader shared by all eight acts and its colour is mix(uDark, uPaper, uLift), so
     strengthening its bottom band would put an ivory band under act 6's session plate and
     act 7's card grid (findings.md:601; both act numbers are the post-2026-09-05 ones). */
  .foot { position: relative; }
  .foot::before {
    content: ''; position: absolute; z-index: -1; pointer-events: none;
    inset: -14px calc(-1 * var(--gut)) -18px;
    background: linear-gradient(180deg, rgb(4 4 8 / 0), rgb(4 4 8 / 0.78) 26%,
      rgb(4 4 8 / 0.78) 74%, rgb(4 4 8 / 0));
  }
}

/* At 320px the header action wraps. Start the eyebrow beneath its second line. */
@media (max-width: 350px) {
  .hero .kicker { top: 84px; font-size: 0.68rem; letter-spacing: 0.1em; }
}

/* Short windows: a 1440x620 browser is common and the acts were laid out against
   height, not against the space actually left over. */
/* Act 6 on a phone that is not tall. `.system-head .lead` is the one piece of copy this
   tier spends - 68px of the head, and one element rather than a body per card - and it is
   the same trade the block made when act 6 was an eight-card bento. What replaced the
   bento's own clamped card bodies is act 6's height ladder in system.css, measured there
   across 26 windows at both of the act's crowded fractions. */
@media (max-width: 900px) and (max-height: 870px) {
  .system-head .lead { display: none; }
  
  /* b-checkpoint's dash graphic, which both neighbouring tiers already hide (the <=700 block
     below and the shared 820 tier); its count is in the card's own title, "Ten gates, one per
     level.", so this costs no words and buys that card its line back. */
  
}

@media (max-height: 820px) and (min-width: 901px) {
  .pad { padding-top: clamp(64px, 9vh, 92px); padding-bottom: clamp(14px, 2.4vh, 26px); }
  .d1 { font-size: clamp(2.6rem, 7.6vw, 7rem); }
  .d2 { font-size: clamp(2rem, 5.4vw, 5rem); }
  .d3 { font-size: clamp(1.3rem, 2.4vw, 2.1rem); }
  .system-head { margin-bottom: 2px; }
  .system-head .lead { display: none; }
  .chaos .d2 { font-size: clamp(1.55rem, 3.2vw, 2.7rem); }
  .chaos .lead-swap { min-height: 0; }
  .chaos .lead { font-size: 0.86rem; line-height: 1.4;
    display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; }
  .cmp-card { padding: 7px 9px; gap: 4px; }
  .cmp-method { display: none; }
  .beats, .segs { height: 1.9em; }
  .cmp-verdict { font-size: 0.7rem; }
  .cmp-punch { font-size: 0.8rem; padding-top: 4px; }
  .cmp-label { font-size: 0.54rem; padding-top: 4px; }
  .cmp-who b { font-size: clamp(0.86rem, 1.1vw, 1.1rem); }
  .stages-head .stage-sub { display: none; }
  .gates { top: auto; bottom: clamp(16px, 3vh, 30px); }
  .stage-panels { bottom: clamp(16px, 3vh, 30px); }
  .sp-units, .sp-theme { display: none; }
  .pane { gap: 6px; padding: 10px 12px; }
  
  
  

  
  
  
  
  
  
  .ticker { gap: 6px 24px; }
  /* `max(8.6vw, 17vh)`: on width alone the headline stopped growing while its 1fr row kept
     the slack, so at 1203x564 three lines of 79px sat in a 324px row and left 95px of dead
     box under the last line. Measured with the height term: 95 -> 2px at 1203x564, 91 -> 1
     at 1100x540, 193 -> 55 at 981x620, 89 -> 24 at 1440x620, and stage overflow stays 0,
     the lead stays unclamped and the base stays inside the fold at all of them. */
  .hero .d1 { font-size: clamp(2.4rem, max(7.9vw, 16vh), 6.4rem); }
  /* No measure here on purpose. 42ch resolves to 349.65px inside a .hero-foot that
     is 452.09px wide, so the sentence wrapped to five lines and the clamp below cut
     the fifth - which is where "same way." lived. Deleting the declaration is not
     the fix: base.css:117's .lead { max-width: 34ch } then wins and the measure gets
     narrower still (283.05px, six lines, 41px cut). It has to be none. */
  .hero .lead { font-size: 0.9rem; line-height: 1.42; max-width: none;
    display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
  .hero-foot { gap: 12px; }
  .hero-rule { display: none; }
  .coord { display: none; }
}

/* ------------------------------------------------------- the band above the 820 tier
   A five-branch block used to live here, from 821px of height up to a per-width ceiling,
   because the bento's 8x10 lattice took its row height from the viewport while the cards'
   content did not and only the 820 tier reconciled them. Act 6 is not a lattice any more:
   its own height ladder is in system.css, keyed on height alone, and it was swept at both
   of the act's crowded fractions across 26 windows. The block and its 100 lines of
   boundary archaeology are in git history at 323600e if the shape is ever needed again.
   The hero's own residual in the same band is the next block, and it is unaffected. */


/* --------------------------------------------- P8 - the hero, in the same band above the tier
   B1 bounded the headline with `min(clamp(3rem, 9.9vw, 11rem), 17vh)` (acts.css:134) and that
   cleared this band from about 870px of height up. Below that a residual stayed, and it is no
   longer the headline's to answer: above `w = h x 17/9.9 = 1.717h` the `17vh` term is the live
   one, so the headline has stopped shrinking with width, while `.hero .lead`'s
   `clamp(1.02rem, 1.34vw, 1.32rem)` keeps growing to its 21.12px ceiling at 1576 wide. Measured
   at rest, stage over / lowest ink past the fold: 1600x821 22/+21.55 (identical at 1920, 2560 and
   3440), 1600x830 18/+17.52, 1500x821 12/+12.27, 1440x821 4/+4.31, 1440x830 0/+0.28,
   1600x865 2/+1.86. Clear from 870 by 0.39px, and from 820 down by 19.67px where the shared tier
   fires.

   The fix is B2's, one pixel higher up. `.hero .lead` already carries `max-width: none` inside the
   `(max-height: 820px) and (min-width: 901px)` tier below, where B2's reviewer ruled it a
   component fix because it introduces no number of its own: it hands the paragraph the measure its
   own container already declares, `.hero-foot`'s 46ch, which the `.cta-row` directly beneath it
   already fills. The same declaration here takes the lead from 415.13px to 452.09px, which is six
   line boxes to five, and the 31.69px that frees is more than the 21.55px the worst corner is
   over. Measured at 1600x821: 0 over and 10.14px of clearance, line lengths 43/54/44/51/40/9
   characters becoming 49/58/38/52/45 - 58 against the 75 the same declaration produces below 820 -
   and the lead's right edge landing on 522.48, which is the `.cta-row`'s own. No word, figure,
   type size, leading or spacing value moves, and nothing is hidden: the closing rule
   `scroll - the board is the page` stays on screen, which is what B1 bought.

   The ceiling is 880 - 15px above the measured 865 edge, the margin this file's other band tiers
   carry, and short of 900 on purpose, because findings.md:37 names 1440x900 as the reference
   window a loose trigger must not reach and both 1920x900 and 1440x900 are gate windows. The floor
   is 1400, 10px below the measured 1410 edge, which is `1.717 x 821` and therefore the narrowest
   window in the entire band that can fail. Outside those four edges every number is byte-identical
   to before: this tier only ever removes height. */
@media (min-width: 1400px) and (min-height: 821px) and (max-height: 880px) {
  .hero .lead { max-width: none; }
}

/* Very short windows: the console keeps the lesson and loses the after-class
   material, which is the only part of it nobody needs mid-lesson. */
@media (max-width: 901px) and (max-height: 700px) {
  /* A pinned hero cannot grow with a short viewport. Keep the message and
     actions visible; the numbers and founder link remain elsewhere on the site. */
  .hero .ticker, .hero-me { display: none; }
  .cs-chain { display: none; }
  
}

/* Leave a clear footer margin at the short desktop height shown by the owner.
   At 1216×565 the 820px tier's 16vh headline pushed the hero actions and final
   ticker labels against the stage edge. This keeps the type large while giving
   the complete second row room to breathe. */
@media (max-height: 640px) and (min-width: 901px) {
  .hero .d1 { font-size: clamp(2.4rem, max(7.2vw, 14vh), 5.6rem); }
}

/* The chess-file ladder becomes a row of tiny, cryptic targets on touch widths and
   runs against the hero copy. The story itself remains scrollable, and its
   visible calls to action still navigate to the curriculum and free sample. */
@media (max-width: 900px) {
  #ladder { display: none; }
  .hero .d1, .hero .lead {
    text-shadow: 0 2px 10px rgb(0 0 0 / 0.95), 0 0 24px rgb(0 0 0 / 0.8);
  }
}

@media (max-height: 640px) and (min-width: 901px) {
  .pane { padding: clamp(8px, 0.9vw, 13px); gap: 5px; }
}

@media (prefers-reduced-motion: reduce) {
   .stage-panel { transition: none; }
  .console { transform: none; }
  /* the plate keeps its bevel and its pointer-lit face — both are static states —
     and loses the 2px travel and the eased crossfade between them. */
  .cta { transition: none; }
  .cta:hover, .cta:active { transform: none; }
  /* The panning light and the press wave are motion with no still state to keep,
     so they go entirely — the highlight parks at 50%, where it shipped. The
     secondary keeps the SWAP — the arrow is information, and the label is still
     replaced by it — and loses the travel: both layers sit where they land, so a
     pointer changes the chip in one step instead of sliding it. */
  .cta { animation: none; }
  .cta-rip { display: none; }
  .cta-l, .cta-go { transition: none; transform: none; }
  .cta:hover .cta-l { transform: none; }
}

/* ------------------------------------------------- act 6's card bodies, the bands no clamp
   could reach. Three banded branches used to close this file, hiding the eight bento card
   bodies at the phone heights where the lattice row held ZERO lines - 390x664, 320x800,
   320x871 and their neighbours, every edge walked a pixel at a time. Act 6 has no lattice
   and no card bodies now: the six glosses are hidden outright below 900px wide, where the
   plate above them says the same things in full, so there is no band left for a clamp to
   fail in. The three branches and their measured grid are in git history at 323600e. */
