/* nukernel/nu.css — THE ONE STYLESHEET.
 *
 * REPAINTED IN ENAMEL, 2026-08-27 (Paul: "bolder... more primary colors and
 * chunkier. It looks like a funeral program"; the design is
 * nukernel/ideal/design-system.html and its name is Enamel). The repaint is a
 * PAINT wave and only a paint wave: every class name, every reserved height,
 * every sticky mechanism, every pane and every measurement below survives —
 * what changed is the tokens at the root and the faces of the seven kinds.
 * Where a visual decision is reversed, its comment is rewritten in place with
 * the date, per this file's own law. The four paints are SEMANTIC, never
 * decorative: hand #1E45E0 (you set it), clock #E5330E (it is happening now),
 * meter #0B9B4E (the engine measured it), flag #FFC61A (armed/hover). A thing
 * is coloured for one of those four reasons or it is ink on the chassis.
 *
 * ...AND SINCE 2026-09-02 THERE ARE TWO MORE FAMILIES, WHICH SAY A DIFFERENT
 * KIND OF THING. Paul, 2026-09-01: *"The design system is not consistent. It
 * uses very little color, and things are uneven based on how text wraps."*
 * The audit that sentence produced counted the four paints in use and found
 * three of them nearly inert — `--clock` five times, `--meter` four,
 * `--meter-tint` zero — while `--hand` did thirty-eight jobs, because all four
 * paints are STATES and the page kept needing to say something that is not a
 * state. Two things, in fact:
 *
 *   CATEGORY — WHICH PLAYER. `--v0 --v1 --v2 --v3 --vb --drum`. Six hues, one
 *   per voice of the band, so one player reads as one colour on every surface
 *   it appears on (the score's motif caps, the roll legend, a nav member box,
 *   a strip head). This is an IDENTITY: it never changes when you press
 *   something.
 *   LEVEL — HOW MUCH. `--q1..--q4`, four steps of one hue, for a velocity, a
 *   weight, a step's loudness. This is a QUANTITY: it changes when the number
 *   does and never when the page does.
 *
 * WHY NEITHER CAN BE MISTAKEN FOR THE FOUR, and it is a rule about SHAPE and
 * not about hue, because hue alone would not carry it (`--v0` is a sky blue
 * beside the hand's cobalt and `--v1` a mid green beside the meter's — near
 * neighbours, named here rather than pretended away):
 *   1. A CATEGORY HUE ALWAYS HAS A WORD BESIDE IT — a legend chip beside the
 *      voice's name, a cap with the motif's name typed in it, a strip head
 *      over the player's name. The four paints never carry a word; they are
 *      states, and a state is read off the control's own face. A hue with a
 *      name beside it is a WHO; a hue on a bare control is a WHAT.
 *   2. A LEVEL HUE IS ALWAYS A FRACTION OF A BOX — a fill whose WIDTH is the
 *      value, inside an ink-bordered well. Nothing on this page paints a
 *      partial box with a state.
 *   3. Neither family ever paints a border, a focus ring, a cap, a hover or a
 *      `<mark>`. Those five belong to the four paints exclusively, which is
 *      what makes the division checkable in review.
 * Both families fall to neutral under `prefers-contrast: more` and
 * `forced-colors: active`, by the same one-property mechanism the section wash
 * uses — see `--wash`.
 *
 * NINE TABS, 2026-08-27 (Paul: "Why don't we make tabs at the top level and
 * let go of the idea of scrolling everything?"). The page is nine panels, one
 * on screen at a time, and the two sticky bands below are the TRANSPORT and
 * the TAB ROW — the second band used to be the heading of the axis you had
 * scrolled into, and that rule is rewritten in place where it stood (search
 * THE SECOND BAND IS THE TAB ROW). The four things this file says are
 * unchanged; what changed is which element band two is.
 *
 * ONE STYLESHEET, AND ONLY ONE (Paul, 2026-08-24: "keep the raw plain HTML but
 * use more controls and a little bit of CSS. use more grid lines in tables, it
 * will help"). This supersedes 2026-08-23's "no CSS or styling", which was the
 * right instruction for a page that had never been read on a phone: measured at
 * 375px on 2026-08-24 the document scrolled 15px sideways, every button was
 * 21px tall, every checkbox 13px, and there were 96 radios on the default
 * record. This file says four things and no fifth — tap targets, two sticky
 * bands, per-table scroll panes, grid lines — plus the box (never the inside)
 * of the class vocabulary PROGRAM.md §2.4 fixes. The page still reads top to
 * bottom with CSS off; that is the promise the reversal is allowed to make.
 *
 * ONE OWNER. Anything a slice needs that is not here is ADDED HERE, not
 * declared in a second stylesheet. band.css, hw.css and drums.css are on disk
 * and unlinked: they belong to pages that are not this one, and mixing another
 * page's state vocabulary into the shell sheet is the opposite of keep-like-
 * with-like. Exactly two of band.css's rules were general and both are carried
 * below with attribution (band.css:14 `font: inherit`, band.css:60-61 the clip).
 *
 * THREE FAILURE MODES, NAMED SO NOBODY REDISCOVERS THEM:
 *   1. NEVER write `overflow-x: hidden` on `body` or `#app`. A sticky element
 *      does not stick inside a scroll container, so that one line kills both
 *      bands silently. The sideways scroll is fixed AT THE TABLE, never at the
 *      body — that is what `.nu-pane` is for.
 *   2. A sticky `<tr>` does nothing. Rows cannot be sticky; cells can. Every
 *      sticky rule below is on a `th` or a `td`.
 *   3. `--bar-h` is a PROMISE: `.nu-tabs { top: var(--bar-h) }` assumes the
 *      bar is exactly that tall, and a control in `.nu-bar` that wraps opens a
 *      gap under the tab row. test/shell.js pins the number. (It said "a THIRD
 *      control"; the bar holds four since 2026-08-27 and the promise is
 *      unchanged — the arithmetic that keeps it is written at `.nu-bar`. It
 *      also said `.nu-ax > h2 { top: var(--bar-h) }`, which was the promise's
 *      creditor until the tabs landed the same day; the creditor moved, the
 *      promise did not.)
 *
 * TWO MODERN FEATURES, both load-bearing only for niceties. `color-mix()` and
 * `:has()` ship in Safari 16.4+ / Chrome 105+ / Firefox 121+. If `:has()` fails
 * an option loses its chosen-border and a disabled one loses its grey — the
 * `is-on` / `is-off` classes sheets.js also writes cover it. If `color-mix()`
 * fails, borders fall back to `currentColor`, which is legible and wrong-ish.
 */

/* ===== THE DESIGN SYSTEM ================================================
   Paul, 2026-08-26: *"Create a simple design system and use plain HTML buttons
   where appropriate but bring more consistency."*

   THE AUDIT THAT PRODUCED THIS, run on the RENDERED page (chromium at 1280x900
   and 390x844, every tab of both strips clicked, computed styles read back —
   not read off the source). 292 interactive elements, and the kinds actually in
   use with the number of DISTINCT COMPUTED LOOKS each one had before this
   section existed:

     input[range]     103   4 looks   a quantity
     input[radio]      48   1 look    a step in a grid
     input[radio]      24   2 looks   clipped under a chip on the ring
     button            41   4 looks   an action
     button            23   2 looks   a tab
     select            39   2 looks   a settled choice
     input[checkbox]   13   2 looks   a switch
     select[multiple]   1   1 look    an open choice among many
     a[href]            0   -         nothing on this page left it

   ...plus the 24 `<label class="nu-opt">` that ARE the widget for the radios
   clipped inside them, in 4 looks of their own.

   THE INTENDED LOOKS ARE THE ONES ARGUED SOMEWHERE IN THIS FILE; THE REST WERE
   ACCIDENTS, AND THERE WERE FOUR. Three were visible in the audit itself:
   a `<button>` inside a `<th>` came out BOLD, because `button { font: inherit }`
   at the top of this file inherits the table header's weight — so eight of the
   twenty-three tabs were a different button from the other fifteen for no
   reason anybody chose; a row of buttons was spaced three different ways
   (a literal " " text node in `#tabs` and `#motif-tabs`, `margin-inline-end:
   .4rem` in `#atlasActs`, and nothing at all in the producer's rows); and
   `.is-now` — the page's one spelling of "inverted" — had NO writer anywhere in
   nukernel/ while `.nu-circ .nu-opt.is-on` re-declared its two lines by hand.
   Each is fixed below, at the one place that defines it. A fourth turned up
   while the fixes were being measured: `#atlasActs button` restated the 44px
   tap floor the top of this file had already given every button, and carried
   the margin that was accident 2 — one rule doing two things that were both
   already done. It is gone.

   AND HERE IS THE SAME PAGE AFTERWARDS, measured the same way at both widths:

     an action    1 look    the word button. (This read "2 looks" while the
                            three transform rows wore <svg> pictures with their
                            padding cut to 2px to fit; the pictures became
                            glyphs on 2026-08-26 and the exception went with
                            them — `.nu-tf-row` set a 44px MINIMUM width
                            and a .3em gap, neither of which is a second LOOK:
                            one is the tap floor and one is spacing; the rows
                            themselves became gutter levels on 2026-08-28 and
                            the selector's tombstone is at the `.nu-tf` block.)
     a tab        1 look    was 2 — the `<th>` bold is gone
     everything else unchanged, and every remaining second look is a STATE the
     browser draws (`:disabled` greys a control's border and face) or the flex
     `min-height: auto` a control gets for being a flex item, which is the same
     26x26 box by another name.

   So: seven kinds, and every kind is one look plus its states.

   SEVEN KINDS OF CONTROL AND THAT IS ALL THE PAGE NEEDS. Nothing here is a new
   widget: this is the list of what already exists, reduced, with what each one
   is FOR — which is the part that was missing.

     1  AN ACTION            `<button type="button">`, the browser's own chrome.
                             It does something now: play, rewrite, take, + line,
                             fork, take it off, forget all of it. 44px floor.
     2  A TAB                the SAME `<button>`, plus `aria-pressed`, plus the
                             current one wearing `<mark>`. A tab is not a
                             different widget from an action and must not look
                             like one; what says "you are here" is the mark and
                             the ARIA state, never a second look.
     3  A SETTLED CHOICE     `<select>` inside `.nu-sel` — one value, decided
                             once, nobody browses it. Meter, key's mode, a
                             voice's instrument.
     4  AN OPEN CHOICE       `<select multiple>` inside `.nu-sheet`, or — where
                             the SHAPE of the field is the point and a list
                             would destroy it — `.nu-opt` chips over clipped
                             radios. On the shipped record that second form is
                             the circle of fifths and nothing else (measured: 24
                             chips, all of them on the ring), but ui/sheets.js
                             still builds one for any single-choice sheet that
                             asks, so the rule below is not a rule for one
                             widget.
     5  A SWITCH             `<input type="checkbox">` at `--box`, its sentence
                             in the `<label>` around it.
     6  A QUANTITY           `<input type="range">` with an `<output>` beside
                             it. 44px in a row of its own; `--cell`-high inside
                             a step grid, where the grid is the tap target.
     7  A STEP               `<input type="radio">`/`checkbox` at `--box` in a
                             `.nu-grid` cell — one of sixteen, so WCAG 2.2's
                             24px dense-grid floor applies and not `--tap`.

   ...AND ONE THING THAT IS NOT A CONTROL AT ALL: `<a href>`, which until
   2026-08-26 the page had none of. A link is the only element here that LEAVES
   the page, so it is the only one that gets an underline (`.nu-wiki`, below).

   FOUR STATES, AND ANY KIND MAY WEAR THEM. They are adjectives, not widgets:
     `.is-on` / `:checked`     chosen — a 2px ink border, or the inversion
     `.is-off` / `:disabled`   refused — greyed, NEVER hidden, reason printed
     `.is-quiet`               inert — dimmed and italic (knobs.js `quiet`)
     `<mark>` / `.is-now`      THE ONE — the browser's own highlight. Reserved:
                               the sounding step, and the tab you are on. It may
                               not be borrowed for anything else, which is why
                               `.nu-here` is a rule down the row and not a tint.

   THE SECTION WASH BELOW IS NOT A STATE and may never read as one — see
   "A COLOUR PER SECTION". It moves the GROUND a control stands on, never the
   control.

   HOW TO ADD SOMETHING. If it is one of the seven, use the seven. If it is not,
   the burden is to say which of the seven it could have been and why that was
   worse, HERE, in prose — the way every other decision in this file is written
   down. A slice may not carry its own look; there is no second stylesheet.
   PROGRAM.md §2.4 carries the same table and the two must not drift.
 */

/* ===== TAP TARGETS =====================================================
   Nothing on this page cleared a thumb. 44px is the isolated-control floor
   (WCAG 2.2 AAA / the Apple figure) and every isolated control now has it;
   26px is the native widget, because a checkbox does NOT answer to
   `min-height` — it answers to `inline-size`/`block-size`, which resize the
   widget itself. `transform: scale()` was the other candidate and it is a
   trap: it leaves the layout box at 13px, so neighbours overlap the visual
   and the hit area stays 13px. */
/* ===== THE TOKENS MOVED OUT, 2026-09-07 (docs/DESIGN-SYSTEM.md step 1) ====
   Paul: *"Create a design system with all the elements we use."* A `:root`
   block of five hundred lines standing at the head of a nine-thousand-line
   stylesheet is a design system nobody can see; `nukernel/tokens.css` is that
   block, alone in a file, with every token argued on its own line, and it is
   linked ahead of this one in index.html.

   WHAT WENT: every custom property this file used to declare — the palette,
   the type scale, the four weights, the four radii, the five spacing steps,
   the two border weights, the tap floors, the slider anatomy, the two fixed
   bands, and the page gutter. Nothing was renamed and nothing changed shape,
   so every one of the rules below reads exactly the token it always read;
   what changed is where the token is DECLARED and what colour it resolves to.
   The palette itself is the Stellate's now — a dark deck, printed legends,
   phosphor values, three lamps — and tokens.css carries that argument.

   WHAT STAYED: this file's rules, all of them, and the prose that explains a
   RULE. Prose that explained a TOKEN moved with the token. The long-form
   history of each one (the eighteen-quanta spacing audit, the twenty-two
   font-sizes, Enamel's retuning, the gutter's rise and deletion) is in
   tokens.css in short form and in git in full.

   THE LAW THIS LEAVES BEHIND: nothing below this line may declare a custom
   property at `:root`, and no rule may type a colour. Both are gated by
   test/design-system.js. */
*,*::before,*::after{ box-sizing: border-box; }
/* ---- NOTHING RUBBER-BANDS (2026-09-06, TABLE.md §15a) ----------------
   Paul: *"In the song section area I can drag it too far right and then the
   whole thing moves including the fixed parts. So it all feels reel wobbly."*
   MEASURED at 390 on Kingston 1969 before this rule: the pane is
   clientWidth 364 / scrollWidth 625, `scrollLeft` clamps at 261, there is no
   dead space to the right of the table, and the frozen section column's left
   edge is 16px at rest and 16px at maximum scroll. The page does not scroll
   sideways (documentElement scrollWidth 390 = clientWidth). So there is no
   layout fault under it: what a hand feels is the OVERSCROLL BOUNCE.
   CHAINING IS NOT BOUNCING, and that distinction is the whole fix. `contain`
   — which `.nu-pane` already carried — stops the gesture CHAINING to the
   scroller behind it; it still lets the scroller rubber-band inside ITSELF at
   its own end, and a rubber-band translates the whole scroller, `position:
   sticky` children included. That is exactly *"the whole thing moves including
   the fixed parts"*. `none` is `contain` plus no bounce.
   AND IT IS ON THE DOCUMENT TOO, which has a second gain worth saying out
   loud rather than hiding as a side effect: `overscroll-behavior-y: none` on
   the page turns OFF pull-to-refresh, so a downward flick at the top of the
   sheet can no longer reload the box in the middle of an edit. */
html, body{ overscroll-behavior: none; }
html{ -webkit-text-size-adjust: 100%; }
body{ margin: 0; background: var(--paper); color: var(--ink);
  /* THE TYPE IS ENAMEL'S NOW (2026-08-27): one heavy system sans for
     everything said, weights with real weight — 900 display / 800 blocks /
     700 labels / 500 body — and no serif anywhere ("It looks like a funeral
     program"). Until today the page rode the browser's default face, which on
     most machines IS a serif. 1.45 and not the specimen page's 1.5: measured,
     1.5 grew the form table's header row enough to spend the geometry budget
     this repaint is allowed (8px) on leading alone. */
  font: 500 16px/1.45 var(--sans);
  /* THE GUTTER COMES OUT OF THE PAGE, AND THIS DECLARATION IS THE WHOLE LAW
     (2026-08-28). Paul: *"Dont let anything go under it."* Everything on this
     page is a child of <body>, so narrowing <body>'s own column by the
     gutter's width re-flows EVERY child inside it — there is no floating
     stripe over a full-width page and no z-index deciding what wins.
     AND THE FULL-BLEED BLOCKS LAND EXACTLY ON ITS EDGE, for free. Six rules
     in this file cancel the page gutter with `margin-inline-end:
     calc(-1 * var(--gr))` and re-pay it as padding (`.nu-bar`, `.nu-ax`,
     `#atlas`, `#atlasWhen`, and the two below them). Their right edge is
     therefore `100vw - --gr - --tray-w + --gr` = `100vw - --tray-w`, which IS
     the gutter's left edge — they bleed up to the rule and stop at it. Not
     one of those six rules had to move, which is the test of whether the law
     was written in the right place.
     MEASURED on the rendered page, 2026-08-28, on all nine tabs at both
     widths, with zero console errors:
       390x844   gutter 56.00px, its left edge at x=334. The content column is
                 310px (it was 366): 390 - `--gl` 12 - `--gr` 12 - 56. An
                 ordinary block's right edge is 322, a full-bleed block's is
                 334 — the rule itself, to the pixel, never past it.
       1280x800  gutter 56.00px, left edge at x=1224. Column 1200 (was 1256);
                 ordinary right edge 1212, full-bleed 1224.
     `documentElement.scrollWidth === clientWidth` at both (390/390, 1280/1280)
     and the greatest right edge of any laid-out block on any of the nine tabs
     is exactly the gutter's left edge. The one apparent exception is not one:
     the globe's land path and abcjs's engraving are geometry inside an SVG
     viewport, and the pane's table inside an `overflow: auto` box — their
     boxes run past their clipper on purpose and are not painted there. What
     has to be inside the gutter is the CLIPPER, and it is. */
  padding-inline: var(--gl) var(--gr);
  /* ...AND SINCE 2026-09-09 THE CHROME IS AT THE FOOT AND NOT AT THE SIDE. The
     law is unchanged and so is where it is written — narrowing <body>'s own
     box re-flows EVERY child inside it, so there is no floating chrome over a
     full-width page and no z-index deciding what wins — but the chrome is a
     BAR now, so what <body> gives up is a strip of HEIGHT at the bottom.
     Paul's *"Dont let anything go under it"* is the same sentence about the
     same law, turned ninety degrees, and `--bar-h` already carries the safe
     area so this may not add it a second time.
     THE SIX FULL-BLEED RULES DID NOT HAVE TO MOVE, again: they cancel the page
     gutter with `margin-inline-end: calc(-1 * var(--gr))` and re-pay it as
     padding, so their right edge is `100vw` — the viewport's own edge, because
     there is nothing standing at it any more. */
  /* ...AND THE GAP ABOVE THE BAR IS `--s2` SINCE 2026-09-05 (TABLE.md §13a.6
     and its budget: the pane at rest must be at least `844 - bar - safe - 8`
     at 390 x 844). MEASURED with `--s3`: the wrap came to 772px against a
     785.6px floor, and the 13.6 it was short is exactly this gap plus the
     `.nu-pan` padding above the table. Both are spent here: the panel's own
     top padding goes to 0 on the band (`#pan-band` below), and the strip
     under the wrap is one step tighter. Nothing else on the page moves by
     more than 3.2px. */
  padding-block-end: calc(var(--bar-h) + var(--s2));
  /* ...AND THERE IS NO TOP STRIP TO RESERVE SINCE 2026-09-05 (TABLE.md §13a.1:
     *"Nothing is fixed but the bottom bar."*). `--top-h` reserved 55.2px at the
     head of every panel for `.nu-top` — the ≡ and the ×, two fixed plates at
     the corner — and MEASURED at 390 x 844 that was 55.2px of a 844px phone
     spent on chrome that never moves, on a page whose grid had 611. The ≡ is
     the last button of `.nu-bar` now and the × belongs to the sheet it closes,
     so the reservation is 0 and the token survives only as the notch's own
     arithmetic for anything that still wants it.
     THE SAFE AREA STILL HAS TO BE PAID, and it is paid where it always was for
     the top of the page: `env(safe-area-inset-top)` on <body>, once. */
  /* ...AND THE TOP STRIP IS RESERVED AGAIN SINCE 2026-09-06 (docs/NAV.md:
     *"THE TOP IS WHERE YOU GO. THE BOTTOM IS WHAT YOU HEAR."*). There are two
     fixed bands and only two, and <body> takes the room for both out of the
     page, which is the whole of *"Dont let anything go under it"* said twice.
     `--top-h` carries `env(safe-area-inset-top)` inside it — the strip pays
     the notch as its own padding — so this line replaces the bare `env()` it
     read from 2026-09-05 to 2026-09-06 rather than adding to it.
     WHAT IT COSTS, SAID AND NOT HIDDEN: 44px of a 844px phone, which is one
     section row of the grid at rest (TABLE.md §16 carries the arithmetic and
     the trade Paul made when he asked for the strip). */
  padding-block-start: var(--top-h); }
/* the display weights. (The h1 rule — "the record's name is the h1 and gets
   the 900; font-weight: 900; letter-spacing: -.01em; line-height: 1.05" —
   went on 2026-08-29 with the heading it dressed: Paul, "Get rid of the play
   buttons and the title of the song"; the record names the page through
   document.title now and no view builds an <h1>. Measured after the cut: the
   selector matched zero elements across all nine tabs.) */
h2, h3{ font-weight: var(--fw-block); }
strong, b{ font-weight: var(--fw-block); }
/* everything counted is mono — the machine voice (design-system.html "type") */
/* (`, .nu-vel-out` rode this selector, and `.nu-grid td > output.nu-vel-out{
   opacity: .75 }` stood in the step-grid block — the sliders' velocity
   readout from the 2026-08-25 rotation. The class lost its last builder when
   the velocity column became the weight BAR whose cap prints its own number
   (ui/eight.js benchVel), and the 2026-08-29 dead-selector pass measured it
   matching nothing on any tab. <output> alone still earns the mono: #reading,
   the faders' caps, every printed value.) */
output{ font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* index.html:5 carries `viewport-fit=cover` and band.css was never linked, so
   until today nothing paid the safe area: on a notched phone in landscape the
   left edge of the drum grid sat under the rounded corner. Do NOT remove
   `viewport-fit=cover` to fix that — removing it also removes the top inset the
   sticky bar needs. */

/* band.css:14, the one general rule that file had: a control that does not
   inherit the page's font is a control from a different page. */
button,input,select,textarea,output,meter{ font: inherit; color: inherit; }
button,select,input[type=number],input[type=text],input[type=search],input[type=range]{
  min-height: var(--tap); }
button,select{ padding-inline: var(--s3); }
/* A BUTTON LOOKS LIKE A BUTTON, EVERYWHERE — AND THE ENAMEL FACE IS WHAT
   SETTLES IT FOR GOOD (2026-08-27). What stood here was `th button, th select{
   font-weight: 400 }`, repairing the accident the audit of 2026-08-26 found:
   `font: inherit` handed 8 of the 23 tab buttons a <th>'s bold and the other
   15 stayed 400 — two looks decided by the furniture. That repair is RETIRED,
   not because it was wrong but because the cause is gone: the face below
   states the button's own weight (800, Enamel's block weight), so no button
   can inherit a weight from anything again. The measurement stays so nobody
   re-litigates it.

   THE FACE ITSELF is design-system.html's `.btn`: white plate, jet border,
   hard shadow, and pressing a control moves it INTO its shadow — the whole of
   Enamel's physicality in three declarations. The shadow is paint outside the
   layout box, so not one button's geometry moves.

   THE WEIGHT AND THE SIZE ARE THE TOKENS' NOW (2026-08-27, Paul: "make the
   button text smaller and not bold"). This said `font-weight: 800` and
   inherited the body's 16px; it says `--btn-fw` (500) and `--btn-fs` (14px),
   which is the ONE place the whole page's button text is decided — the
   argument the retired `th button { font-weight: 400 }` repair was making,
   finally made once instead of per-furniture. Measured after: 44px tall
   still, because the height is `min-height: var(--tap)` and never the type. */
button{ font-weight: var(--btn-fw); font-size: var(--btn-fs);
  color: var(--ink); background: var(--panel);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  box-shadow: var(--shadow); cursor: pointer; }
button:hover{ background: var(--flag-tint); }   /* the flag warns: about to matter */
button:active{ transform: translate(var(--press), var(--press)); box-shadow: none; }
/* refused: faint, never hidden — and it does not press, because there is
   nothing under it to press into */
button:disabled{ color: var(--faint); background: var(--well);
  border-color: var(--faint); box-shadow: none; cursor: default; transform: none; }
/* A BUTTON SEATED IN A TABLE CELL IS ALREADY FRAMED BY THE GRID, so it keeps
   the plate and the border and gives up the shadow: measured, 44px shadows on
   the form list's section numbers painted over the rule of the row below and
   the table read as smeared. The press still travels — it just has no shadow
   to fall into, so it is the border that says "down" via the darker plate. */
th button, td button{ box-shadow: none; }
th button:active, td button:active{ transform: none; background: var(--flag); }
/* THE CURRENT TAB IS A HAND-BLUE CAP — hand paint, because you chose it
   (design-system.html "tab row"). `aria-pressed` was already on every tab
   (the design system's kind 2: "what says 'you are here' is the mark and the
   ARIA state"), so the paint keys off the state that was already true and
   ui/eight.js does not move. The <mark> inside goes transparent here so the
   cap is one colour: on a cobalt cap the mark has nothing left to say that
   the cap is not already saying. */
button[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill); }
button[aria-pressed="true"]:hover{ background: var(--lamp-hot); }
button[aria-pressed="true"] mark{ background: transparent; color: inherit; }
/* THE MENU IS A PLATE TOO — the same jet border and 6px ease as a button, one
   weight down (700, the label weight): a settled choice is quieter than a
   verb. No shadow on a select anywhere: 39 of them sit in the board's tables
   and panes, and a shadow per menu made the desk read as forty loose parts
   (measured on the rendered board, 2026-08-27).

   THE DOM IS NOT FROZEN ANY MORE, AND THE ARROW IS OURS (2026-09-02). This
   paragraph said, from 2026-08-27 until today: *"The browser's own arrow
   survives because `appearance` is left alone — the design page's `.sel::after`
   arrow needs a wrapper element and the DOM is frozen this wave."* The wave is
   over. Paul, 2026-09-01: *"Things like select boxes are very plain and could
   be combo boxes."* `ui/selects.js selectField` wraps its `<select>` in a
   `<span class="nu-combo">` now — ONE call site, and it is the only one — so
   the triangle, the detent colours and the long-list filter are `.nu-combo`'s
   (see its block, below `.nu-sel`).

   THIS RULE STILL OWNS EVERY BARE SELECT, and there are 39 of them: a menu in
   a `<td>` (`selectEl`, which stays bare on purpose — a `<td>` caps it at 9em
   and a wrapper would fight the cell), a menu in a slot row, a menu on a bus
   plate. They keep the browser's own arrow, and that is not an inconsistency
   to be tidied later: a bare `<select>` in a dense grid IS a different control
   from a labelled one in a form, and the browser draws the dense one better
   than we would in 9em of column. */
select{ font-weight: var(--btn-fw); background: var(--panel);
  border: var(--bw) solid var(--ink); border-radius: var(--r1); cursor: pointer; }
select:hover{ background: var(--flag-tint); }
select:disabled{ color: var(--faint); background: var(--well);
  border-color: var(--faint); cursor: default; }
select option, select optgroup{ background: var(--panel); color: var(--ink); }
/* text/number entry: a cut, not a plate — you write into it */
input[type=number], input[type=text], input[type=search]{ background: var(--panel);
  border: var(--bw) solid var(--ink); border-radius: var(--r1); }
/* THE NATIVE WIDGETS TAKE THE HAND. `accent-color` is one line and it is the
   whole of "a value you set is cobalt" for every checkbox and radio on the
   page — 142 native controls painted semantically with no second widget. */
input[type=checkbox], input[type=radio]{ accent-color: var(--hand); }
/* ---- AND THE NATIVE RANGE GETS THE PAGE'S OWN SLIDER (2026-08-27) --------
   `input[type=range]` was in that list until today, and it is why the
   transport's room slider (and the year slider, and the tempo slider) read as
   a different species from the board: `accent-color` buys the BROWSER's
   widget in cobalt — a 4px pill with a round dot — sitting beside a rack full
   of welled faders with square caps. Paul, 2026-08-27: *"The horizontal
   sliders should have the same handles and vibes as the vertical sliders,
   just be horizontal."* The anatomy is `.nu-vs-track`'s and the numbers are
   the same three tokens; only the plumbing differs, because a native range
   has no children to paint and must be reached through its shadow parts.

   ONE RULE FOR EVERY RANGE ON THE PAGE, AND THAT IS DELIBERATE. Three of the
   ranges here are the hidden keyboard CHANNELS under the painted sliders
   (`.nu-vs-in`, `.nu-pbin`, `.nu-vain`) — `opacity: 0`, `pointer-events:
   none` — so dressing them costs nothing and cannot drift; the rest are the
   ones you see. Scoping this to `#vol` would have been a second place a
   slider's face is decided, which is the thing this file does not do.

   THE FILL IS THE ONE PART THAT NEEDED A HAND FROM OUTSIDE THIS FILE, and the
   reason is worth writing down before somebody tries again: WebKit and Blink
   expose no progress pseudo-element (Firefox's `::-moz-range-progress` is
   below and is free), and CSS cannot read a form control's value — no token,
   no `:has()`, no `attr()` puts paint to the left of the cap. So ui/eight.js
   publishes the percentage as `--p` beside the `setVol` it already ran: two
   lines, at the one place that knows. Where nothing publishes it `--p` falls
   back to 0 and the control is a well with a cap in it — still this page's
   slider, and not the browser's. */
/* ONE BLOCK FOR THE NATIVE RANGE, NOT TWO (2026-09-02). A SECOND full skin
   stood at the foot of this file — written 2026-08-27 with the atlas's own
   when-slider, unscoped, a 10px track and a 26px square thumb — and both were
   live at once. Measured which actually won, because source order is the
   answer people assume and it is the wrong one here:
   `input[type=range]:not([list])` is (0,2,1) against `input[type="range"]`'s
   (0,1,1), so THIS block won every contested declaration — the 20px trough,
   the 38x22 cap, the `--p` fill, the centred grip, the focus ring — and the
   later block only ever contributed what this one did not state. So the merge
   changes nothing this file had not already decided, and it removes
   the trap the map named: "reconcile to one before any token work, or a
   slider token will appear to do nothing."
   THE FIVE DECLARATIONS THAT CAME UP FROM THE SECOND BLOCK — `inline-size`,
   `block-size: var(--ctl)`, `margin: 0`, the pan-y touch contract (sideways
   writes the value, vertical is still the page) and the grab cursor, plus
   `:disabled` — ARE KEPT AT THE SECOND BLOCK'S OWN SPECIFICITY, on a plain
   `input[type=range]` selector two lines down, and that is not tidiness: those
   five are the ones three rules elsewhere on this page deliberately BEAT.
   `.nu-pbin, .nu-vain` (0,1,0 EACH — a comma is a list, not a sum; the 2026-09-03
   doubling below is what reading that pair as (0,2,0) cost) and `.nu-vs-in`
   state `block-size: 100%` on the
   invisible keyboard channels that lie over the painted bars, and
   `.nu-grid input[type=range]` (0,2,1) states 30px in a 36px cell. Written
   inside the `:not([list])` selector (0,2,1) instead, `block-size` would have
   started winning against two of them and silently resized three controls
   nobody was looking at. One block, two selectors, the cascade exactly as it
   was — which is what "reconcile" has to mean when the thing being reconciled
   is a cascade. */
input[type=range]:not([list]){ appearance: none; -webkit-appearance: none;
  background: transparent; }
input[type=range]{ inline-size: 100%; block-size: var(--ctl); margin: 0;
  touch-action: pan-y; cursor: grab; }
input[type=range]:active{ cursor: grabbing; }
input[type=range]:disabled{ cursor: default; opacity: .5; }
/* ...AND A HIDDEN CHANNEL STAYS HIDDEN WHEN IT IS REFUSED (2026-09-03).
   Paul, on a screenshot of the motif editor: *"Doubling like this in the
   motif editor."* Every rest and every hold row drew TWO handles — the
   painted cap and, a few pixels behind it, this block's own 38x22 cap on the
   native <input> — and the weight cell drew its number's plate twice.

   THE ARITHMETIC, WHICH IS THE WHOLE BUG. The three hidden keyboard channels
   say `opacity: 0` on a single class each — `.nu-pbin` / `.nu-vain`
   (nu.css §THE TWO LAID-DOWN SLIDERS) and `.nu-vs-in` — which is (0,1,0).
   `input[type=range]:disabled` one line up is (0,2,1): an element, an
   ATTRIBUTE selector (`[type=range]` counts in the class column, which is the
   half everybody drops) and a pseudo-class. It BEATS the channels, so the
   instant ui/eight.js `sync()` disables a step's channel ("don't let me set
   degree on held or rest notes") the channel faded UP from 0 to .5 and the
   browser's skinned range appeared on top of the bar it is meant to be
   invisible under. Measured on the rendered page before this rule: 32 of 34
   hidden channels at `opacity: .5`, all of them in the motif bench, all of
   them disabled; every other slider on the page (the board's faders, the
   chair sheets, the transport) at 0, because nothing else disables a channel.
   The comment at the head of this block called `.nu-pbin, .nu-vain` (0,2,0) —
   that is the two selectors of a LIST added together, not the specificity of
   either, and that miscount is why the collision was not seen when the two
   range blocks were merged on 2026-09-02. It reads (0,1,0) now.

   SO THE EXCEPTION IS STATED WHERE THE TRAP IS SET, not next to the channels:
   a reader of `:disabled` has to be able to see that the invisible channels
   are out of it. It is written as the SAME SHAPE as the rule it must beat
   plus the channel's own class — (0,3,1) against (0,2,1) — so it wins by
   construction and not by source order; a first attempt at
   `.nu-pbin:disabled` (0,2,0) LOST to the attribute selector and was measured
   losing on the rendered page. The `cursor` is left alone: these three are
   `pointer-events: none` and no cursor of theirs was ever on screen. */
input[type=range].nu-pbin:disabled,
input[type=range].nu-vain:disabled,
input[type=range].nu-vs-in:disabled{ opacity: 0; }
input[type=range]:not([list])::-webkit-slider-runnable-track{
  block-size: var(--sl-trough);
  background:
    linear-gradient(var(--hand), var(--hand)) left / var(--p, 0%) 100% no-repeat,
    var(--well);
  border: var(--bw) solid var(--ink); border-radius: var(--r1); }
input[type=range]:not([list])::-webkit-slider-thumb{ -webkit-appearance: none;
  inline-size: var(--sl-grab); block-size: var(--sl-cap);
  /* centre the cap across the well: half the difference the two tokens
     already state, plus the well's own border */
  margin-block-start: calc((var(--sl-trough) - var(--sl-cap)) / 2 - var(--bw));
  /* the grip runs ACROSS the travel, the way the standing fader's does
     (`.nu-vs-thumb::after`) — so on a laid-down slider it is a vertical rule,
     not a horizontal one: `--bw-hard` wide by 12px tall */
  background:
    linear-gradient(var(--ink), var(--ink)) 50% 50% / var(--bw-hard) 12px no-repeat,
    var(--panel);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  box-shadow: var(--shadow); }
input[type=range]:not([list])::-moz-range-track{ block-size: var(--sl-trough);
  background: var(--well);
  border: var(--bw) solid var(--ink); border-radius: var(--r1); }
input[type=range]:not([list])::-moz-range-progress{ block-size: var(--sl-trough);
  background: var(--hand); border-radius: var(--r1) 0 0 var(--r1); }
input[type=range]:not([list])::-moz-range-thumb{
  inline-size: var(--sl-grab); block-size: var(--sl-cap);
  /* the grip runs ACROSS the travel, the way the standing fader's does
     (`.nu-vs-thumb::after`) — so on a laid-down slider it is a vertical rule,
     not a horizontal one: `--bw-hard` wide by 12px tall */
  background:
    linear-gradient(var(--ink), var(--ink)) 50% 50% / var(--bw-hard) 12px no-repeat,
    var(--panel);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  box-shadow: var(--shadow); }
input[type=range]:not([list]):focus-visible::-webkit-slider-thumb{
  outline: var(--bw-hard) solid var(--hand); outline-offset: 1px; }
input[type=range]:not([list]):focus-visible::-moz-range-thumb{
  outline: var(--bw-hard) solid var(--hand); outline-offset: 1px; }
/* THE ONE RANGE THAT KEPT THE BROWSER'S WIDGET — AND IT IS GONE, 2026-08-29.
   Paul: *"Get rid of the time slider."*

   THE EXEMPTION IS RETIRED BY THE CONTROL LEAVING, which is one of the two
   ways its own note said it could end: "`#atlasYear` carries a `<datalist>` of
   85 ticks — every stop the atlas has a record at — and Chrome and Safari draw
   a datalist's ticks only on the widget they own: `appearance: none` deletes
   them, and no stylesheet can put 85 of them back. A PAINT WAVE DOES NOT GET
   TO DELETE A FACT TO WIN A FAMILY RESEMBLANCE. … the way to retire it is for
   ui/atlas.js to draw its own tick lane, at which point the `:not([list])`
   comes off and the year slider joins the family."

   WHAT ACTUALLY HAPPENED IS BETTER THAN EITHER BRANCH: the 85 ticks were the
   catalogue's own shape drawn as a comb, and the catalogue is now on the page
   as 199 named rows. The fact was not deleted to win a look; it was promoted
   from a comb to a list you can read. The `:not([list])` guards above stay —
   they cost nothing, and a future range with a datalist inherits the same
   honest exemption — and this rule stays with them for that range's sake. */
input[type=range][list]{ accent-color: var(--hand); }

/* A ROW OF BUTTONS, SPACED ONCE. The audit found three spellings of the same
   strip: a literal " " text node between the buttons (`#tabs`, `#motif-tabs`,
   the producer's rows), `margin-inline-end: .4rem` on the button (`#atlasActs`),
   and nothing at all. `gap` is the one that cannot drift, and it is on the
   CONTAINER, so a strip that gains a button gains its spacing for free.

   THE TEXT NODES STAY, and that is deliberate rather than leftover: a
   whitespace-only text run between flex items is not rendered as an item (CSS
   Flexbox §4), so it costs nothing here — and with this stylesheet OFF it is
   the only thing keeping "form" and "performance" from reading as one word.
   The page must read with the stylesheet off; that promise is what pays for
   the redundancy.

   `align-items: center` and not `baseline`: a tempo icon is 44px of picture
   with no text baseline in it, and beside a word-button baseline alignment
   drops it half a line. */
.nu-row{ display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2);
  margin-block: var(--s2); }
/* AND IT MADE THE STRIPS SHORTER, WHICH WAS NOT THE POINT BUT IS THE
   MEASUREMENT. Inline-block buttons pay for the whitespace text node AND for
   the baseline gap under each line; flex pays for neither. `#tabs` at 390px
   went from THREE lines to two and at 1280 from two to one; `#motif-tabs` and
   `#atlasActs` are unmoved. The strip is 5px taller at 390 (94 against 89)
   because two 44px rows plus one .4em gap is more than three squeezed
   line-boxes, and that is the right trade: the tap targets were always 44 and
   now the row admits it. */
input[type=number]{ padding-inline: var(--s2); inline-size: 5.5em; }
input[type=checkbox],input[type=radio]{
  inline-size: var(--box); block-size: var(--box);
  margin: 0 var(--s2) 0 0; vertical-align: middle; }
/* THIS IS WHAT TURNED 96 THIRTEEN-PIXEL RADIOS INTO 96 FULL ROWS without one
   line of ui/eight.js moving: every control on the page is already wrapped in
   a <label>, so giving the label the height gives the gesture the height.
   (This named `select()`, `check()` and `field()` and quoted their line
   numbers. Two of the three were deleted with the last <select> on 2026-08-24
   — the sheets do the wrapping now, ui/sheets.js emits `<label class="nu-opt">`
   around every option, and there are 544 of them on the shipped chant rather
   than 96. The rule did not have to move, which is the point the comment was
   making; the names it made it with were the ones that went.) */
label{ display: inline-block; min-block-size: var(--tap); line-height: var(--tap); }
#app p{ margin-block: var(--s2); }
:focus-visible{ outline: var(--bw-hard) solid var(--hand); outline-offset: 1px; }
/* (2026-08-27: `Highlight` -> the hand. Focus is the hand's own paint in
   Enamel — the ring says "your hand is here" — and the UA Highlight blue was
   the one control colour on the page nobody chose.) */

/* ===== A LINK, THE ONLY THING ON THIS PAGE THAT LEAVES IT =================
   Paul, 2026-08-26: "add actual Wikipedia links for each genre we have at the
   top by the title". `#title` is the record's name — "Rome 600" — and beside it
   sits the article the anchor IS (ui/eight.js `draw`, table in nukernel/wiki.js).

   AN UNDERLINE, WHICH NOTHING ELSE HERE HAS. The design system at the top of
   this file has seven kinds of control and a link is not one of them: every one
   of the seven acts on the record in front of you and this is the one thing
   that takes you off the page. That difference has to be visible, and the
   browser's own affordance for it is the underline, so the underline is left
   alone rather than restyled.

   SMALLER THAN THE NAME IT SITS BESIDE. `#title` is the h1 and the record's
   name is the heading; the article is a footnote to it, not a second title, so
   it drops to the body size and takes the h1's weight off. Measured at 375px:
   "Rome 600 Gregorian chant" is one line, which matters because `#title` sits
   directly above the sticky `.nu-bar` and a wrap here would push the bar and
   everything under it down.

   `[data-kind]` IS WRITTEN AND NOT USED FOR A LOOK, and that is deliberate.
   31 of the 191 rows are not a genre article — 20 are the act, 4 the album that
   IS the era, 7 an article wider than the anchor — and what says so is a REAL
   `<span class="nu-kind">` in the DOM, because a `::after` with `content:` is
   not text a stylesheet-off page can print. The attribute is left on the
   element for a gate to read and for the day somebody wants to tell an act from
   an album without reading the word. */
a{ color: var(--hand); }  /* 2026-08-27: a link is hand-blue, underline kept */
/* (`.nu-wiki{ font-size: 1rem; font-weight: 400; white-space: nowrap }` is
   GONE, 2026-08-29. The paragraph below says the class "moved rather than
   went" into the genre list's column — but the column's anchor is `.nu-ixw`
   (ui/atlas.js:665) and nothing was left wearing `.nu-wiki`: the dead-selector
   pass measured zero matches across all nine tabs. The SHAPE the rule stated
   lives at `.nu-ixw`, exactly where that paragraph says it was restated.
   `.nu-kind` WAS alive and is not, 2026-09-08. Paul: *"get rid of 'The
   Broader' — what does that even mean?"* The span was the printed half of
   wiki.js's `kind`, and once the act and album links went the only word it
   could say was "the broader" — a research term, on 27 rows of a list of 500,
   that a reader could do nothing with. ui/atlas.js stopped emitting it and the
   rule goes with it rather than sitting here matching nothing; the FIELD is
   untouched, still written by the extractor and still what WIKI.md reports.) */

/* ===== THE TWO STICKY BANDS ============================================
   Two, never three: 52 + 38 = 90px of a 667px screen, 13.5%. In landscape
   that would be 26%, so the axis heading drops out below 480px of height. */
/* ===== THE .nu-bar IS GONE, 2026-08-29 =================================
   Paul: *"Get rid of the play buttons and the title of the song."* / *"Add a
   permanent play button to the top of the nav. When I tap it the nav is taken
   over by play options."*

   SEVEN RULES STOOD HERE and every one of them was arithmetic about fitting
   five controls into a row that could not wrap: `.nu-bar` itself (sticky, a
   `--panel` plate with a full-ink rule, full-bleed, `min-block-size:
   var(--bar-h)`), `.nu-bar > *`, `.nu-bar > button` at .4em of padding, a
   `max-width: 374px` gap squeeze to .2em, `.nu-bar > button > .nu-rd`, and the
   range's `flex: 1 1 auto; min-inline-size: 4em` with a 3.5em phone floor.
   They are deleted with the band, and their measurements are kept here because
   they are the reason the gutter is the shape it is:

     · FOUR CONTROLS, 2026-08-27: 364.6px of content in a 296px strip at 320,
       68.6 over, paid with .6em -> .4em on the gap, .6em -> .4em on three
       buttons' padding, and the room word clipped below 430.
     · FIVE CONTROLS, 2026-08-28: 269.8 against 264, 5.8 over, and the gap
       alone moved nothing because the range is `flex: 1 1 auto` and simply
       grew into what the gaps gave back. It came off the range's own floor,
       4em -> 3.5em, and the strip measured 52.00px tall with
       scrollWidth === clientWidth at 320, 375, 390, 430, 820 and 1280.

   A COLUMN HAS NO SUCH SUM TO MAKE, and that is the whole of what this round
   bought: five 44px marks stacked in a 56px gutter are 220px of a phone's
   844, against a 52px band that had to be re-measured at six widths every time
   a control was added. `--bar-h` survives as the variable other rules were
   written against; nothing in this file sets a height from it any more.

   WHAT IS LOST, SAID OUT LOUD: the transport is no longer visible while you
   are on another level of the stripe. #play is (it is in the head, at every
   level); rewrite, take, the voicing and the room are one press away instead
   of zero. That is Paul's trade, made in Paul's sentence.

   test/shell.js A7 ("exactly one .nu-bar, and it is --bar-h tall") IS STALE AS
   OF THIS ROUND and is not this round's file to rewrite: it now measures a
   band that was deleted on purpose. The honest replacement is the assertion
   the gutter already deserves — one `.nu-tray`, `--tray-w` wide, with `#play`
   in its head at every level. */

/* ===== AND `#title` WENT WITH IT ======================================
   `.nu-wiki` and `.nu-kind` STAY, and they moved rather than went: the one
   Wikipedia link that sat beside the <h1> is 191 links in a column of the
   genre list now (Paul: "Add Wikipedia links to the genre list in a column"),
   and both classes are worn by that column. The paragraph that stood on
   `.nu-wiki` — "SMALLER THAN THE NAME IT SITS BESIDE. `#title` is the h1 and
   the record's name is the heading; the article is a footnote to it… measured
   at 375px, 'Rome 600 Gregorian chant' is one line, which matters because
   `#title` sits directly above the sticky `.nu-bar`" — described a heading and
   a band that are both deleted. What survives it is the SHAPE the class still
   needs, restated for its new host at `.nu-ixw` below: a link in a 44px row is
   a footnote to the row, not a second title. */

/* ===== THE ENGINE READOUT IS A LOG LINE NOW (2026-08-28) ================
   Paul: *"Get rid of the media (mediaEl) held plays offline etc section on the
   top; move that info to the logger."* `<p id="engine">` is gone from
   index.html and this rule went with it. It is written out here rather than
   deleted because what it bought — and what it cost — is the whole argument
   for the logger being a log and not a line.

   IT READ: `#engine { min-block-size: 2lh; font-family: var(--mono);
   font-weight: 600; font-size: .85rem; color: var(--meter); }`, and its note
   carried this measurement, made at 390x844 and 1280x844 on 2026-08-25 by
   writing each sentence into the element and reading its box back:

     empty (before you press play)                          0px
     "stream · runway 3.1s"                                18px   both widths
     "stream · runway 3.1s · 1 dropout, worst 168 ms,
      last at 0 min"                                       36px @390, 18px @1280

   — "so pressing play dropped the whole editable page 34px, and a dropout …
   dropped it another 18px with nobody touching anything. That is the clock
   moving the editing interface, which is the one thing this page may not do."
   The fix was to reserve two lines at every width and never a breakpoint,
   because the sentence is audio/live.js's and any width pinned here would rot.

   ALL OF THAT REASONING SURVIVES AND THE 36px DOES NOT. A gutter foot reserves
   its geometry once, at creation, and holds one button whose SIZE never
   changes whatever the engine says; the sentences go into a panel that is
   `position: fixed` and therefore cannot move a row of the page whether it is
   open or shut. The 36px of permanently blank reserved band at the top of a
   390px phone is simply not spent any more. The `--meter` green and the mono
   face moved with the words — see `.nu-log li[data-kind="engine"]`, which is
   the same three declarations on the same sentences, so ENGINE TRUTH still
   looks like engine truth wherever it is printed. */

/* THE NEW-VERSION LINE (2026-08-27). Built by nukernel/audio/offline.js when
   the worker reports it replaced a version and a reload is not free — never at
   boot, so this rule paints nothing on a normal load. It sits UNDER the bar
   beside #engine and takes no reserved height: an element that is not there
   must not hold a strip of the page open all day for the one deploy that lands
   while somebody is listening. */
.nu-new{ display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-block: var(--s1) 0; font-size: .9rem; }

/* ===== THE SECOND BAND IS THE TAB ROW, AND IT USED TO BE THE HEADING =====
   Paul, 2026-08-27: *"Why don't we make tabs at the top level and let go of
   the idea of scrolling everything? The tabs are: Where / Tempo / Key / Motif
   / Band / Mix / Produce / Score / Export."*

   WHAT THIS PARAGRAPH USED TO SAY, AND WHY IT WAS RIGHT. It read: "A STICKY
   HEADING NEEDS SOMETHING TO BE STICKY INSIDE OF. `position: sticky` releases
   at the bottom of its CONTAINING BLOCK, so with every <h2> a direct child of
   #app the first heading would pin for all 3000px of the page and the last
   would never pin at all. `.nu-ax` — one <section> per axis — is that
   containing block, which is why ui/eight.js has to wrap them." Every word of
   that is still true of `.nu-ax` and is why ui/eight.js still wraps an axis in
   one; what is gone is the REASON to want it, which was that a reader
   scrolling seventeen thousand pixels needs a band saying which of the eight
   axes is under their thumb. There is no such scroll now. One panel is on the
   page at a time and the band at the top names all nine places at once, so
   the heading has nothing left to tell anybody that the marked tab six pixels
   above it does not already say.

   SO THE HEADING IS VISUALLY HIDDEN AND NOT DELETED, which is `.nu-vh`'s own
   recipe used for its own reason (band.css:60-61; the `room` word in the
   transport and the transform rows already do this): the <h2> is still in the
   DOM, still the first child of its section, still what a screen reader
   announces when `restoreFocus` puts the focus on an axis, and still what the
   page reads as a heading with the stylesheet off — where all nine panels are
   visible and the document is the one long document it always was. What it no
   longer does is print a second visible copy of a name the tab already owns.
   ONE OWNER PER FACT: the visible name of a panel is the word on its tab.

   WHAT WENT WITH IT, NAMED so nobody restores half of it: the ink band's
   paint, the `--head-h` reserve on an h2, the `@media (max-height: 480px)`
   rule that unpinned the heading in landscape (two bands at 90px of a 667px
   screen was 13.5% and 26% in landscape — the tab row is ONE band and pays
   that rent once), and the two-headings-at-the-handoff measurement, which is
   kept below because the thing it measured cannot happen any more and
   somebody will otherwise measure it again. */
/* …AND IT IS `.nu-vh`'s OWN FOUR DECLARATIONS, not a fifth spelling of them.
   `.nu-vh` is a CLASS and a heading built by ui/eight.js `axis()`, by
   ui/engineer.js and by ui/produce.js would have to be given it in three
   files; the rule is one selector here instead, and it says exactly what
   `.nu-vh` says at :1137 so there is one recipe for "hidden from the eye,
   present for everything else" even though there are two selectors reaching
   it. If that pair ever drifts, this is the copy that is wrong. */
.nu-ax > h2, #atlas > h2{ position: absolute; width: 1px; height: 1px;
  margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.nu-ax > h3, .nu-ax h3{ font-size: 1rem; margin: var(--s4) 0 var(--s1); }/* THE COUNTDOWN, AND IT IS THE CLOCK SPEAKING. `[data-live="pending"]` in the
   DOM (ui/eight.js), holding no control — the badge and the button are below
   the rule and belong to a hand. It reads as a MEASUREMENT and not as a label,
   so it takes the meter green and the machine face every value on this page
   already wears: "a slider with no green beside it is a slider that reaches no
   sound", and this is the green that says the edit you just made is on its way
   to one.
   TWO LINES IN 47px: the number at the size of a button's word, the unit under
   it at the size of a tray digit. Measured at 390: "16" is 17.8px wide and
   "beats" is 30.2px, both inside the 47px column with no wrap and no clip. */
.nu-count{ display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 0; padding-block: var(--s1);
  font-family: var(--mono); color: var(--meter); line-height: 1.05; }
.nu-count b{ font-weight: var(--num-fw); font-size: .95rem;
  font-variant-numeric: tabular-nums; }
.nu-count small{ font-size: .6rem; font-weight: 600; }
/* NOTHING PENDING IS NOTHING DRAWN, and it is `:empty` rather than an
   attribute the clock would have to remember to set and unset. ui/eight.js
   writes the two children on a pending beat and `replaceChildren()` on the
   landing; the box folds to zero by having nothing in it, so there is exactly
   one state to keep honest instead of two. It also means the element — and its
   `data-live="pending"` declaration — is in the DOM from boot at every moment,
   which is what a gate reads to know the clock has a licence for this square
   inch and for no other in the gutter. */
.nu-count:empty{ display: none; }
/* ===== THE LOG ITSELF ===================================================
   `position: fixed` IS THE LOAD-BEARING DECLARATION, and it is `.nu-say`'s
   argument word for word: the sheet is POSITIONED, never inserted into flow,
   so opening it cannot move a row under a thumb. index.html carries the rest
   of why it ships at the end of <body>.

   IT STARTS AT THE GUTTER'S INNER RULE. Paul: *"Dont let anything go under
   it."* `inset-inline-start: var(--tray-w)` is that sentence as arithmetic —
   the sheet's near edge IS the gutter's inner edge, so nothing it draws is
   ever on top of the marks it was opened from, and it grows away from the
   stripe, which is the only direction there is room in. Upward from the floor,
   because that is where its button is.

   WHICH SIDE THE GUTTER IS ON IS `.nu-tray`'S FACT AND THIS RULE FOLLOWS IT
   (rewritten 2026-08-28, the same afternoon it was first written the other way
   round). Paul: *"Move the right nav to the left so it doesn't interfere with
   the scroll on the right."* The gutter went from `inset-inline-end: 0` to
   `inset-inline-start: 0` and this sheet went with it, from `-end` to `-start`
   — one declaration each. The list of everything that has to move with the
   stripe is kept at `.nu-tray` itself, where the fact lives; this is one of
   the three entries on it, and ui/glyph.js `place()` is the third, which is
   the one that does NOT have to be edited because it MEASURES the gutter
   rather than being told where it is.

   AND IT IS A COLUMN OF ITS OWN, WITH ITS OWN SCROLL. A log grows; a fixed box
   that grew with it would eventually be taller than the screen and there would
   be no way to reach the top of it. So it is capped at 60% of the viewport and
   scrolls INSIDE itself (`overscroll-behavior: contain` is right here and
   wrong on `.nu-traylist`, for the reason written there: this box ALWAYS has
   something to scroll once it is worth opening, so containing it steals no
   swipe from the document). */
/* ===== `.nu-strip-out` — THE FLYOUT CHASSIS, FACTORED (2026-09-02) ========
   Paul, 2026-09-01: *"Perhaps we should experiment with strips that fly out to
   give us access to all options, like MacOS system settings used to."*

   IT IS ALREADY BUILT, THREE TIMES. `.nu-log`, `.nu-explain` and `.nu-say`
   were written a week apart and arrived at the same six declarations
   independently — fixed, anchored on the gutter's inner rule, the `--panel`
   plate, one `--bw` of ink, `--r1`, the hard shadow — because there is only
   one right answer on this page and each of them found it. Three copies of one
   answer is how the fourth copy comes out slightly different, so the six are
   said here, once, and the three keep only what actually distinguishes them:
   WHICH EDGE they hang from, HOW WIDE they are, and what is inside.

   THE CLASS IS IN THE SELECTOR LIST, NOT IN THE DOM, and that is deliberate
   for exactly this wave. The three existing strips are built by three files
   this round did not open; naming them here costs nothing and moves nothing,
   and `.nu-strip-out` on its own is live from this moment for anything NEW —
   the seed flyout (B1) and any strip after it get the chassis by wearing one
   class and stating two facts.

   WHAT A NEW STRIP STILL OWES, because the chassis cannot supply it:
     · an anchor — `inset-block-start` or `inset-block-end` (the chassis fixes
       only the INLINE start, which is the gutter's rule and is not negotiable:
       shell A6i fails anything that overhangs the stripe);
     · a width, in the `min(<n>ch, calc(100vw - var(--tray-w) - 12px))` shape
       the two below use — the second arm is what keeps it inside a 320px
       phone and is not optional either;
     · a `max-block-size` if it can grow, because MENUS NEVER SCROLL INSIDE
       THEMSELVES is the law and a strip is the standing exception to it, which
       has to be argued at each strip (see `.nu-log`'s own paragraph). */
.nu-strip-out, .nu-log, .nu-explain, .nu-say{
  position: fixed; z-index: 55;
  /* ...AND THE NEAR EDGE IS THE PAGE'S OWN NOW, 2026-09-09. It was
     `var(--tray-w)` — the gutter's inner edge, because nothing might overhang
     the stripe (shell A6i). The stripe is deleted and the chrome is a bar at
     the FOOT, so the constraint turned ninety degrees with it: a strip starts
     at the page's inline gutter and stops above the bar, which is what every
     `inset-block-end` below now spends `--bar-h` on. */
  inset-inline-start: var(--gl);
  margin: 0;
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--panel); color: var(--ink);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  box-shadow: var(--shadow);
  font-size: var(--t2); line-height: 1.35; }
.nu-strip-out[hidden], .nu-log[hidden], .nu-explain[hidden], .nu-say[hidden]{
  display: none; }
/* and the chassis's own default anchor and width, for a strip that states
   neither: the foot of the page, the log's own measure */
.nu-strip-out{ inset-block-end: calc(var(--bar-h) + 4px);
  inline-size: min(46ch, calc(100vw - 2 * var(--gl)));
  max-block-size: min(60vh, 520px);
  padding: var(--s2) var(--s3); }
/* WHAT IS LEFT OF THE LOG: where it hangs, how wide, and how tall.
   ...AND IT HANGS FROM THE TOP SINCE 2026-09-06 (docs/NAV.md). The log's own
   mark is the last row of the hamburger and the hamburger is at the top right;
   a panel that opened at the opposite corner from the button that opened it is
   the arrangement `#nu-menu`'s own note has already refused once. Same width,
   same ceiling, same chassis — only the anchor turned over. */
.nu-log{ inset-block-start: calc(var(--top-h) + 4px);
  inline-size: min(46ch, calc(100vw - 2 * var(--gl)));
  max-block-size: min(60vh, 520px);
  padding: var(--s2) var(--s3); }
/* ONE LINE PER THING THAT HAPPENED, NEWEST FIRST. A real <ol> — the order is
   the meaning, and with the stylesheet off it has to stay the meaning — with
   its numbering off, because the numbers a reader wants are the beats and the
   values, not "1, 2, 3". */
.nu-log ol{ list-style: none; margin: 0; padding: 0; }
.nu-log li{ display: flex; gap: var(--s3); align-items: baseline;
  padding-block: var(--s1); border-block-start: var(--bw) solid var(--rule); }
.nu-log li:first-child{ border-block-start: 0; }
/* THE STAMP. Tabular, dim, and fixed-width so the sentences line up down the
   left edge of the column rather than stepping in and out with the clock. */
.nu-log time{ flex: 0 0 auto; font-family: var(--mono); font-size: .72rem;
  font-variant-numeric: tabular-nums; color: var(--dim); }
.nu-log li > span{ flex: 1 1 auto; min-inline-size: 0;
  overflow-wrap: break-word; }
/* ENGINE TRUTH STILL LOOKS LIKE ENGINE TRUTH. These are the three declarations
   `#engine` wore under the transport (the mono face, 600, `--meter`), on the
   same sentences, from the same one owner — audio/live.js `engineLine()`. The
   readout moved; what it looks like did not. */
.nu-log li[data-kind="engine"] > span{ font-family: var(--mono);
  font-weight: 600; color: var(--meter); font-size: .78rem; }
/* AND THE VALUE INSIDE AN ACTION LINE takes the machine voice the rest of the
   page gives to every number a control prints (`--num-fw`), so "swing" and
   "58%" read as the two different kinds of word they are. */
.nu-log b{ font-weight: var(--num-fw); }
/* THE RESTING SENTENCE. Not a refusal and not styled as one — nothing has been
   refused, there is simply nothing yet — so it is the page's ordinary quiet
   second voice and it carries no `.nu-why` furniture. */
.nu-log p{ margin: 0; color: var(--dim); }

/* ===== THE EXPLAINER'S PAGE (2026-08-30) =================================
   Paul: "add a ? Icon above the log icon that fully explains every aspect of
   a genre." The sheet the ? opens — ui/explain.js builds it, appended to
   <body> on the first press. It is the logger's own chassis one rule up:
   fixed on the gutter's inner side, the --panel plate, the hard shadow,
   its own scroll — POSITIONED, never inserted into flow, so opening it moves
   nothing under a thumb. Wider than the log (52ch against 46) because it
   carries sentences the genre table wrote, and top-anchored because it is a
   document you read from the top, not a tail you read from the bottom.
   `overflow-wrap` guards A1 at 320: a `cannot` sentence is the longest prose
   this page ships and it must fold, never widen. */
/* ===== TOMBSTONE: THE EXPLAINER'S SKIN, DELETED 2026-09-02 ============
   Paul, after using the composer: *"Get rid of explain — that's the genre
   editor's work now."*

   SEVENTY-SEVEN LINES CAME OUT HERE and none of their arguments were wrong;
   they were arguments about a POPOVER, and there is no popover. `.nu-explain`
   dressed `ui/explain.js`'s panel — the top-anchored 52ch strip, the sticky
   axis headings, the eight-word anchor strip, the lineage flow's `--d` indent
   and its `└`, the `.nu-xcannot` well — and that file is deleted with the ?
   mark that opened it. The selector matches nothing on the page, so every one
   of those rules was dead ink the moment the module went.
   WHERE THE JOB WENT. The Rules panel (`ui/rules.js`, over `ui/xtab.js`'s
   extraction — which is the half of `ui/explain.js` that survived and was
   never in this block) is the genre editor, and it wears the page's own panel,
   table and field skins. Nothing was copied down here for it: a second copy of
   the strip's dress is exactly what this deletion is for.
   WHAT IS DELIBERATELY LEFT ALONE: `.nu-explain` is still NAMED in the
   `.nu-strip-out` selector list above, beside `.nu-log` and `.nu-say`. Those
   two are live strips and the chassis is theirs; the third name costs nothing,
   and the paragraph over it is the record of why three files independently
   wrote the same six declarations. A name in a selector list is not a rule
   about a thing that exists. */

/* A SHUT PANEL, AND IT IS SHUT BY A RULE AND NOT BY AN ATTRIBUTE. `hidden`
   would have been one character and it would have cost this file the one
   promise the reversal above is allowed to keep: with the stylesheet off the
   page still reads top to bottom as the same document. `[hidden]` is the UA
   stylesheet's, not this file's, so a shut panel would stay shut with nu.css
   disabled and eight ninths of the page would be gone. A class rule vanishes
   with the sheet. `inert` — set beside this attribute by ui/eight.js — is what
   does the half `hidden` was wanted for: a shut panel is out of the tab order
   and out of the accessibility tree even though it is only display:none. */
.nu-pan[data-off]{ display: none; }
/* TWO HEADINGS AT THE HANDOFF, MEASURED AND LEFT — AND THE HANDOFF IS GONE
   (STATE.md item 21; retired 2026-08-27, kept so it is not measured a third
   time). Swept one pixel at a time across all six seams at 1280x900: every
   seam had a window where two <h2>s were both in the 0-120 band, 73 / 73 / 78 /
   75 / 76 / 73 px of scroll wide. At y=1789 `1 · Time` was pinned at top 52 and
   `2 · Alphabet` arriving in flow at top 99. The containing block was never
   wrong — the outgoing heading released EXACTLY at its own section's bottom,
   h2.top === section.bottom - 39 at every pixel after the release, across all
   six seams — so it was native sticky push-out and not a heading outstaying
   its axis. The only pure-CSS lever was `margin-block-end` on the h2, which
   would have unpinned every heading 78px before its axis ended, so the last
   78px of every section would be read under the wrong heading or none: that
   trade bought 78px of overlap and sold the feature.
   THERE IS NO SEAM NOW. One panel is on the page at a time, one `.nu-ax` in
   it, and its heading is not sticky and not visible; two of them can no more
   be in the band together than one can. test/shell.js's A6 — "exactly one
   pinned `.nu-ax > h2`", which this note had to explain could only ever be
   true at the three positions the gate sampled — is rewritten to the claim
   that IS true everywhere: the transport sits at 0 and the tab row sits at
   --bar-h, at every scroll position, on every tab. */

/* ===== A COLOUR PER SECTION ============================================
   Paul, 2026-08-26: *"Give each section a slightly different color."*

   WHICH "SECTION", AND THE OTHER READING THAT WAS CONSIDERED AND LOST. A record
   also has sections — head, verse, bridge — and they are drawn on this page as
   ROWS of the form list, cells of a voice's table and tiles of the score
   ribbon. Colouring those is a different feature: they are never blocks you
   scroll past, they are already spoken for by the playhead, and a tint on them
   would land in the very cells `<mark>` and `.nu-here` share, which is the
   conflation 2026-08-24 spent a day undoing. What Paul was doing when he said
   it was scrolling six thousand pixels of black and white; the blocks that go
   by are the seven `<section>`s — the atlas, the five axis sections, and the
   board. Those are what take the wash.

   IT IS ARITHMETIC ON AN INDEX, NOT A PALETTE. Nobody types a colour here. A
   section's `--sec` is its ordinal among its siblings and the hue is
   `--sec * 137.5deg` — the golden angle, which is what you use when you do not
   know how many you will need, because consecutive indices land as far apart on
   the wheel as they can and no two of the first dozen are close. The ladder
   below is written modulo twelve (`:nth-child(12n+k)`), so a page of thirteen
   sections does not run out — the thirteenth reuses the first hue, twelve
   blocks and several screens away from it.

   "SLIGHTLY" IS THE WHOLE SPECIFICATION and `--wash` is where it is stated:
   4% of a fully saturated hue mixed into `Canvas` in oklab. Measured on the
   rendered page, light: the paper moves from #ffffff to between #fbf5f7 and
   #f2f9fb, and CanvasText on the darkest of them is 19.8:1 — WCAG AAA wants
   7:1. Dark: from #121212 to between #151116 and #101517. Nothing on the page
   changes colour except the ground.

   IT MOVES `--paper`, AND THAT IS THE WHOLE MECHANISM. `--paper` in this file
   has always meant THE GROUND YOU ARE STANDING ON — it is what the sticky
   heading paints itself with so the page does not run under it, what the pane's
   scroll-shadow gradients fade to, what a step grid's sticky first column and
   the circle's face sit on. Redefining it ON THE SECTION makes every one of
   those follow the wash with no second rule and no list to keep in step; the
   heading rule above already anticipated this in as many words ("Any future
   accent colour must paint here too"). `--rule`, `--rule-strong` and `--zebra`
   are mixed against `Canvas` and NOT against `--paper`, so every line on the
   page keeps exactly the weight it had — the wash moves the ground and never
   the ink.

   IT IS NOT A STATE AND MAY NOT READ AS ONE. `<mark>` is the browser's own
   `Mark`/`MarkText` pair and `.is-now` is a full ink inversion; both are orders
   of magnitude louder than 4% and neither is a hue this ladder can produce.
   Measured on the rendered page: the largest colour distance between any two
   sections' grounds is smaller than the distance from any ground to `<mark>`.

   AND IT VANISHES WHERE IT SHOULD. With the stylesheet off there is no colour
   and nothing is lost, because the wash says nothing the `<h2>` above it does
   not already say in words. Under `forced-colors` and `prefers-contrast: more`
   `--wash` goes to 0% and every section is Canvas again — one property, one
   place, rather than a `background: Canvas` override per selector. Dark mode
   needs no query: the mix is against `Canvas`, which is what `color-scheme`
   already swapped.

   FULL BLEED, THE WAY `.nu-bar` ALREADY DOES IT (:112) — cancel the body gutter
   and re-pay it as padding — because a wash with a 12px white margin down each
   side reads as a box drawn around the section rather than as ground under it.
   Net zero on where any content sits: measured, every `.nu-ax`'s content box is
   at the same x it was, and `documentElement.scrollWidth === innerWidth` at 390
   and 1280.

   ENAMEL KEEPS THE WASH AND MOVES ITS GROUND (2026-08-27). Everything above
   stands: arithmetic on an index, nobody types a colour, 4% is the whole
   specification, `<mark>` is orders of magnitude louder. Two things changed
   with the repaint. (1) The mix lands on `--ground` — the chassis — instead
   of `Canvas`, because Canvas is retired with dark mode (the root block says
   why). (2) The question the repaint had to answer in prose: is a
   golden-angle hue DECORATIVE paint, which Enamel forbids? No — the four
   paints state facts about a CONTROL (hand set it, clock is on it) and the
   wash states a fact about WHERE YOU ARE, on the ground and never on a
   control, at 4% where the four paints are full strength. The other
   candidate, a painted left rule per section, was tried on the rendered page
   and refused: a 3px coloured rule reads as a control state (it is exactly
   what `tr.nu-here` draws), which is the conflation this comment already
   forbids. The wash is the one mechanism that cannot be mistaken for any of
   the four paints, so it stays. `--rule`/`--rule-strong`/`--zebra` are
   literal colours now, so "every line keeps exactly the weight it had" is
   true by construction rather than by mixing discipline. */
/* (`:root{ --wash: 4% }` AND ITS TWO MEDIA OVERRIDES STOOD HERE and are in
   nukernel/tokens.css with every other token — see the tombstone at the head
   of this file. The paragraph above is about the WASH, which is a mechanism
   this file draws; the NUMBER is a token, and it moved.) */
/* ...AND THE TWO NEW FAMILIES FALL WITH IT (2026-09-02). CATEGORY and LEVEL
   are the fifth and sixth colour families (see the top of this file) and they
   are subject to exactly the same rule the wash is: a reader who has asked for
   more contrast, or whose system has taken the palette away entirely, gets ink
   and the chassis. This is ONE mechanism said twice, not two mechanisms: the
   token is re-declared to something neutral and every rule that reads it
   follows, because no rule anywhere below types one of these hexes.
   CATEGORY collapses to `--ink` — six identities become one, which is honest
   (the WORD beside each chip is still there and is what actually names the
   player; the hue was only ever the shortcut). LEVEL collapses to `--hand`,
   its own top step, so a fill is still the paint it always was and the value
   is read off the fill's WIDTH, which is where it was already being read. */
/* (THE `prefers-contrast: more` AND `forced-colors: active` OVERRIDES OF THE
   CATEGORY AND LEVEL FAMILIES STOOD HERE, and are in nukernel/tokens.css
   under THE TWO ROOMS THAT ARE NOT THEMES. The paragraph above is the
   argument and stays where the families are drawn; the declarations moved.) */
/* ===== `.nu-vpaint` — THE ONE THING THAT WEARS A CATEGORY (2026-09-02) =====
   THE CONTRACT, AND IT IS TWO ATTRIBUTES AND ONE CLASS:

     `[data-vi="0".."5"]`  declares `--vpaint` for itself AND for everything
                           inside it. Put it on a row, a strip, a cap, a chip —
                           anywhere the answer to "which player" is fixed. The
                           number is the voice's own index in `DOC.voices`; a
                           record with more than six voices wraps, and the
                           wrap is visible rather than silent (see CATEGORY).
     `.nu-vpaint`          PAINTS with it: a filled plate in the player's hue
                           with the page's own ink border and white ink on top.
                           Every one of the six is dark enough for white text
                           (checked: the lightest is `--v0` #1F8FD6 at 3.1:1
                           against white, which is why the ink BORDER is not
                           optional — it is what keeps the plate's edge legible
                           where the fill's contrast is thin).
     `.nu-vpaint.is-edge`  the quiet form: a 3px edge bar in the hue and no
                           fill, for a row that must keep its own ground (a nav
                           member box, a table row).

   WHY A UTILITY AND NOT A RULE PER SURFACE. Four surfaces want this — the
   score's motif caps, the roll legend, `ui/eight.js`'s band rows and
   `ui/engineer.js`'s strips — and three of them are built by files this round
   did not open. A utility is the only shape in which the colour can be
   ADDED to a surface by the agent that owns that surface, in one class and one
   attribute, without a fifth rule appearing in this file each time. */
[data-vi="0"]{ --vpaint: var(--v0); }
[data-vi="1"]{ --vpaint: var(--v1); }
[data-vi="2"]{ --vpaint: var(--v2); }
[data-vi="3"]{ --vpaint: var(--v3); }
[data-vi="4"]{ --vpaint: var(--vb); }
[data-vi="5"]{ --vpaint: var(--drum); }
.nu-vpaint{ background: var(--vpaint, var(--ink)); color: var(--on-fill);
  border: var(--bw) solid var(--ink); border-radius: var(--r2); }
.nu-vpaint.is-edge{ background: transparent; color: inherit; border: 0;
  box-shadow: inset 3px 0 0 var(--vpaint, var(--ink)); border-radius: var(--r0); }
.nu-ax, #atlas{
  --paper: color-mix(in oklab,
                     hsl(calc(var(--sec) * var(--wash-turn)) var(--wash-sat) var(--wash-lit))
                     var(--wash), var(--ground));
  background: var(--paper); }
/* THE CANCEL-AND-RE-PAY IS THE PANEL'S INSET NOW, NOT `--gl` (2026-09-02).
   Paul, 2026-09-01: *"Many inner sections lack any padding and just smash into
   the nav."* The air is paid at `.nu-pan` (see its rule below), which means a
   washed axis inside a panel now starts 12.8px in from the panel's edge — and
   a wash that stops 12.8px short of the gutter's rule is a stripe of bare
   chassis down the leading edge of every tab, which is the defect the panel
   inset was added to fix, drawn one element further in.

   SO THE ARITHMETIC MOVES WITH THE PADDING AND NOTHING ELSE CHANGES. The rule
   used to cancel `--gl`/`--gr` and re-pay them; it cancels the panel's inset
   and `--gr` and re-pays those. The blocks land exactly where they did before
   the padding existed — on the gutter's rule at the start, on the glass at the
   end — and the CONTENT inside them is inset by `--s4`, which is the whole
   ask. Measured after: A1 (`scrollWidth === clientWidth`) and A6i (nothing
   past the gutter's left edge) at 320/375/430/820/1280.

   IT IS SPLIT IN TWO BECAUSE `#atlas` IS ITSELF A `.nu-pan` (index.html:348)
   and a `.nu-ax` is a CHILD of one. The panel's own padding IS the atlas's
   inset — there is nothing for it to cancel on the leading side — while a
   `.nu-ax` has to reach back out through its parent's padding. One fact, two
   positions in the box tree, so two rules rather than one rule that is wrong
   for one of them.

   MEASURED ON THE RENDERED PAGE, 2026-09-02, every tab at 320 and 1280: the
   panel's own left edge is x=56 (the gutter's right edge, to the pixel), its
   `padding-left` computes to 12.8px, and the washed `.nu-ax` inside it is back
   out at x=56 — bleeding to the rule and stopping there. `scrollWidth ===
   clientWidth` at both widths on all eleven tabs, and test/shell.js A1/A5/A6i
   green at 320/375/430/820.

   THE ONE ASSUMPTION, SAID OUT LOUD: `--s4` is `.8em`, and an `em` cancel only
   cancels if the two boxes agree about `em`. Neither `.nu-pan` nor `.nu-ax`
   sets a `font-size`, so both resolve against `body`'s 16px and both are
   12.8px. A `font-size` on either is the one edit that silently breaks this,
   which is why it is written here and not left to be discovered. */
.nu-ax{
  margin-inline: calc(-1 * var(--s4)) calc(-1 * (var(--s4) + var(--gr)));
  padding-inline: var(--s4) calc(var(--s4) + var(--gr)); }
#atlas{
  margin-inline-end: calc(-1 * var(--gr));
  padding-inline-end: calc(var(--s4) + var(--gr)); }
/* THE ORDINAL IS ON THE PANEL NOW, AND THE TWELVE-RULE LADDER IS GONE
   (2026-08-27). What stood here was twelve `#app > .nu-ax:nth-child(12n+k)`
   rules and its argument was "THE LADDER IS THE ONLY WAY CSS CAN COUNT: there
   is no function that reads a `:nth-child()` match back out as a number —
   `sibling-index()` will do this in one line and is too new to ship on a page
   that has to work on the phone in Paul's pocket". That is still true about
   CSS and it no longer has anything to count. When the page was one scroll,
   #app held an unknown number of axis sections one after another and the
   ladder was what turned "the third block you scroll past" into a hue. The
   page is nine tabs now (Paul, 2026-08-27) and the blocks are a FIXED, NAMED
   list — the nine `.nu-pan` hosts in nukernel/index.html, in Paul's own order
   — so the ordinal is simply written on each one. Nine lines instead of
   fifteen, no counting, and the same single formula above turns the number
   into a colour. ONE PALETTE DECISION, unchanged, and it is still "137.5
   degrees apart".

   THE NUMBERS ARE READING ORDER AND THAT IS THE WHOLE SCHEME. This paragraph
   used to have to explain why `#atlas`, `#deck` and `#produce` were "the only
   numbers in this file that are HAND-KEPT" and why they were 0, 5 and 6 rather
   than 13 and 14 (measured: `--sec: 14` came out at hue 125deg against
   ax-time's 137.5 — twelve degrees, one unit of red at a 4% wash). Now EVERY
   number is hand-kept and the exception has become the rule, so the warning it
   ended on is worth restating rather than deleting: the golden angle only
   promises that CONSECUTIVE indices are far apart. Numbered 0-8 in reading
   order, every neighbour is a full 137.5deg from the one before it. A tenth
   panel would take 9; a panel inserted in the middle renumbers the ones after
   it, which is one line each and is the price of not counting.

   A `--sec` ON THE PANEL AND NOT ON THE SECTION, because a panel may hold a
   `.nu-ax` that ui/eight.js did not build (the board's, the producer's) and a
   custom property inherits. `.nu-ax` no longer declares `--sec: 0` of its own
   for exactly that reason: a default here would win over the panel's value and
   every block would come out at hue 0. */
/* THE ONE RULE THAT ENDS THE SMASH (2026-09-02). Paul, 2026-09-01: *"Many
   inner sections lack any padding and just smash into the nav."* Measured
   before this declaration: `.nu-pan` had exactly two rules — `--sec: 0` and
   `[data-off]{display:none}` — no padding, no margin, no max-width, ever, so
   every panel's first control began at x = the gutter's inner ink rule, zero
   gap, at every width and on every tab.

   IT IS PAID HERE AND NOT AT `--gl`, AND THAT IS THE WHOLE OF WHY THE TWO
   SENTENCES DO NOT FIGHT. 2026-08-30 — *"Get rid of all margins and padding on
   the outside of the views"* — is about the OUTSIDE and still holds: `--gl` is
   0 (see its own paragraph, which carries this reversal beside it) and
   `body`'s `padding-inline` is untouched, so the number test/shell.js A7 and
   A6i measure has not moved. 2026-09-01 is about the INSIDE. A view that runs
   to the edge and a control that has room to breathe are the same view.

   `var(--s4) var(--s4) 0` — air at the top and on both sides, NOTHING at the
   bottom, because `body` already pays `max(24px, env(safe-area-inset-bottom))`
   there and a second block of air under the last control would be two owners
   of one gap. `--s4` is "a plate's own padding" on the spacing scale, which is
   what a panel is.

   WHAT RE-PAYS IT: the wash rule above, in one place, for `.nu-ax` and
   `#atlas`. Anything else that wants to bleed full-width inside a panel cancels
   `--s4`, not `--gl`, and says so where it does it.

   THE STEP GRIDS KEEP THEIR 3.2px AND DO NOT OPT OUT — re-measured 2026-09-02,
   and this is the check the round was told to make. `.nu-grid` lives inside a
   `.nu-ax`, and a `.nu-ax` cancels the panel's inset and re-pays it as its own
   padding — which means the grid's containing block is exactly the width it
   was yesterday, and the 3.2px of slack the motif table has at 320 (see HOW
   WIDE A DRAWN TUNE HAS TO BE) is untouched. The negative-margin escape hatch
   the plan reserved for the grids is therefore NOT spent, and it is written
   down here so the next hand does not spend it looking for a problem that was
   already solved one element up. If a future panel ever draws a grid OUTSIDE a
   `.nu-ax`, that is the case that needs the hatch. */
.nu-pan{ --sec: 0; padding: var(--s4) var(--s4) 0; }
#atlas{ --sec: 0; }
/* (`#pan-tempo{ --sec: 1 }` STOOD HERE and is the THIRD hole in the ladder,
   2026-09-06: the Time tab is deleted (TABLE.md §10b step 1) and its controls
   are the table's merged TIME row, so the panel that wore hue 1 is gone. The
   ordinal is left as a hole for the reason the next paragraph gives.) */
/* (`#pan-key{ --sec: 2 }` STOOD HERE and the ordinal is DELIBERATELY LEFT AS
   A HOLE, 2026-09-04. Tempo and Key are one tab (`Time`, nukernel/TABLE.md §8)
   and `#pan-key` is deleted from index.html, so the selector has no subject —
   but the ladder's whole meaning is that consecutive numbers are 137.5deg
   apart, and renumbering nine panels to close a gap would repaint every one of
   them. `#pan-structure` two rules down is the same hole for the same reason,
   with the same date. */
#pan-motif{ --sec: 3; }
#pan-band{ --sec: 4; }
#deck{ --sec: 5; }
#produce{ --sec: 6; }
#scoredeck{ --sec: 7; }
/* THE LADDER IS TWELVE LONG NOW (2026-09-02). `#videodeck` and `#saverdeck`
   shipped in index.html on 2026-09-01 and were never given a number, so both
   inherited `.nu-pan`'s `--sec: 0` and wore the atlas's hue — two panels
   painted as a third, which is precisely the fact the wash exists to state.
   `#rulesdeck` and `#pan-structure` join them (Paul, 2026-09-01: the Rules
   view *"it'll need a new icon in the left nav"*, and Structure *"should be
   top level, not buried under band, and below band"*). `#exportdeck` renumbers
   from 8 to 12 — the price the paragraph above names for inserting in the
   middle, one line, paid.

   TWO OF THESE SELECTORS MATCH NOTHING YET, AND THAT IS DELIBERATE — read this
   before the dead-selector pass (see `.nu-tf-row` below) deletes them. That
   pass exists because "a selector with no subject is read by the next hand as
   a promise about the page", and it is right about a FACE: a rule that draws
   something nothing wears is a lie. This is not a face. It is a DECLARED LIST
   — a ladder of ordinals in reading order, whose whole meaning is that every
   panel has a number and that consecutive numbers are 137.5deg apart. A hole
   in it is not a tidy absence; it is the next panel silently taking hue 0. So
   the ladder is written whole, ahead of its hosts (`#rulesdeck` and
   `#pan-structure` are wave 1a's to build), and the dead-selector pass's own
   test — "does the next hand read this as a promise?" — is answered here: yes,
   and the promise is kept the moment the host lands. */
#videodeck{ --sec: 8; }
#saverdeck{ --sec: 9; }
#rulesdeck{ --sec: 10; }
/* (`#pan-structure{ --sec: 11 }` STOOD HERE — deleted with its host on
   2026-09-04 (TABLE.md §6 ¶A). Another hole, kept for the reason above: 11 is
   spent, and 12 stays 12 so `#exportdeck` keeps the hue it has. */
#exportdeck{ --sec: 12; }

/* ===== TABLES ==========================================================
   "use more grid lines in tables, it will help" (Paul, 2026-08-24). Every
   cell is ruled; the header row is ruled twice as heavily; alternate body
   rows are tinted — except in a step grid, where a tint would fight the
   playhead and the sticky lane column. */
/* A TABLE IS A PLATE (2026-08-27): white panel, jet frame, screwed onto the
   washed chassis — which is ALSO what keeps the abcjs engravings and every
   dense grid at full contrast whatever the section wash under them is doing.
   `--paper` is re-declared to the panel INSIDE the table (and inside the pane
   that scrolls one, below) so every rule that paints itself "paper" — the
   sticky lane column, the board's sticky row labels — lands on the plate it
   is actually sitting on instead of on the ground the plate is screwed to.
   Same mechanism the section wash uses, one level further in. */
/* A TABLE IS AS WIDE AS THE COLUMN IT IS IN (2026-08-28). Paul: *"Make
   elements like tables and grids 100% wide."* Every table on this page was
   shrink-to-fit before this line — the browser's default for a table with no
   width — so a form list of five short sections came out 307.7px in the 366px
   the phone leaves, the kit's step grid 129.7px in the same 366, and the
   engineer's mirror right under them 366px because ITS words happened to be
   long. Three plates of three different widths screwed to one chassis, and
   the width meant nothing: it was a measure of the longest word in the table,
   not of anything about the song. One declaration here is the whole change —
   `inline-size: 100%` is a MAXIMUM for a table under `table-layout: auto`,
   not a straitjacket: the browser still gives each column its content and
   only distributes the SLACK, so a table that was already at or over its
   column is untouched by this line and the ones that were narrow grow into
   the room they were already given.
   IT IS NOT AN OVERFLOW RULE. A table whose min-content is wider than its
   column is still wider than its column and still needs the `.nu-pane` below
   to scroll it; see `td > select`'s length cap and `.nu-fitcell`, which are
   how the two tables that were over the line got under it. */
table{ border-collapse: collapse; --paper: var(--panel); inline-size: 100%;
  background: var(--panel); border: var(--bw) solid var(--ink); }
.nu-pane{ --paper: var(--panel); }
caption{ text-align: left; font-size: .85rem; color: var(--dim); opacity: 1;
  font-weight: 600; padding-block: var(--s1); }
th,td{ border: 1px solid var(--rule); padding: 2px 4px; text-align: left;
       /* 1.3, NOT THE BODY'S 1.45 (2026-08-27): the repaint's geometry budget
          is 8px on the measured surfaces and the form table spent 9 of it on
          leading plus the new 2px frame — measured 80px -> 89px at both
          widths. Cells are data, not prose; 1.3 buys the height back
          (measured 84px) and the prose keeps its air. */
       line-height: 1.3;
       vertical-align: middle; }
/* The tables here are built with createElement and appended row-first, so
   there is no implicit <tbody> — `table > tr` is a real selector on this page
   and BOTH spellings are needed. */
table > tr:first-child > th, thead th{ border-block-end: var(--bw-hard) solid var(--rule-strong); }
table > tr:nth-child(even) > td, tbody > tr:nth-child(even) > td{
  background: var(--zebra); }
.nu-grid > tr:nth-child(even) > td{ background: none; }

/* WHICH SECTION YOU ARE WRITING, IN THE FORM LIST — A RULE, NOT A HIGHLIGHT.
   The form list became the one owner of "where am I in the song" on 2026-08-25
   (Paul, asked to choose: "The first is good."), so it has to SAY which row
   that is. It may not say it with <mark>: <mark> is the browser's own
   highlight, it is the only one this page uses, and in this very cell it
   already means "this section is SOUNDING" (the playhead writes inside the
   button — ui/eight.js `secNumber`). Two facts drawn identically is the
   conflation 2026-08-24 spent a day undoing.
   So it is a heavy rule down the left of the row, for the same reason the zebra
   is refused in a step grid five lines up: a tint fights the playhead, a rule
   does not. `tr.nu-beat` in the grids below is the same answer to the same
   question, which is why this is a rule of the same weight. */
tr.nu-here > th:first-child, tr.nu-here > td:first-child{
  /* the rule is HAND-BLUE now (2026-08-27): "which row am I writing" is a
     fact about where your hand is, which is exactly what the hand paint is
     for — and it keeps the playhead's clock-red mark in the same cell
     unmistakable for it, which is the distinction the whole comment above
     exists to protect. Same 3px, same edge, new paint. */
  border-inline-start: var(--bw-hard) solid var(--hand); }

/* A WIDE TABLE SCROLLS INSIDE ITSELF OR IT SCROLLS THE WHOLE DOCUMENT, and
   there is no third option. Measured 2026-08-24: two seventeen-column grids at
   382px against a 375px phone put documentElement.scrollWidth at 390 — every
   vertical swipe on the page could drift sideways. One pane holds exactly ONE
   table; NEVER nest panes, and never put a sticky heading inside one, because
   `overflow-x: auto` computes `overflow-y` to `auto` as well and the heading
   would stick to the pane instead of the page. The pane is focusable
   (tabIndex 0, set by ui/eight.js) because a region a mouse can scroll must be
   reachable from a keyboard. The four backgrounds are the classic Roma
   shadow: two paper-coloured covers that scroll WITH the content and two
   shadows fixed to the box, so a shadow shows exactly when there is more
   table off that edge. */
/* ...AND IT IS `none` AND NOT `contain` SINCE 2026-09-06 (§15a, above): the
   pane is the one surface on this page a thumb drags SIDEWAYS, `contain` stops
   the chain and not the bounce, and a bounce carries the frozen section column
   with it. Both axes, because the pane scrolls both. */
.nu-pane{ overflow-x: auto; overscroll-behavior: none; max-inline-size: 100%;
  background:
    linear-gradient(to right, var(--paper) 30%, transparent) left / 24px 100% no-repeat local,
    linear-gradient(to left,  var(--paper) 30%, transparent) right / 24px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--scrim), transparent) left / 10px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--scrim), transparent) right / 10px 100% no-repeat scroll; }

/* THE SIXTEEN-STEP GRID, ROTATED, and the tension this file used to resolve
   the other way. It read, and the argument is kept because it was right about
   its own shape: "16 columns at the 44px isolated-control floor is 704px + a
   44px lane label = 748px, which shows 8 of 16 steps on a 375px phone: two
   swipes to read one bar and you can never see beat 1 and beat 4 at once. That
   is a worse instrument than a small one. 16 x 36 + label = 617px in a 351px
   pane — 10 steps visible, one swipe, and beat 1 stays under the sticky lane
   label." Every number in that is measured and the conclusion was the best one
   available to a grid with STEPS ACROSS.

   Paul, 2026-08-25: *"Rotate the drum kits and motif editors to be vertical.
   They'll fit on a phone screen that way."* Steps run DOWN now (ui/eight.js
   `drumGrid` and `hookGrid`), so `--cell` keeps its meaning — one step of a
   sixteen-step grid, 36px, which clears WCAG 2.2 AA Target Size (Minimum) at
   24x24, the criterion that exists precisely for dense equally-spaced grids —
   and its CONSTRAINT MOVES FROM WIDTH TO HEIGHT. Sixteen rows at 36 is 576px
   of a page that already scrolls that way, and the width becomes the number of
   COLUMNS the grid has: the kit's widest in the whole catalog is jazz's seven
   lanes (272px, all 130 genres swept for lane counts) and the motif grid's is a
   fixed five (292.8px, measured at every viewport from 320 to 1280 — the table
   does not stretch). Both are inside the 366px column a 390px phone leaves and
   inside the 296px one a 320px phone leaves, so
   neither takes a `.nu-pane` at all — see ui/eight.js `stepGrid`, and the note
   there about test/shell.js.

   AND THE SECOND OF THOSE TWO NUMBERS IS WRONG — 2026-09-03, re-measured on
   the rendered page rather than reasoned from the viewport. The deck a step
   grid actually sits in is 224px at 320 and 255px at 390, not 296 and 366:
   the gutter takes a fixed column out of the left of every width. So jazz's
   seven lanes (272px) ALREADY spilled a 320px phone sideways before anything
   in this round, and the drum editor's lane offer — which can put all twelve
   lanes on one cell, 454px — spills every phone. `drumGrid` therefore asks the
   browser whether the table is wider than the box it is in and takes a pane
   when it is, which is what A5 asks of every other table on the page; A5c was
   narrowed the same day to ban only the pane the argument above was ever
   about, one around a grid that CANNOT scroll. The numbers in the paragraph
   above are left standing because the conclusion they support is still the
   right one for a grid that fits, and a corrected number with no record of the
   wrong one teaches nobody where it came from.

   THE PARENT REACHED THE OLD ANSWER TOO and it is worth keeping the pointer:
   main:app/daw.css:732-742 — "a 32-step phrase at 390px would give 9px columns,
   so columns keep a 24px floor and the label column rides sticky-left over the
   scroll". Same shape, a table instead of a grid. If a thumb disagrees on a
   real phone the answer is one custom property; do not build a toggle. */
.nu-grid th, .nu-grid td{ padding: 0; text-align: center; }
.nu-grid td{ min-inline-size: var(--cell); block-size: var(--cell); }
/* THE FIRST COLUMN IS THE COUNT NOW, not the lane label — it carries `1 e & a`
   and it is the playhead. Sticky anyway: nothing here overflows, so nothing
   slides under it, and the rule costs nothing and is one less thing to put back
   the day a record turns up with more lanes than a phone is wide. */
.nu-grid th:first-child, .nu-grid td:first-child{
  position: sticky; left: 0; z-index: 5; background: var(--paper);
  text-align: left; padding-inline: 4px;
  border-inline-end: var(--bw-hard) solid var(--rule-strong); }
.nu-grid input[type=checkbox], .nu-grid input[type=radio]{
  inline-size: var(--box); block-size: var(--box); margin: 4px; }
/* ---------- WHERE THE BEAT FALLS -------------------------------------------
   Sixteen rows read as one block. `1 e & a 2 e & a …` says where you are, but
   only once you are reading the labels; a heavier rule every four rows says it
   to the eye at a glance, which is what a tracker has always drawn and what
   Paul asked the tables for ("use more grid lines in tables, it will help",
   2026-08-24). A ZEBRA WAS THE OTHER CANDIDATE and it is refused for the reason
   the rule below already gives: a tint in a step grid fights the playhead. */
.nu-grid tr.nu-beat > th, .nu-grid tr.nu-beat > td{
  border-block-start: var(--bw-hard) solid var(--rule-strong); }
/* ---------- HOW WIDE A DRAWN TUNE HAS TO BE ---------------------------------
   Paul, 2026-08-25: "Make motif sliders much less tall." / "Add velocity to
   motif sliders." / "Rotate the drum kits and motif editors to be vertical."

   THE THIRD SENTENCE ANSWERED THE FIRST TWO AND THIS BLOCK IS THE RECORD OF
   IT. What stood here was a pair of HEIGHTS for two VERTICAL sliders stacked
   in one cell — `.nu-vr` 16px wide, `.nu-vr-deg` 56px tall, `.nu-vr-vel` 40px
   — and the argument for them was careful and is worth keeping in one line:
   the step grid's other rows are floored at 36px each, 110px of slider made
   the motif table 290px, 56px made it 236px, and below 56 "adjacent degrees
   are 2px apart and the row stops being a picture". All of that was about a
   picture drawn ACROSS.

   ROTATED, THE PICTURE IS DRAWN DOWN and the degree is horizontal position —
   a piano roll on its side. So the numbers here are WIDTHS, and the height is
   whatever fits the 36px row the grid already had: the degree slider is 30px
   tall in a 36px cell, which is a bigger tap target than the 16x56 handle it
   replaces and is why "make motif sliders much less tall" needed no second
   answer. It was NOT also shortened; rotating is what answered it.

   84 AND 52, MEASURED. The row is count + three radios + degree + velocity:
   20.2 + 3x36 + 97.8 + 65.8 = 292.8px, and the 84 and the 52 are the sliders
   inside the last two of those (the rest is each readout). 15 degrees across
   84px is 5.6px a step — visibly different positions, where the old 56px
   vertical handle gave under 4px a step. THE CEILING IS NOT THE PHONE IN
   PAUL'S HAND, IT IS THE NARROWEST ONE THE GATE CHECKS: test/shell.js measures
   at 320, where the column is 296px, so the table has 3.2px of room and the
   73px of slack at 390 is not slack at all. Do not spend it.

   AND THE 44px FLOOR IS CANCELLED HERE, exactly as it was for the vertical
   ones. The rule at the top of this file gives every `input[type=range]` a
   44px `min-height`, which is right for an isolated slider on a page and wrong
   inside a grid whose whole vocabulary is a 36px cell: it would have made
   every step row 44px and the block 704px. `.nu-grid` is the scope, so a
   slider anywhere else on the page keeps its floor. (This cancellation used to
   be keyed on `[orient=vertical]` and to say `min-block-size: 0` because those
   elements' block axis was the horizontal one — a 44px WIDTH, four columns'
   worth. Same cancellation, ordinary axes.) */
.nu-grid input[type=range]{ min-height: 0; min-block-size: 0;
  block-size: 30px; margin: 0; vertical-align: middle; }
/* (`.nu-hr-deg` 84px / `.nu-hr-vel` 52px stood here — the two step-grid
   slider widths. WRITER-LESS SINCE 2026-08-27: the Bench replaced the step
   grids' sliders with the pitch and weight bars (see THE BENCH at the foot of
   this file), so the rules are gone rather than left as the kind of orphan
   the `.is-now` audit above hunted. The 84/52 arithmetic in the comment above
   is kept as the record of the rotated shape.) */
/* THE READOUT SITS BESIDE ITS SLIDER, NOT UNDER IT. It was `display: block`
   under a vertical slider, in a row of its own; in a row-per-step grid a block
   readout would push every row past 36px and undo the whole rotation. `2ch` so
   that `-7` and `7` do not move the slider beside them as your finger drags —
   a column that reflows while you use it is the same complaint one size down. */
.nu-grid td > output{ display: inline-block; min-inline-size: 2ch;
  font-size: .8rem; line-height: 1; text-align: right; vertical-align: middle; }
.nu-grid th span, .nu-grid th mark{ font-size: .8rem; }

/* ===== THE VOCABULARY: THE BOX, NEVER THE INSIDE ========================
   PROGRAM.md §2.4. Each slice owns what goes in; this file owns the box. */

/* band.css:60-61, lifted verbatim in intent: hidden to the eye, present to a
   screen reader, and NEVER `display:none`, which takes it out of the
   accessibility tree and out of the tab order with it. */
.nu-vh{ position: absolute; width: 1px; height: 1px; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* THE INVERSION, AND IT HAS EXACTLY ONE OWNER NOW. `.is-now` is this page's
   only spelling of "this one is on, and you can see it across the room". The
   audit of 2026-08-26 found the name had no writer left anywhere in nukernel/
   — the playhead says "sounding" with `<mark>`, the browser's own highlight —
   while `.nu-circ .nu-opt.is-on` five hundred lines down had copied its two
   declarations out by hand. Two places defining one look is the accumulation
   Paul asked to be rid of, so the circle's chosen key is added to THIS
   selector list and its own rule is gone. `.is-now` therefore stays in the
   vocabulary as a live name and not a fossil: it is what the ring wears.
   (Both `.is-on` and `:has(:checked)` for the reason the border rule needs
   both — the class covers the browsers without `:has()`.) */
.is-now,
.nu-circ .nu-opt.is-on, .nu-circ .nu-opt:has(:checked){
  /* THE INVERSION IS A HAND-BLUE CAP NOW, NOT AN INK ONE (2026-08-27). It read
     `background: var(--ink); color: var(--paper)` — the loudest thing a
     monochrome page could say. Enamel gives "chosen" its own paint: cobalt is
     the hand, and the chosen key on the ring is the clearest case of "if
     cobalt is on it, you put it there" on the whole page. Same two
     declarations, same single owner, new paint. */
  background: var(--hand); color: var(--on-fill); }
/* <mark> IS THE CLOCK'S OWN HIGHLIGHT (2026-08-27). It was the browser's
   default — Mark/MarkText, a yellow the UA chose — and yellow is the FLAG's
   word now, which the sounding step is not: it is not about to matter, it is
   happening. The playhead's mark takes the clock's tint with the clock's ink,
   so "sounding" reads red-hot everywhere the clock writes (the form list's
   section number, the step grids' count column). The one <mark> that is not
   the clock — the chosen tab's — is unpainted by the cap rule above, where
   the reason is argued. */
mark{ background: var(--clock-tint); color: var(--ink); font-weight: 800; }
/* GREYED, NOT HIDDEN (Paul, 2026-08-24: "when an option makes another one
   unaccessible gray it out"). `:disabled` is the DOM already saying it, so a
   slice sets `input.disabled = true` and the grey follows for free. Hiding
   destroys the shape of the possible, which is what a composer reads a sheet
   for. */
.is-off, .nu-opt:has(:disabled){ opacity: .45; }
.is-quiet{ opacity: .7; font-style: italic; }
.nu-hint{ font-size: var(--t2); color: var(--dim); }
/* 12px, NOT .85rem, AND THE PIXEL IS LOAD-BEARING (2026-08-27). The audition
   say-line reserves its own minHeight by measuring "the record is playing —
   the score above is what you hear", and that sentence in the new sans is
   371px at 13.6px, 371 -> 371/357/343 at 13/12.5/12 against a 366px column at
   390 — so at the old size every say-line reserved TWO lines and #staff grew
   19px past the repaint's 8px geometry budget. 12.5 fits with 9px to spare
   and 12 with 23, and a reserve that flips lines on a font-metrics sneeze is
   the wrong side of that trade, so 12. (Under the old serif the same string
   was ~326px, which is why .85rem never wrapped before today.) */

/* THE SHEETS (D2). A grid, not a wrapping row: sheets.js measures its own
   longest word and asks for a column count on `data-cols`, and a grid is the
   only layout where the reason text under one option cannot shove its
   neighbours out of line. The count is advice — below 460px it is halved,
   because four columns of anything on a 320px phone is four columns of one
   letter. */
.nu-sheet{ display: grid; grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  /* a sheet is a PLATE now (2026-08-27): white panel, 2px jet frame, on the
     washed chassis — the options inside are panel-on-panel, framed by their
     own 2px ink */
  gap: 4px; border: var(--bw) solid var(--ink); background: var(--panel);
  --paper: var(--panel);
  padding: 6px; margin-block: var(--s3);
  /* ---- EVEN ROWS IN A SHEET (2026-09-02) ----------------------------------
     Paul, 2026-09-01: *"things are uneven based on how text wraps."*

     MEASURED BEFORE THESE TWO DECLARATIONS: a grid row is as tall as its
     tallest cell and a grid item STRETCHES to its row by default, so one
     option carrying a `.nu-why` made its whole row taller AND turned every
     neighbour into a tall empty chip with its word floating in the middle of
     it. Two or three reasons down a sheet and the sheet reads as a broken
     table.

     `grid-auto-rows` IS STATED so the row has a floor of exactly one tap
     target and grows only for content that genuinely needs it; `align-items:
     start` is what stops the neighbours coming with it. After: a chip with no
     reason is 44px at every width, whatever its neighbours are doing.

     THE REASON ITSELF DID NOT MOVE, AND THAT IS A DECISION, NOT AN OMISSION.
     The obvious next step is to take `.nu-why` out of the chip's flow
     entirely — absolutely positioned under it, or hoisted to the sheet's own
     full-width track. Both were rejected, in these words: positioned, it lies
     across the row underneath it (a reason that overlaps its neighbour is
     worse than a reason that makes its chip taller); hoisted, it is no longer
     inside the option's `<label>`, which is where `ui/sheets.js:64-68` puts it
     and where `test/sheets.js` reads it back off the artifact — and GREYED,
     NOT HIDDEN, WITH THE REASON PRINTED is a law with a throw behind it
     (ui/sheets.js:149-152). The reason may not be moved, truncated, hidden or
     tooltipped. So it costs its OWN chip's height and nothing else's, which is
     the whole of the defect that was actually visible. */
  grid-auto-rows: minmax(var(--tap), min-content);
  align-items: start;
  min-inline-size: 0; }
.nu-sheet[data-cols="2"]{ --cols: 2; }
.nu-sheet[data-cols="3"]{ --cols: 3; }
.nu-sheet[data-cols="4"]{ --cols: 4; }
@media (max-width: 460px){
  .nu-sheet[data-cols="3"], .nu-sheet[data-cols="4"]{ --cols: 2; } }
@media (max-width: 340px){ .nu-sheet{ --cols: 1; } }
.nu-sheet > legend{ padding-inline: var(--s2); font-weight: 600; }
.nu-sheet > .nu-grp, .nu-sheet > .nu-why{ grid-column: 1 / -1; }
.nu-grp{ margin: var(--s2) 0 0; font-size: var(--t2); opacity: .75;
  border-block-end: 1px solid var(--rule); }
.nu-why{ display: block; font-size: var(--t1); opacity: .8; line-height: 1.25; }
/* `.nu-sheets` is NOT on §2.4's table — sheetRow() emits it as the wrapper for
   a row of sheets under one heading (ui/sheets.js:168), and the law is that a
   name a slice needs is added HERE rather than in a second stylesheet. So it is
   added here, and this is the notice. */
.nu-sheets > h3{ font-size: 1rem; margin: var(--s4) 0 var(--s1); }
/* THE OPTION IS THE TARGET, and the radio inside it is CLIPPED, never
   `display:none` (PROGRAM.md §2.3): clipped it still takes focus, still
   answers the arrow keys, and still tells a screen reader what it is. Which
   means the chosen state has to be drawn by the label — the border does it,
   and `is-on`/`is-off` from sheets.js cover the browsers without `:has()`. */
.nu-opt{ display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  min-block-size: var(--tap); line-height: normal;
  /* 2px INK AND NOT 1px GREY (2026-08-27, Enamel): an option is a control and
     a control's frame is ink. The padding pays the pixel back — 2+3 = 1+4 —
     so not one option's box moved; that is the same trade the old is-on rule
     made in the other direction, and it means is-on below no longer needs to
     touch the width at all. */
  background: var(--panel); border: var(--bw) solid var(--ink); border-radius: var(--r1);
  padding: 3px calc(.5em - 1px);
  overflow-wrap: anywhere; }
.nu-opt > input[type=radio], .nu-opt > input[type=checkbox]{
  position: absolute; width: 1px; height: 1px; margin: -1px;
  overflow: hidden; clip-path: inset(50%); }
.nu-opt .nu-w{ font-weight: 600; }
.nu-opt .nu-why{ flex: 1 0 100%; font-weight: 400; }
.nu-opt.is-on, .nu-opt:has(:checked){
  /* CHOSEN IS THE HAND (2026-08-27). This rule used to thicken the border to
     2px ink and repay the padding; the base chip is 2px ink now, so "chosen"
     stops being a weight and becomes the paint it always meant: cobalt frame,
     cobalt wash. Same box to the pixel. */
  border-color: var(--hand); background: var(--hand-tint); }
.nu-opt:has(:focus-visible){ outline: var(--bw-hard) solid var(--hand); outline-offset: 1px; }

/* ===== THE CIRCLE OF FIFTHS ============================================
   (Paul, 2026-08-24: "Maybe put the circle of fifths back in there for key
   selection, it was nice.")

   PORTED, NOT LINKED. These rules are band.css:72-110's `.dcircle / .dopt /
   .dko / .dki`, carried across under this file's own names and this file's own
   custom properties. band.css stays on disk and stays UNLINKED: it belongs to a
   page that is not this one, and mixing another page's state vocabulary into
   the shell sheet is the opposite of keep-like-with-like (the ONE OWNER note at
   the top of this file). What crossed is the geometry, which is the part that
   was never about that page — the arrangement of twelve fifths on a clock face
   is the same arrangement wherever it is drawn. `.nu-circ` / `.nu-circ-face` /
   `.nu-ko` / `.nu-ki` are not on PROGRAM.md §2.4's table, and the law is that a
   name a slice needs is added HERE rather than in a second stylesheet — so they
   are added here, and this is the notice.

   THREE THINGS CHANGED IN THE CROSSING, all of them so the diagram obeys the
   laws THIS page runs on rather than the ones that page ran on.
     1. The positions are `.nu-opt` — the very same label-over-clipped-radio
        every other option on this page is — so the greying, the chosen border,
        the focus ring and the 24px box floor are inherited and not re-stated.
        band.css's `.dopt` rules are therefore NOT ported; they are already here
        under another name, forty lines up.
     2. `#bbb` and `#fff` became `var(--rule)` and `var(--paper)`, so the rings
        and the label backgrounds track the ground in dark mode like everything
        else. A hard-coded white behind twenty-four words is twenty-four white
        stickers on a dark page.
     3. The <fieldset> and the face are two elements rather than one. A
        <legend> is painted into its own parent's border and the diagram needs
        the whole box, so `.nu-circ` is the labelled control and
        `.nu-circ-face` is the square the roundness is measured in.

   AND IT IS THE SECOND PLACE IN THIS FILE THAT SETS A SIZE, for the reason
   `.nu-grid`'s cell is the first: here the size IS the geometry. Every radius
   and ring diameter is in em, so ONE font-size on the face scales the whole
   diagram to fit a 390px phone, and the inner ring's twelve relative minors
   have to be smaller than the outer ring's twelve majors or they collide —
   measured at page-default type they overlap by 5-9px and the ring is
   unreadable. That is a diagram's own scale, like the numbers on an axis, not a
   type hierarchy in the page's text. */
/* A DIAGRAM IS NOT A ROW. Every other control on this page takes the column it
   is given; this one is 21em of geometry and no wider however wide the window
   is, so the box that frames it is capped rather than stretched — a 336px
   circle centred inside a 1280px rule reads as a mistake. `min()` and not a
   media query: at 390px the cap is the column and nothing changes. */
.nu-circ{ border: var(--bw) solid var(--ink); background: var(--panel);
  /* the diagram is a plate like the sheets (2026-08-27), and `--paper` is
     re-declared so the twenty-four labels that punch through the ring strokes
     punch through in the plate's own white */
  --paper: var(--panel);
  padding: 6px; margin-block: var(--s3);
  min-inline-size: 0; max-inline-size: min(100%, 26em); }
.nu-circ > legend{ padding-inline: var(--s2); font-weight: 600; }
.nu-circ > .nu-why{ margin-block: var(--s1) var(--s2); }
/* ===== THE SCALE IS THE COLUMN'S, NOT THE VIEWPORT'S (2026-09-02, wave 4) ==
   `font-size: clamp(.72rem, 3.6vw, 1rem)` stood here and it was calibrated
   against a 56px gutter: `3.6vw` is a fraction of the GLASS, and what the
   diagram has to fit inside is the COLUMN — the glass less the gutter less the
   panel's own inset. Paul, wave 4: *"if it needs to be wider that's fine"*;
   `--tray-w` floors at 96px now, so at 320px the column is 198.4px while
   `3.6vw` still says 11.52px of em, which is 17.2em = 198px of ring plus a
   label on each end. Measured on the rendered page at 320x568: the leftmost
   `.nu-ko` drew at x 94.2 and the gutter's band is 0-96 — a control under the
   stripe, which is test/shell.js A6i and the one thing `body{padding-inline}`
   exists to make impossible.
   THE FACE'S `max-inline-size: 100%` DID NOT SAVE IT and could not: the box is
   clipped to the column, the twenty-four labels are `position: absolute` at a
   radius in `em`, and an em does not know what a max-width is. The scale has to
   be the arithmetic, which is what this is.
   THE ARITHMETIC: the widest thing the diagram draws is `2 x --r` (17.2em)
   plus half a label at each end, and a label is at least 26px wide (the WCAG
   24px floor, stated two rules down) — so ~20em of column buys the whole ring.
   `100vw - --tray-w - 26px` is the column (the panel's `--s4` inset, twice,
   is 25.6px), and dividing by 20 is the em that makes the ring fit it.
   Measured after: 320 -> 9.92px of em, 390 -> 12.76, 430 -> 13.9, 1280 -> the
   1rem cap. The floor is .62rem so a narrower glass than this page is drawn
   for stops shrinking the words rather than shrinking them to nothing. */
.nu-circ-face{ position: relative; inline-size: 21em; block-size: 21em;
  font-size: clamp(.62rem, calc((100vw - 26px) / 20), 1rem);
  margin: 10px auto; max-inline-size: 100%; }
/* THE TWO RING STROKES are the box's own before/after — one stroke per ring,
   each at exactly twice its labels' radius, so the words sit ON the line and
   punch through it with their own background.
   ...AND "PUNCH THROUGH IT" WAS A CLAIM, NOT A FACT, UNTIL 2026-08-25. Paul:
   *"put the notes in the circle of fifths on top of the circles with white
   backgrounds. make them opaque the circles cut through them."* He is
   describing what the sheet actually drew, and the two rings were drawing it
   DIFFERENTLY, which is the tell. `::before` is the face's FIRST child, so the
   twelve outer labels painted over the outer stroke and cut it cleanly.
   `::after` is its LAST child, so the inner stroke painted over the twelve
   relative minors and the line ran straight through every one of the words.
   Measured at 390x844 with both strokes coloured to make them visible: the
   outer radius (8.6em) passes inside all twelve `.nu-ko` boxes and the inner
   radius (5.8em) inside all twelve `.nu-ki` boxes — the geometry was always
   the same for both rings and only the PAINT ORDER differed, and the
   screenshot shows Dm, Am, Em, Bm, F♯m, C♯m, G♯m, D♯m, B♭m, Fm, Cm and Gm all
   struck through.
   The background was already `var(--paper)` and already opaque — what was
   missing was the ORDER, so it is stated: the strokes are z-index 0 and the
   words are z-index 1. One number each, and no element moves.
   ...AND THEY DO NOT TAKE TAPS, which is not a nicety: `::after` is the
   face's LAST child, so it paints over the twelve labels sitting on the inner
   ring, and a transparent background still hit-tests. Measured 2026-08-25, the
   whole inner ring was untappable — playwright reported `<div
   class="nu-circ-face"> intercepts pointer events` on every relative minor.
   A line drawn through a control is decoration; decoration does not get to
   swallow the gesture. */
.nu-circ-face::before, .nu-circ-face::after{ content: ""; position: absolute;
  left: 50%; top: 50%; transform: translate(-50%, -50%); pointer-events: none;
  border: var(--bw) solid var(--rule); border-radius: 50%; z-index: 0; }
.nu-circ-face::before{ inline-size: 17.2em; block-size: 17.2em; }
.nu-circ-face::after { inline-size: 11.6em; block-size: 11.6em; }
/* ONE ROTATION OUT AND ONE BACK. --a is the HOUR and it is the only thing that
   differs word to word; ui/selects.js sets it on the label, where twelve
   numbered classes used to sit in the old file. The second rotate is what keeps
   every word upright: without it the ring reads like a tyre. */
.nu-circ .nu-opt{ position: absolute; left: 50%; top: 50%; margin: 0;
  display: flex; justify-content: center; text-align: center;
  background: var(--paper); z-index: 1;
  transform: translate(-50%, -50%) rotate(var(--a))
             translateY(calc(var(--r) * -1)) rotate(calc(var(--a) * -1)); }
.nu-circ .nu-opt .nu-w{ white-space: nowrap; }
/* THE 44px ROW IS CANCELLED HERE AND THE 24px BOX IS NOT. `label { min-block-size:
   var(--tap) }` at the top of this file is right for a control that owns a row
   and wrong for one of twenty-four positions on a clock: measured, the inner
   ring's hours are 37-43px apart at a phone's type, so 44px boxes would overlap
   each other and the ring would be unreadable and un-tappable both. What must
   NOT go is WCAG 2.2's 24px floor for a radio (test/shell.js A3), so that is
   what is stated — a real number, not an unset. */
.nu-circ .nu-opt{ min-block-size: 26px; min-inline-size: 26px;
  padding: 2px var(--s2); line-height: 1.15; align-items: center; }
/* --r IS AN em AND em IS RESOLVED ON THE ELEMENT THAT USES IT, which is the one
   trap in this whole diagram and it cost a measurement to find. A custom
   property is substituted as TOKENS, not as a resolved length: `--r: 5.4em` set
   on a label whose own `font-size` is `.8em` is 5.4 x (0.8 x the face's em),
   and the inner ring lands 20% closer to the middle than the ring stroke drawn
   at the face's own scale. Measured at 390px: the twelve minors came out on a
   60.6px radius against a 75.6px stroke and the diagonal pairs (Em/Bm, G♯m/D♯m,
   Gm/Dm) overlapped by 4px. So NO POSITIONED LABEL SETS ITS OWN font-size —
   both rings sit at the face's scale, both radii mean the same thing, and the
   inner ring is made smaller by shrinking THE WORD INSIDE it instead. */
.nu-circ .nu-ko{ --r: 8.6em; }
/* ...and the outer ring, where there IS room, keeps the full thumb. */
@media (min-width: 400px){ .nu-circ .nu-ko{ min-block-size: 34px; } }
.nu-circ .nu-ki{ --r: 5.8em; padding-inline: var(--s1); }
/* the inner ring's words are smaller than the outer ring's, which is the one
   thing about this diagram that is not negotiable: at equal type the twelve
   relative minors collide with each other by 5-9px (band.css measured it, and
   so did this file) */
.nu-circ .nu-ki .nu-w{ font-size: .8em; }
/* THE CHOSEN POSITION IS INVERTED (Paul, 2026-08-25: *"when I click then invert
   them"*). Two declarations, and they are not new vocabulary: `.is-now` sixty
   lines up is `background: var(--ink); color: var(--paper)` and is this page's
   only spelling of "this one is on, and you can see it across the room". The
   `.nu-opt.is-on` border rule above still applies and is what a
   forced-colours mode falls back to; the invert is what a reader sees, and on
   a ring of twelve small words a 2px border alone was not enough to find at a
   glance. Both selectors, for the same reason the border rule needs both: the
   class covers the browsers without `:has()`. */
/* ...and this is where those two declarations WERE, until 2026-08-26's audit
   found that `.is-now` — the same two declarations, sixty lines below the top
   of the vocabulary — had no writer left in nukernel/ at all. Rather than keep
   a name with no user beside a copy of what it says, the ring's chosen key was
   added to `.is-now`'s own selector list and this rule was removed. Not one
   pixel moved and not one word of the argument above is withdrawn; what changed
   is that the look is now defined once, which is the whole of what Paul asked
   for on the same day. */
/* A REASON IS THE ONE THING ON THE RING THAT MAY BE WIDER THAN A WORD, and it
   is capped rather than clipped: greyed is not hidden (the note over `.is-off`
   above), so a key that cannot be said says why right there, wrapping, even
   though it makes the diagram lumpy for as long as it is unsayable. Nothing on
   the shipped record ever greys a key — gates.json carries no option rule for
   `alphabet.key` — so this is the rule that keeps the law true rather than the
   rule that draws the page. */
.nu-circ .nu-opt .nu-why{ flex: 1 0 100%; max-inline-size: 9em;
  white-space: normal; }

/* THE MENUS (2026-08-24, evening). `.nu-sel` / `.nu-sels` are the `.nu-sheet` /
   `.nu-sheets` pair one widget over: a settled parameter that was asked back as
   a menu ("We can return some things to select menus … in general where there
   is ONE option a dropdown is preferred"). Not on §2.4's table yet, and the law
   is that a name a slice needs is added HERE rather than in a second
   stylesheet — so it is added here, and this is the notice.

   ALMOST NOTHING IS NEEDED, and that is the argument for the widget: the two
   rules at the top of this file — `select { min-height: var(--tap) }` and
   `label { min-block-size: var(--tap) }` — already give a menu its target size
   and already make its label part of the gesture. What is left is width. */
.nu-sels > h3{ font-size: 1rem; margin: var(--s4) 0 var(--s1); }
/* THE QUESTION IS A LINE OF ITS OWN AND THE CONTROL IS THE LINE UNDER IT.
   Paul, 2026-08-25: "Put interactive elements on new lines below the titles or
   questions."

   THIS RULE IS A REVERSAL AND HERE IS WHAT IT REVERSED, kept because the old
   reasoning was right about the mechanism and only wrong about the taste:
   "THE LABEL AND ITS MENU ARE ONE ROW THAT MAY WRAP. flex, so the word and the
   menu sit on one baseline at 1400px and stack at 320px without either being
   given a fixed width." Every clause of that still describes what happens
   below; what changed is that the wrap is no longer left to the width. It
   ALWAYS wraps, because at 390x844 "what happens to it after it's played?"
   and its menu were sharing 366px — measured, 41 controls at 390 and 56 at
   1280 had their own label's words on the same line as themselves, and at 1280
   there is room, which is exactly why the line was won by the words and the
   menu ended up wherever the sentence stopped.

   ONE LINE DOES IT, AND IT IS THE STYLESHEET'S OWN IDIOM ALREADY: `flex: 1 0
   100%` on the words is what `.nu-opt .nu-why` above has always used to put a
   reason on its own line under an option. The container stays a wrapping row,
   so everything AFTER the words — a menu, or a slider and its readout — stays
   on ONE line together instead of becoming a column of one item each.

   `.nu-field` is ui/eight.js's `number()` and `check()` (a slider with its
   readout, a checkbox with its sentence) and `.nu-sel` is ui/selects.js's
   `selectField`. Two names, one rule, because it is one question: they are
   listed together rather than given a third shared class, which would be a
   name that meant "the other two". */
.nu-sel{ margin-block: var(--s3); }
/* ===== `.nu-field` — THE ONE EVEN-ROW PRIMITIVE (2026-09-02) ==============
   Paul, 2026-09-01: *"things are uneven based on how text wraps."*

   MEASURED BEFORE THIS RULE. A field was `min-block-size: var(--tap)` and a
   wrapping flex, so its height was 44px PLUS however many lines the question
   took — one line for "swing", two for "what happens to it after it's
   played?", three at 320 for the longest of them. Put four fields in a column
   and you get four different heights, and nothing in the column lines up with
   anything in the next.

   THE RECIPE IS NOT NEW, IT IS `.nu-ixli`'s, WHICH IS THE ONE SURFACE ON THIS
   PAGE WHERE EVEN ROWS ARE ALREADY A SOLVED PROBLEM (measured 44.00px, every
   row, one line, at 320/375/390/430/1280 — see its block): a stated height, a
   track that may not grow, and `white-space: nowrap; overflow: hidden;
   text-overflow: ellipsis; min-inline-size: 0` on the text so it CUTS instead
   of wrapping. Here it is two `--tap` rows and no third: the question on line
   one, the control on line two, 88px, always.

   WHAT THE ELLIPSIS COSTS, AND WHY IT IS PAID. A cut question is a question
   you cannot finish reading. Three things make that survivable, and they are
   the same three `.nu-ixli` relies on: the whole string is still in the DOM
   (so it is announced in full, and it is what the gates read back); the
   `<select>` under it is showing the ANSWER, which is usually the shorter and
   more specific half of the fact; and a question is asked once per panel while
   a ragged column is misread every time it is scrolled past. This is the trade
   Paul asked for in the sentence, written down rather than discovered.

   THE 2026-08-25 REVERSAL IS NOT REVERSED: the question still gets its OWN
   line (`flex: 1 0 100%`), which is what "questions on their own line" bought,
   and everything after it still shares the second line, which is what kept a
   slider and its readout together. The line is a fixed 44px now instead of
   however tall the words made it.

   TWO EXEMPTIONS, BOTH OLDER THAN THIS RULE AND BOTH NAMED:
     · `.nu-plate .nu-sel` (the bus gear grid) states `min-block-size: 0` and a
       `.78rem` label because its columns are 108px and its rhythm is the
       rack's, not the form's — it keeps its own height and its own wrap.
     · `.nu-circ .nu-opt` (the ring) cancels the 44px row for 26px for the
       reason written at its own rule. Neither is a `.nu-field`. */
.nu-sel > label, .nu-field > label{
  display: flex; align-items: center; align-content: start;
  /* NO ROW GAP, AND THAT IS THE DIFFERENCE BETWEEN 88 AND 93.6. Measured with
     `gap: var(--s2)` on both axes: every menu field came out 44 + 5.6 + 44 =
     93.6px and the promise of "two tap rows" was 5.6px short of true. The two
     lines need no gap between them because each is a full 44px line box with
     its content centred in it — the air is already inside the rows. The
     COLUMN gap stays: it is what separates a slider from its readout. */
  gap: 0 var(--s2); flex-wrap: wrap;
  line-height: normal;
  min-block-size: calc(2 * var(--tap)); }
.nu-sel > label > .nu-w, .nu-field > label > .nu-w{
  flex: 1 0 100%; font-weight: var(--fw-label);
  block-size: var(--tap); line-height: var(--tap);
  min-inline-size: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* AND THE SECOND LINE IS ONE LINE, WHICH A NATIVE RANGE WAS BREAKING. Measured
   before this rule: the tempo field was 122.2px at BOTH 320 and 1280 — 44 for
   the question, 48 for the slider and 19 for its `<output>` on a THIRD line —
   because `input[type=range]{ inline-size: 100% }` (the one range block, above)
   makes the slider's flex basis the whole line and pushes the readout off it.
   `flex: 1 1 0` gives the slider the line MINUS the readout instead, which is
   the arrangement the 2026-08-25 reversal was protecting ("everything AFTER the
   words stays on ONE line together").
   AND ITS HEIGHT IS `--tap` HERE, NOT `--ctl`. 48 is a slider standing on its
   own — the board's faders, a strip's send — where the four extra pixels are
   the cap's shoulder; seated in an even row it takes the row, because 44 + 48
   is not a `--tap` multiple and one control's shoulder is not worth the whole
   column stepping. Still 44px of thumb, still over the floor. Measured after:
   every field 88.0px at 320 and 1280. */
.nu-sel > label > input[type=range], .nu-field > label > input[type=range]{
  flex: 1 1 0; inline-size: auto; min-inline-size: 0; block-size: var(--tap); }
/* A MENU OF 108 INSTRUMENTS MUST NOT BE 108 INSTRUMENTS WIDE. A <select> sizes
   itself to its longest option, which on `sound.instrument` is a phone and a
   half; the cap makes the browser ellipsise the CLOSED control and leaves the
   open list alone, which is where the long names are actually read. 30ch and
   not 100%: a menu stretched across a 1400px desktop is a 1345px control
   holding one word. `min-inline-size: 0` is what lets it shrink below its
   content on a phone. Measured on the harness: 240px at both 390 and 1400, and
   documentElement.scrollWidth == innerWidth at both. */
.nu-sel > label > .nu-combo{ flex: 0 1 auto; min-inline-size: 0;
  max-inline-size: min(100%, 30ch); }
/* ...EXCEPT A LOZENGE FIELD, WHICH IS NOT A CLOSED CONTROL. The cap above
   ellipsises a one-line widget holding one word; a field of forty-two words
   capped at 30ch is a column of pills three-fifths of the way across the pane,
   which is the same argument `#pan-band .nu-vsheet > .nu-lzfield`'s
   `grid-column: 1 / -1` makes at the other end of this file. */
.nu-sel > label > .nu-combo[data-widget="lozenge"]{ flex: 1 1 100%;
  max-inline-size: 100%; }
/* (`.nu-sel > label > select` HELD THESE THREE DECLARATIONS until 2026-09-02.
   The `<select>` is inside a `<span class="nu-combo">` now, so the cap has to
   be on the box that is actually the flex item; the select fills it, which is
   `.nu-combo > select{ inline-size: 100% }` below. MEASURED after the move, on
   the rendered page: every visible `<select>` 44px tall and 16px, zero under
   either floor, on all eleven tabs at 320 and 1280, and
   `documentElement.scrollWidth === clientWidth` at both — which is the
   promise the paragraph above actually makes. The 30ch cap itself is
   unchanged and untested by this edit: it is the same declaration on a box
   one element out.) */
.nu-sel > .nu-why{ margin-block-start: var(--s1); }
/* ===== `.nu-combo` — THE COMBO BOX (2026-09-02) ===========================
   Paul, 2026-09-01: *"Things like select boxes are very plain and could be
   combo boxes."*

   IT IS STILL A `<select>`, AND THAT IS THE LOAD-BEARING DECISION. The
   2026-08-25 law — A SINGLE-CHOICE CONTROL IS A `<select>`, full stop — is not
   touched: the control the browser gives a thumb is unchanged, `data-sel` /
   `data-k` / `data-v` are unchanged (they are how focus survives `draw()`),
   every gate that counts menus still counts the same 39, and the option list
   is still the browser's own popup, which is the one part of a menu no page
   has ever drawn better. What is added is a WRAPPER, at one call site
   (`ui/selects.js selectField`), and three things the wrapper makes possible:

     THE ARROW. `appearance: none` plus a `::after` triangle, which is what
     `ideal/design-system.html:202-218` has drawn since Enamel and what this
     file could not have because the arrow needs a box to hang on. It is the
     page's own mark in the page's own dim, on the page's own plate, instead of
     six different arrows on six different platforms.
     THE DETENT. `.is-seated` — you are standing where the record put you — and
     `.is-said` — you moved it. `design-system.html:214-215` names both. This
     is the one thing a plain select cannot say and the one thing a composer
     most wants to see across a panel of forty of them: WHICH of these did I
     touch? Dim for the default, hand for a value you set, and the hand is
     exactly right because the hand is "a value you set".
     THE FILTER. Over 24 options (`sound.instrument` is 108) a menu stops being
     a menu and becomes a list you scroll hunting for a word. `.nu-combo-filter`
     is a `<input type=search>` before the select that hides non-matching
     `<option>`s — which is what "combo box" has meant to a hand since 1993.
     It is not a text entry for the VALUE: you cannot type a value that is not
     in the table, because the table is the vocabulary and inventing a word is
     the one thing a settled parameter must not allow.

   THE BOX. `inline-grid` and not `inline-flex`: the filter and the select are
   two full-width rows when both are present and one row when only the select
   is, and a grid says that in one declaration without a `flex-basis: 100%` on
   each child. The triangle hangs off the BOTTOM row — the select is always
   last — so it lands on the menu whether or not there is a filter above it. */
.nu-combo{ position: relative; display: inline-grid; grid-template-columns: 1fr;
  gap: var(--s1); min-inline-size: 0; max-inline-size: 100%; }
.nu-combo > select{
  appearance: none; -webkit-appearance: none;
  inline-size: 100%; min-inline-size: 0;
  /* room for the triangle: the arrow's own column, plus the padding the plate
     already pays on that side, so no option's text ever runs under it */
  padding-inline-end: calc(var(--s3) + 1.1em); }
.nu-combo::after{ content: "\25BE";   /* ▾ — the same symbol stack the marks use */
  position: absolute; inset-inline-end: var(--s3); inset-block-end: 0;
  block-size: var(--tap); display: flex; align-items: center;
  pointer-events: none; color: var(--dim);
  font-family: var(--sym);
  font-size: var(--t2); line-height: 1; }
/* THE DETENT COLOURS. Written on the WRAPPER by `selectField`, on build and on
   every change, from one comparison: the select's value against the option the
   table calls its default (`value === ""` / `"default"`, or the word "—").
   A menu whose table declares NO default detent wears NEITHER class — silence
   rather than a claim, because "you set this" is a fact and a control that
   cannot know it may not assert it. */
.nu-combo.is-seated > select{ color: var(--dim); }
.nu-combo.is-said > select{ color: var(--hand); font-weight: var(--fw-label); }
.nu-combo.is-said::after{ color: var(--hand); }
.nu-combo:has(> select:disabled)::after{ color: var(--faint); }
/* ...AND NO ARROW AT ALL OVER A LOZENGE FIELD (2026-09-05). `menu()` puts
   every widget in this same chassis on purpose — "a menu may not read as a
   different species for being drawn a different way" — but the ▾ is the
   chassis saying "there is a list behind me, tap to open it", and a lozenge
   field has no behind: every word is already on the glass. Paul photographed
   the result on the TIME row's mode picker (2026-09-05): a stray picker arrow
   floating over a field of pills, which reads as a second control on one fact.
   ONE CONTROL, ONE OWNER — and the arrow is the half that goes. */
.nu-combo[data-widget="lozenge"]::after{ content: none; }
.nu-combo[data-widget="lozenge"]{ display: block; }
/* THE FILTER FIELD. 44px like everything else a thumb touches, 16px like
   everything else that takes focus on iOS (`font: inherit` from the top of
   this file gives it that and the type scale may not take it away — see the
   scale's own exemption list), and a `search` type so a phone offers the right
   keyboard and a clear button. It carries no visible label: its `aria-label`
   is "filter <the question>", which the widget writes, because a visible one
   would be a second copy of the question already on line one. */
.nu-combo-filter{ inline-size: 100%; min-inline-size: 0; }
.nu-combo-filter::-webkit-search-cancel-button{ cursor: pointer; }
/* ...and the same cap for a menu in a table cell, where the chord quality went
   ("chord quality can be selects inside the 'the changes' table"). The cell is
   inside a `.nu-pane`, which scrolls sideways, so an unbounded select would
   push the whole chart off the phone rather than be clipped by it.

   AND `100%` WAS NOT A CAP AT ALL, WHICH COST A MEASUREMENT TO FIND. Paul,
   2026-08-25: *"the third column is all the dropdowns"* — so a voice's
   per-section word is a <select> in a table cell now, and the kit's word is a
   list of SIXTY-EIGHT. A `<select>` sizes itself to its widest option, and a
   PERCENTAGE max-width resolves against a table cell whose own width is being
   computed from that select: circular, so it constrains nothing. Measured at
   390x844 on the kit's section table: the select came out 334px inside a 343px
   cell, the table 457px inside a 366px pane, and the "does" column was off the
   right-hand edge — a dropdown you have to swipe to before you can read it is
   not a dropdown in a column. A LENGTH is a real cap: at 9em the same table is
   inside the pane, the select ellipsizes its own text (which is what a browser
   does with a narrow select), and the popup undoes it the moment you open it.
   The chord chart's own quality menus are shorter than 9em and are unmoved.

   AND `inline-size: 100%` JOINED IT 2026-08-28 — the cap says how WIDE a menu
   may get, this says it is as wide as its COLUMN and, more to the point, that
   it may be NARROWER. Paul: *"Make elements like tables and grids 100% wide."*
   A table that is 100% of its column can still be wider than the column if
   nothing in it can shrink, and "the changes" was exactly that: measured at
   390 with the tables widened and nothing else changed, its six columns came
   to 397.9px inside a 366px pane and the chart scrolled 32px sideways inside
   itself. Every other cell there is at its floor — 39px of bar number, 13.5px
   of "i", two 84px sliders (a degree is a PICTURE of a position; shrink it and
   it stops being one, which is the argument three blocks up) and 41px of
   "root". The menu is the one cell with slack, because a <select> is the one
   control that renders its own text short. `inline-size: 100%` with
   `min-inline-size: 0` is what tells the table so: measured after, the quality
   menu is 76.3px, the table 366px, and the pane's scrollWidth equals its
   clientWidth. Above 9em nothing changes — the cap still wins — so on a
   1280px page the menus are the same size they were. */
td > select{ inline-size: 100%; min-inline-size: 0; max-inline-size: 9em; }
/* GREYED, NOT HIDDEN, THE SAME AS EVERYWHERE ELSE HERE — AND WITH NO NEW RULE.
   selects.js puts `is-off` on a refused option and `is-quiet` on an inert one,
   which the two rules already at the top of this file
   (`.is-off, .nu-opt:has(:disabled){ opacity: .45 }` and `.is-quiet{ opacity:
   .7; font-style: italic }`) dress for free, on top of the browser's own grey
   for `option:disabled`. Adding `option:disabled{ opacity: … }` here would be a
   second, higher-specificity opinion about the same pixel. The REASON is in the
   words the option says (ui/selects.js `optionText`) rather than in a
   `.nu-why`, because an <option> may contain nothing but text. */

/* THE BOARD (D3) WAS TWO <table>s UNTIL 2026-08-27 — the tombstone under this
   paragraph has the turn; the paragraph itself is kept as the record of the
   era it describes, in its own words. It read: the board is two tables, each
   in its own pane — the channels, then the
   returns and the main — one column per strip and one row per control, so
   "a horizontally scrolling row of channel strips" is the pane's scroll and a
   fixed column width, not a flex row. Both tables are `.nu-board` and every
   rule below dresses both: the split ui/engineer.js made is which STRIPS and
   which ROWS each table carries, and it deliberately needed no new widget.

   The row-label column rides sticky-left the way the step grid's lane column
   does, because a fader you have scrolled to is useless if you can no longer
   read what row it is — and it does so on BOTH tables, which is why the rule
   below is written against `.nu-board` and not against an id.

   TWO SLICES ASKED THIS FILE FOR SOMETHING ON 2026-08-24 AND ONLY ONE NEEDED A
   RULE, which is worth saying because they touch the same page. The atlas slice
   (STATE.md item 19) took the 760px map floor out of the atlas block at the
   foot of this file; the board slice
   (STATE.md item 20, "twenty-three dropdowns survive, and they are all on the
   board") needed NOTHING NEW — its per-channel `place` and three sends went
   <select> -> <input type=range>, which `.nu-board input[type=range]` below
   already carried, and the master's fifteen menus became `.nu-sheet`s, which
   the sheets block above already styles. So they do not disagree and no rule
   was arbitrated. The consequence for anyone editing below: that range rule is
   now LOAD-BEARING for the channel strip rather than incidental — the board
   measured 557px -> 260px at a 390px viewport when the menus left, because the
   selects were what forced the columns past the declared 92px. */
/* THE TWO TABLES RETIRED 2026-08-27 — the one-board round (ideal/
   one-board.html, binding; Paul: "Vertical space is cheap. Don't use knobs.
   Have simple vertical sliders stacked and labeled"). The board is TALL
   STRIPS now — side by side at 1280 and stacked at 390 for the sixteen hours
   between that round and the tabs of the same evening, ONE STRIP AT A TIME in
   a tab row since (see the block below, which carries Paul's sentence and the
   numbers) — and the rules for the old `.nu-board` grid (92px channel columns, 124px bus columns, the
   nu-edge silkscreen, the sticky row-label column) went with the tables they
   dressed. Every measurement in the paragraphs above stays true OF THE THING
   IT MEASURED; the thing is gone. `.nu-eng` survives as the read-only mirror
   inside a voice's sheet. */
.nu-eng th{ inline-size: 7em; text-align: left; }
.nu-mirror td{ padding-inline: var(--s2); }
.nu-mirror td.is-dflt{ color: var(--dim); }

/* ===== THE ONE BOARD (2026-08-27) ======================================
   Down a strip reads in signal order; the page grows tall, never wide. */
/* ---- ONE STRIP AT A TIME, IN TABS (2026-08-27, Paul: "In the mixing board —
   Put the instruments inside tabs the mixing board instead of stacking them").
   REVERSAL WRITTEN DOWN, NOT DELETED. This was
     `grid-template-columns: repeat(auto-fit, minmax(250px, 1fr))`
   and the strips were "side by side at 1280 and stacked at 390" — every word
   of that was true of the thing it dressed, and the thing is gone. Measured
   on the rendered page the day it changed, the shipped chant grown to seven
   voices: seven strips of ~1,007px each made `#strips` 7,193px at 390 and
   2,092px in four columns at 1280; the document was 16,608px tall. The auto-fit
   grid was not doing badly — there was simply no width at which eight and a
   half screens of console is a mixing board.

   `#strips` holds exactly ONE strip now, so there is nothing to lay out
   ACROSS and the only rule it needs is a WIDTH.

   IT WAS A 780px CAP UNTIL 2026-08-28 and the argument is kept rather than
   deleted, because it was a real one: "780px is the rack's own
   `max-inline-size` two blocks down, chosen so the strip and the buses it
   sends into are the same object on the page rather than two column widths;
   a single strip stretched to a 1280px window would put its fader a screen
   away from its sends. Below 780 the cap is inert and the strip is the column,
   which is what a phone wants anyway."

   Paul, 2026-08-28: *"Make elements like tables and grids 100% wide."* The cap
   was inert at 390 exactly as it claimed, so this changes nothing a phone
   sees; at 1280 it was the one thing on the Mix tab that was NOT the width of
   the column, and the section-automation table directly under it — a table,
   now 100% by the rule at the top of this file — made the mismatch impossible
   to read as anything but an unfinished edge. The old argument's worry is
   answered where it actually lives: the strip's own rows (`.nu-srow-ins`,
   `.nu-sends`, `.nu-fadwrap`) lay their controls out from the left in signal
   order, so a wider strip puts more paper to the RIGHT of the fader, not more
   distance between the fader and the sends above it — and `.nu-sends` and
   `.nu-fadwrap` keep their own 30em caps two blocks down, which are the rules
   that actually hold a BANK of controls at a hand's width and are not touched
   here. Measured after the change at 1280: strip 1256px, the fader 459px
   below the send row and 0px further from it across. */
.nu-strips{ display: grid; inline-size: 100%;
  gap: var(--s4); align-items: start; margin-block: var(--s4); }
.nu-strip{ background: var(--panel); border: var(--bw) solid var(--ink);
  border-radius: var(--r1); box-shadow: var(--shadow);
  padding: var(--s3); display: grid; gap: var(--s3); align-content: start; min-width: 0; }
.nu-strip.is-off{ opacity: .55; }
.nu-sname{ display: block; font-size: var(--t4); font-weight: var(--fw-display);
  text-transform: uppercase; line-height: 1; }
.nu-strip header small{ display: block; color: var(--dim); font-size: .72rem; }
.nu-srow{ border-block-start: var(--bw) solid var(--ink); padding-block-start: var(--s3);
  display: grid; gap: var(--s2); }
/* ---- THE THREE INSERT SLOTS GET THE WIDTH THE TAB BOUGHT (2026-08-27) ----
   This is the point of the tabs and not a decoration of them: a slot carries
   a seat menu, a wet and up to two of the module's own face knobs, and in a
   250px stacked column those four sat one under another with the chip's name
   truncating. With one strip on the page the row has the column (it had the
   780px cap on the day this was written — see `.nu-strips`), so `auto-fit` at
   a 218px floor puts all three slots side by side there. MEASURED, stepping
   the viewport from 360 to 1000 in tens: one column to 480, two from 490,
   three from 720 — so a phone gets them stacked, which is what a phone wants,
   and the 780px strip gets all three in a line. The row is a grid and not a
   flex wrap so the three slots are the SAME WIDTH and (by grid's own default
   stretch) the SAME HEIGHT as each other however many have a chip in: a seat
   that grew when you filled it would move the seat beside it.

   THE FLOOR IS ALSO A THIRD OF THE ROW SINCE 2026-08-28, and it is what makes
   "grids 100% wide" true of this one (Paul, that day). `auto-fit` collapses
   EMPTY tracks and stretches the rest — which is what it was chosen for — but
   the row's label spans `1 / -1`, so every track it lays down has an item in
   it and none of them ever collapse. Measured the moment the strip stopped
   capping at 780: five 242px tracks in a 1238px row, three slots in the first
   three and 490px of ruled nothing to their right. `max(218px, a third of the
   row)` says the same thing in the grid's own terms — a track may never be
   narrower than a third, so the row can never lay down a fourth. Below a
   667px row the third is under 218px, the 218 wins, and every breakpoint the
   paragraph above measured is where it was: stepping the viewport from 360 to
   1000 in tens with the new floor in, one column to 480, two from 490, three
   from 710 — the paragraph above says 720 for the third, measured on the
   capped strip; the one step is the difference and it is recorded rather than
   silently overwritten. */
/* ...AND THE 218px FLOOR IS CAPPED AT THE ROW'S OWN WIDTH (2026-09-03). The
   genre bus took this row the day its three chips became three slots, and the
   bus plate sits ~20px further right than a strip does, so at 320 the floor
   alone (218) pushed the slot to x=341 — test/shell.js A1 read 341 vs 320 on
   the Mix tab. `min(218px, 100%)` is the floor a row can actually afford: at
   every width >= 218 it is the same 218 as before, byte for byte. */
.nu-srow-ins{ grid-template-columns:
  repeat(auto-fit, minmax(max(min(218px, 100%), calc((100% - .8em) / 3)), 1fr)); }
.nu-srow-ins > .nu-rowlab{ grid-column: 1 / -1; }
.nu-rowlab{ font: 700 .68rem/1 var(--mono, monospace); letter-spacing: .1em;
  text-transform: uppercase; margin: 0; }

/* ---- one insert slot ---- */
/* WHAT A SLOT RESERVES, AND THE ONE RESERVATION THAT IS REFUSED, with the
   number. Measured on the rendered page at 390 and at 1280, every chip in the
   FX vocabulary driven through slot 1: an empty slot is 59px (a seat row and
   nothing else), a filled one is 194px with any of the ten chips that carry a
   mix and 197px with `sweep`, whose wet is refused and whose refusal sentence
   is one line taller.

   RESERVED, AT NO COST: the three slots are cells of one grid row and the row
   STRETCHES them (the browser's default `align-items`, said out loud here
   because `align-items: start` on .nu-srow-ins would silently take it away).
   So wherever the slots sit side by side — 720px and up, measured, where the
   row is three columns; 490px and up for two — filling one never changes the
   size of the ones beside it. Nothing moves sideways, ever.

   REFUSED, WITH THE NUMBER: a blanket `min-block-size: 197px` so that an
   EMPTY slot reserves a filled one's box. It was written, measured and taken
   out — at 390 the slots stack one per row, so reserving the body on all
   three made the strip 1,421px where it is 1,007px, 414px of dashed nothing
   on a phone for two seats a hand has not used. What is left moving is the
   194 - 59 = 135px a slot grows when you seat a chip in it, and that movement
   is the gesture's own answer arriving BELOW the menu that caused it — the
   seat row itself, and everything above it, holds still. */
.nu-slot{ background: var(--well); border: var(--bw) solid var(--ink); border-radius: 3px;
  padding: var(--s2); display: grid; gap: var(--s2); align-content: start; }
.nu-slot.is-empty{ border-style: dashed; background: transparent; }
.nu-slot.is-off{ opacity: .6; }
.nu-slotrow{ display: flex; gap: var(--s2); align-items: center; }
.nu-slotn{ font: 700 .75rem var(--mono, monospace); color: var(--on-fill); background: var(--ink);
  inline-size: 22px; block-size: 22px; border-radius: var(--r2); flex: none;
  display: inline-flex; align-items: center; justify-content: center; }
.nu-slot.is-empty .nu-slotn{ background: var(--faint); }
.nu-slotrow select{ flex: 1 1 auto; min-inline-size: 0; max-inline-size: 100%; }
.nu-slotbody{ display: flex; gap: var(--s2); justify-content: space-between;
  align-items: end; flex-wrap: wrap; }

/* ---- THE SLIDER: ONE ANATOMY, TWO AXES -------------------------------
   (2026-08-27, Paul: *"The horizontal sliders should have the same handles
   and vibes as the vertical sliders, just be horizontal."*)

   THIS BLOCK IS THE ONE OWNER OF WHAT A SLIDER LOOKS LIKE. Until today the
   page had two species of slider and the difference was not a decision, it
   was an accident of where each one was written: the board's fader (here)
   was a WELL with a chunky grabbable CAP, and the bench's pitch bar, weight
   bar and the transport's room slider were flat FILLS with a number and NO
   HANDLE AT ALL — measured on the rendered page, `.nu-pb` and `.nu-vab` were
   40px troughs holding a 36px fill and nothing to grab, and `#vol` was the
   browser's own widget wearing `accent-color`. Same job, three faces.

   FOUR PARTS AND NO FIFTH, named ACROSS and ALONG the axis so one set of
   numbers builds both directions (`--sl-trough`, `--sl-cap`, `--sl-grab`,
   `--sl-line` at the root):
     THE TROUGH   a well cut into the plate: `--well`, `--bw` ink, `--r1`.
                  `--sl-trough` (20px) ACROSS. Vertical: 20 wide, 96 or 180
                  tall. Horizontal: 20 tall, centred in the 40px line.
     THE FILL     `--hand`, because it is a value you set. It grows from the
                  anchor — the bottom, the left, or the marked zero — and goes
                  `--faint` when the control is off or the hit is a ghost.
     THE CAP      the panel plate you grab: `--panel`, `--bw` ink, `--r1`,
                  `var(--shadow)`. `--sl-cap` (38px) ACROSS, so it stands 9px
                  proud of the well on each side — that proudness IS the
                  handle, and it is what the horizontal bars were missing.
                  `--sl-grab` (22px) ALONG.
     THE CHANNEL  the native `input[type=range]` underneath: invisible,
                  unpressable, focusable. The keyboard and the screen reader
                  never touch the paint above.

   THE VALUE IS TYPED THE SAME (`--num-fw` mono, tabular) AND SITS WHERE THE
   AXIS LEAVES ROOM, which is the one honest difference between the two
   directions and is written down rather than smoothed over: a standing fader
   prints its value UNDER the track (`.nu-vs-val`), because a column has room
   below it; a laid-down one prints it IN THE CAP (`.nu-pbb`, `.nu-van`),
   because a bench row is 52px tall and has no "below". Measured: the motif
   row's fixed 52px line leaves 46px of content, and a value line under the
   bar would need 40 + 4 + 16 = 60px, which is 8px past the whole row.

   THE TOUCH LAW IS UNTOUCHED. `touch-action` stays declared on the control
   and only the control, the pointer is still captured on pointerdown, and
   nothing here reads or writes a gesture.

   THE FACE, SAID ONCE — every trough on the page, then every cap. The
   per-axis geometry follows each control; the paint is only here. */
.nu-vs-track, .nu-pb, .nu-vab{
  background: var(--well); border: var(--bw) solid var(--ink);
  border-radius: var(--r1); }
.nu-vs-thumb, .nu-pbb, .nu-velA::after{
  position: absolute; background: var(--panel);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  box-shadow: var(--shadow); }

/* ---- and the standing one, which is where the anatomy came from ---- */
.nu-vcol{ display: grid; gap: var(--s1); justify-items: center; min-width: 0; }
.nu-vs{ display: inline-grid; gap: var(--s1); justify-items: center; }
.nu-vs-lab{ font: 700 .62rem/1.2 var(--mono, monospace); letter-spacing: .08em;
  text-transform: uppercase; text-align: center; }
.nu-vs-track{ position: relative; inline-size: var(--sl-trough); block-size: 96px;
  margin-inline: 12px; touch-action: none; cursor: grab; display: block; }
.nu-vs-tall .nu-vs-track{ block-size: 180px; }
.nu-vs-track::before{ content: ""; position: absolute;
  inset: -10px -14px; /* the touch halo: 48px wide, full travel tall */ }
.nu-vs-fill{ position: absolute; left: 0; right: 0; bottom: 0;
  block-size: calc(10px + (100% - 20px) * var(--v, 0)); background: var(--hand); }
.nu-vs-thumb{ left: 50%;
  bottom: calc((100% - var(--sl-grab)) * var(--v, 0));
  transform: translateX(-50%);
  inline-size: var(--sl-cap); block-size: var(--sl-grab); }
/* the grip: the standing cap has no value printed in it, so it says "grab me"
   with a rule across the middle instead */
.nu-vs-thumb::after{ content: ""; position: absolute; left: 7px; right: 7px;
  top: 50%; block-size: var(--bw-hard); margin-top: calc(var(--bw-hard) / -2);
  background: var(--ink); }
.nu-vs-in{ position: absolute; top: -10px; bottom: -10px; left: 50%;
  transform: translateX(-50%); inline-size: 44px; block-size: calc(100% + 20px);
  margin: 0; opacity: 0; pointer-events: none;
  writing-mode: vertical-lr; direction: rtl; }
.nu-vs-track:focus-within{ outline: var(--bw-hard) solid var(--hand); outline-offset: 3px; }
.nu-vs-val{ font: 600 .7rem var(--mono, monospace); font-variant-numeric: tabular-nums;
  text-align: center; max-inline-size: 5.5em; }
.nu-vs.is-off .nu-vs-fill{ background: var(--faint); }
.nu-vs.is-off .nu-vs-track{ border-color: var(--faint); touch-action: auto;
  cursor: default; }
.nu-vs.is-off .nu-vs-thumb{ box-shadow: none; border-color: var(--faint); }
.nu-vs .nu-why{ max-inline-size: 9em; }

/* A BANK, NOT FOUR LOOSE CONTROLS (2026-08-27, with the tabs). `.nu-sends`
   dresses the send row and the EQ row, and `space-between` inside a 250px
   stacked column read as a bank. In the 780px the tab bought it read as four
   isolated sliders 240px apart — measured, the genre send and the main send
   ended up 700px from each other on one row of one strip. The cap puts them
   back at a hand's width; above it the row simply stops growing. (It used to
   end "which is the same thing the rack does with its own 780" — the rack is
   100% since 2026-08-28 and this 30em is now the ONLY rule holding a bank of
   sliders together, which is where that job always belonged: a bank is a
   hand's width because a hand is that wide, not because a strip was.) */
.nu-sends{ display: flex; gap: var(--s1); justify-content: space-between;
  flex-wrap: wrap; max-inline-size: 30em; }

/* ---- pan detents ---- */
.nu-panrow{ display: flex; gap: var(--s1); }
.nu-panbtn{ flex: 1 1 0; min-block-size: 44px;
  font: var(--btn-fw) .8rem var(--mono, monospace);
  background: var(--panel); border: var(--bw) solid var(--ink); border-radius: 3px;
  cursor: pointer; padding: 0; }
.nu-panbtn:hover{ background: var(--flag-tint); }
.nu-panbtn[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill); }
.nu-panword{ margin: 0; font: 600 .72rem var(--mono, monospace); }
.nu-panword.is-dflt{ color: var(--dim); }

/* ---- the fader row ---- */
/* …and the same cap on the fader block, for the same reason: the level, the
   refused meter well and the mute/solo pair are ONE object on a console. */
.nu-fadwrap{ display: flex; gap: var(--s3); align-items: flex-end;
  justify-content: space-between; flex-wrap: wrap; max-inline-size: 30em; }
.nu-duo{ display: grid; gap: var(--s2); align-content: end; }
.nu-tgl{ min-block-size: 44px; min-inline-size: 72px; font-weight: var(--btn-fw);
  background: var(--panel); border: var(--bw) solid var(--ink); border-radius: 4px;
  cursor: pointer; }
.nu-tgl[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill); }
.nu-drive{ display: block; }
.nu-goes{ margin: 0; border-block-start: var(--bw) solid var(--ink);
  padding-block-start: var(--s2); font: 600 .7rem var(--mono, monospace);
  color: var(--dim); }
.nu-goes b{ color: var(--ink); }

/* ---- ONE TAB ROW OVER ONE PANEL (2026-08-27, Paul: "Put the effects buses
   and mains into special tabs after the voices -- now the board is one tabbed
   space that is consistent and easy to understand") ----
   `#boardpanel` holds whichever tab is open — a `.nu-strips` with one strip in
   it, or a `.nu-rack` with one plate. It needs no rule of its own except the
   one thing a swapping container must promise: that it cannot grow sideways
   whatever is put in it (desk-gate G13 measures it at 390 and at 1280). The
   `min-inline-size: 0` is what keeps that promise — a grid/flex child's
   default `auto` minimum is how a wide table pushes a page sideways.
   (`max-inline-size: 780px` stood beside it as "a belt" over two children that
   capped themselves at the same number. Both children are 100% since
   2026-08-28 — Paul: *"Make elements like tables and grids 100% wide."* — so
   the belt now holds the panel narrower than the two things it exists to
   contain, which is the one thing it must not do.) */
.nu-boardpanel{ min-inline-size: 0; }

/* ---- THE SEAM: the bus tabs are their own group, on their own line ----
   The voices and the buses are different kinds of thing (a voice tab is a
   per-instrument gesture, a bus tab a record-level one), and the row says so
   by GROUPING rather than by painting a divider: `role="group"` with its own
   label, `flex-basis: 100%` so it always begins a fresh line. Measured at 390
   on the shipped chant: the four bus buttons plus their three arrows and the
   leading label are wider than the 366px the row has, so the group wraps to
   two lines and would have wrapped there anyway — the break costs nothing and
   buys a boundary that is always in the same place. At 1280: voices on one
   line, the whole series on the next. It WRAPS and never scrolls, which is
   `.nu-row`'s own law inherited (`flex-wrap: wrap`). */
.nu-busgroup{ flex: 0 0 100%; display: flex; flex-wrap: wrap;
  align-items: center; gap: var(--s2); margin-block-start: var(--s1); }
/* the connector between two bus tabs — a row item, not part of a label, so
   the `<mark>` on the open tab cannot swallow it and so it is still a glyph
   between two words with the stylesheet off. */
.nu-tabarrow{ font: 700 .9rem var(--mono, monospace); color: var(--dim); }
/* the sentence the retired top-of-rack connector used to carry, re-homed:
   every strip's four sends land in the stages to its right. */
.nu-seamlab{ font: 700 .62rem var(--mono, monospace); letter-spacing: .06em;
  text-transform: uppercase; color: var(--dim); }

/* ---- the bus rack, in series, top to bottom ----
   `#rack` holds exactly ONE plate since the board tabs (2026-08-27); the grid
   is what it always was and lays out one child perfectly well, and its width
   is the same one `.nu-strips` takes, so the strip and the plates it sends
   into are one object on the page. That shared number was 780px until
   2026-08-28 and is 100% now — Paul: *"Make elements like tables and grids
   100% wide."* The pairing is the point and it is preserved: whatever the
   strip is, the plate under it is. */
.nu-rack{ inline-size: 100%; display: grid; margin-block: var(--s4); }
.nu-plate{ background: var(--panel); border: var(--bw) solid var(--ink);
  border-radius: var(--r1); box-shadow: var(--shadow);
  padding: var(--s4); display: grid; gap: var(--s3); }
.nu-plate.is-off{ opacity: .7; }
.nu-bushead{ display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); align-items: center; }
/* ===== `.nu-namebar` — THE INK PLATE THAT NAMES A BLOCK (2026-09-02) ======
   `ideal/design-system.html:101-105` draws it as `section > h2`: white on jet,
   caps, tracked — "the name plate". `.nu-busname` has been exactly that since
   the board was built, on one surface, under a name that says "bus". Four more
   surfaces want it now (the Rules view's genre name — Paul, 2026-09-01: *"The
   name of the genre should be obvious"* — the Band roster, Structure and
   Produce), so the plate gets the general name and `.nu-busname` becomes what
   it always was: the plate, on the board.

   IT DOES NOT REINSTATE THE HEADING THAT WAS HIDDEN. Every `.nu-ax > h2` is
   `.nu-vh` because the visible name of a panel is the word on its tab (one
   owner per fact, nu.css:788-813, and shell A6 sweeps for a sticky one). A
   namebar names something INSIDE a panel — a bus, a player, a section, the
   genre a rule is about — which the tab does not say and cannot.

   THE SUB-LINE IS OPTIONAL AND IT IS DIM, and it is the one thing the board's
   version never had: a plate that says "LEAD" over "nylon guitar" says two
   different kinds of fact in one block, which is what the nav's own two-line
   mark does (§2.1) and what makes a roster readable without a legend. */
.nu-namebar, .nu-busname{
  display: block; inline-size: fit-content;
  font-family: var(--sans); font-size: var(--t4); font-weight: var(--fw-block);
  line-height: 1; letter-spacing: .08em; text-transform: uppercase;
  color: var(--on-fill); background: var(--ink);
  padding: var(--s3) var(--s4); border-radius: var(--r1); }
.nu-namebar-sub{ display: block; margin-block-start: var(--s1);
  font-size: var(--t1); font-weight: var(--fw-body); letter-spacing: 0;
  text-transform: none;
  /* the second voice, ON the plate: `--dim` is a chassis grey and would
     disappear into jet, so the sub-line is the plate's own white at 72% —
     3.4:1 against #191611, which clears AA for the 11.5px it is set at only
     because it is never the only copy of what it says (the roster's own
     column carries the same word). */
  color: var(--on-fill-dim); }
/* WHAT `.nu-busname` KEPT OF ITS OWN: nothing. It was `.85rem/800/6px`, which
   is `--t4`/`--fw-block`/`--r1` to within the rounding the type scale exists
   to remove — 13.6px becomes 18.4px on the four bus plates. It is allowed to
   grow rather than being pinned at .85rem because the plate is
   `inline-size: fit-content` inside a `flex-wrap: wrap` `.nu-bushead`, so a
   wider plate wraps the head rather than widening the board. NOT RE-MEASURED
   BY THIS ROUND: `desk-gate` G13 (`#boardpanel` must not grow sideways at 390
   or 1280) is the gate that owns that claim and it was not in this slice's
   list. It is named here so the next hand runs it rather than trusting this
   paragraph. */
.nu-busin{ color: var(--dim); font: 600 .72rem var(--mono, monospace); }
.nu-busrow{ display: flex; flex-wrap: wrap; gap: var(--s4); align-items: end; }
.nu-busmodel{ align-self: center; }
.nu-flow{ display: grid; justify-items: center; gap: var(--s1); padding-block: var(--s3); }
.nu-tri{ inline-size: 0; block-size: 0; border-left: 18px solid transparent;
  border-right: 18px solid transparent; border-top: 22px solid var(--ink); }
.nu-flowlab{ font: 700 .68rem var(--mono, monospace); letter-spacing: .08em;
  text-transform: uppercase; }

/* ---- the one measured meter (green = the engine, measured) ----
   `--meter-tint` HAS ITS FIRST USE HERE (2026-09-02). It was declared with the
   other three tints on 2026-08-27 and had ZERO callers on the whole sheet —
   which the colour audit found and which is a small version of the same
   problem the audit was about: the meter is the paint this page is quietest
   in. The WELL is the right first use and nothing else was: a tint is a
   GROUND, and this is the one ground on the page whose whole subject is a
   measured signal. An empty meter now says "this is a meter, and it is
   reading nothing" instead of "this is a well like every other well"; a
   sounding one still says the level in full-strength `--meter` above it, so
   the fill has more contrast to work against, not less. */
.nu-meterwell{ position: relative; display: block; inline-size: 16px;
  block-size: 180px; background: var(--meter-tint); border: var(--bw) solid var(--ink);
  border-radius: var(--r2); overflow: hidden; }
.nu-meterbar{ position: absolute; left: 0; right: 0; bottom: 0; display: block;
  background: var(--meter); block-size: 0; }

/* ---- section automation: the word grid; time runs down ---- */
.nu-trims{ border-collapse: separate; border-spacing: 3px; inline-size: 100%; }
/* the grid declares itself to the clock (data-live="trimrow", engineer.js) but
   the [data-live] PAINT convention (line ~1629, clock-red TEXT for live
   readouts) must not bleed over a whole table of set words: here the clock's
   red is the row mark (`tr.now`, below), and everything said stays ink. */
table.nu-trims{ color: var(--ink); }
.nu-trims thead th{ font: 700 .62rem/1.3 var(--mono, monospace);
  letter-spacing: .06em; text-transform: uppercase; color: var(--dim);
  text-align: center; }
.nu-trims tbody th{ font: 700 .62rem/1.3 var(--mono, monospace);
  letter-spacing: .06em; text-transform: uppercase; text-align: left;
  padding-inline: var(--s2); }
.nu-trims tbody th small{ display: block; color: var(--dim); font-weight: 600; }
.nu-trims td{ padding: 0; }
.nu-trimbtn{ inline-size: 100%; min-block-size: 44px; min-inline-size: 56px;
  border: var(--bw) solid var(--ink); border-radius: 3px;
  font: var(--btn-fw) .72rem var(--mono, monospace); cursor: pointer; padding: 0; }
.nu-trims tr.now td .nu-trimbtn{ border-color: var(--clock); border-width: var(--bw-hard); }
.nu-trims tr.now th{ background: var(--clock-tint); border-radius: 3px; }
.w-out{ background: var(--ink); color: var(--on-fill); }
.w-hush{ background: var(--hand); color: var(--on-fill); }
.w-back{ background: var(--hand-tint); }
.w-mid{ background: var(--panel); color: var(--dim); }
/* DIM IS DERIVED, BRIGHT IS SET — the page's own law, on the word grid
   (2026-08-28). A cell the hand has not touched draws the dB the RECORD deals
   that voice in that section (audio/desk.js derivedTrim), and it must read as
   a reading rather than as a setting: same well as `—`, tabular figures so a
   column of them lines up, and no weight of its own. The moment a word is set
   the cell takes that word's own colour and this class comes off. */
.nu-trimbtn.is-derived{ font-variant-numeric: tabular-nums; font-weight: 400;
                        letter-spacing: 0; opacity: .78; }
.w-fwd{ background: var(--flag-tint); }
.w-lift{ background: var(--flag); font-weight: 800; }
/* A SET CELL KEEPS ITS WORD'S PAINT UNDER THE POINTER. The global
   `button:hover` (line 286, flag-tint, specificity 0-1-1) outranks the word
   classes (0-1-0) — measured 2026-08-27 on a screenshot: hovering `out`
   put its white text on pale yellow. The flag may warn on the UNSET cell
   (w-mid keeps the global hover); a cell that already says a word answers
   the pointer in its own color, one shade harder where the palette has one. */
.nu-trimbtn.w-out:hover{ background: var(--ink); }
.nu-trimbtn.w-hush:hover{ background: var(--lamp-hot); }
.nu-trimbtn.w-back:hover{ background: var(--hand-tint); }
.nu-trimbtn.w-fwd:hover{ background: var(--flag-tint); }
.nu-trimbtn.w-lift:hover{ background: var(--flag); }
.nu-refusals{ line-height: 1.35; }

/* THE SECOND TABLE'S RULES RETIRED WITH THE FIRST'S, 2026-08-27 (the
   one-board round). The 124px bus column, the 250px main, the `.nu-board td`
   select/label/small dressing and the nu-edge silkscreen all dressed cells of
   tables that no longer exist; the rack is `.nu-plate`s in series now (rules
   above). What survives is `.nu-gear` — one strip's own controls flowing to
   the width of the strip — because the plates stack their settled menus in
   exactly the shape the old `its own gear` cell did, and 108px is still the
   narrowest a labelled menu reads at (measured against "wow & flutter"). */
.nu-gear{ display: grid; align-content: start;
  grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); gap: 0 var(--s3); }
.nu-plate .nu-sel{ margin-block: var(--s1) var(--s3); }
.nu-plate .nu-sel > label{ gap: var(--s1); min-block-size: 0; }
/* AND THE PLATE'S LABEL KEEPS ITS OWN WRAP (2026-09-02). The even-row rule
   above gives every `.nu-w` a fixed 44px line and an ellipsis; on a 108px
   column of the gear grid that would cut nearly every word and add 44px per
   control to a rack that is already the densest thing on the page. The plate
   was already exempt from the 44px row (`min-block-size: 0`, one line up) and
   this is the same exemption said about the label. */
.nu-plate .nu-sel > label > .nu-w{ font-size: var(--t1); font-weight: 600;
  block-size: auto; line-height: 1.2; white-space: normal; overflow: visible; }
.nu-plate select, .nu-plate .nu-combo{ inline-size: 100%; max-inline-size: 100%; }
/* THE ROW-GROUP HEADING IS GONE, AND ITS RULE IS DELETED RATHER THAN LEFT DEAD.
   It read: "A ROW-GROUP HEADING INSIDE THE TABLE. A bus has no `place` pot and
   the main has no `→ delay` send, so those cells are blank — which is what a
   real console's master strip looks like beside a channel strip. These two
   bands are what make the blanks read as 'these rows are not about you' instead
   of as holes; without them the grid is right and unreadable." — plus a note
   that the band's `th` was the one `tbody th` on the board that was NOT sticky,
   because it spanned the whole table and a sticky element as wide as its
   scroller only fights the scroll.

   EVERY WORD OF THAT WAS TRUE OF A GRID WITH 99 EMPTY CELLS IN IT. Paul,
   2026-08-25: "Do you want to put the bus and main into their own board so it's
   not all empty" — so the returns and the main are their own table now
   (ui/engineer.js VIEW 2) and the blanks the bands were dressing are down to
   one cell on either board. A band that groups nothing is furniture, and the
   rule went with it; the two CAPTIONS are what name the boards now. */

/* A GROUP IS A DIFFERENT KIND OF STRIP AND THE GRID CANNOT SAY SO. Bus 1 and
   bus 2 are the engine's own accumulators and return to the main; bus 3 and
   bus 4 have no return at all — `engine: null` in fields.js BUSROWS — and their
   sends are summed into whichever bus they are aimed at (audio/desk.js
   `feedSplit`), which is a `<select>` in the column's own gear cell. Four
   identical columns for two different kinds of thing is exactly the "different
   slices, different looks" complaint in reverse: one look for two meanings.

   A WASH AND NOT A COLOUR, and the same 4% `--wash` the section grounds are
   made of, so this is one decision rather than two — it goes to 0 under
   `prefers-contrast: more` and `forced-colors` with them, and the page loses
   nothing when it does, because the `goes to` row already prints the route in
   words. It may not compete with `<mark>` or `.is-now`, and at 4% of CanvasText
   it cannot: the same measurement the section wash makes.

   `data-bus` IS ON THE CELLS BECAUSE ui/engineer.js `cellFor` PUTS IT THERE
   (2026-08-26, one line, at the recipe's request). `:nth-child()` was the other
   way to reach these two columns and it would break the moment a fifth bus
   lands, which is the exact thing the four-bus round was building toward. */
#racktbl th[data-bus="room"], #racktbl th[data-bus="aux"],
#racktbl td[data-bus="room"], #racktbl td[data-bus="aux"]{
  /* A TRANSLUCENT IMAGE AND NOT A BACKGROUND COLOUR, WHICH COST A MEASUREMENT.
     `background-color` replaces the zebra rather than joining it: measured, the
     two group columns came out a flat srgb 0.96 while their neighbours
     alternated 1.00 / 0.93, so the columns that were meant to read as QUIETER
     read as lighter than every other row. A translucent linear-gradient is an
     IMAGE, painted over whatever background-color the cell already has, so a
     zebra row under the wash is 0.89 and a plain one is 0.96 — both a step
     darker than the cell beside them, which is the whole ask. */
  background-image: linear-gradient(
    color-mix(in srgb, var(--ink) var(--wash), transparent) 0 100%); }

/* THE ROUTING LIST IS A LEGEND, NOT A PARAGRAPH. ui/engineer.js prints one
   `<li>` per EDGE on the desk — the main's dry into bus 1, each group's route,
   and the two that are literals in fx_bus.dsp — and a reader scans down the
   left edge looking for the bus they are on, so the disc marker earns the
   browser's own 40px indent and it is the LEADING that wants closing up. It is
   a real `<ul>` and not a definition list of `<span>`s for the reason
   everything here is a real element: it has to read with the stylesheet off. */
.nu-rack ~ ul.nu-hint{ line-height: 1.35; }
.nu-rack ~ ul.nu-hint > li{ margin-block: var(--s2); }

/* THE RECORD-LEVEL BAND — DELETED 2026-08-27 with the last thing in it, and
   the rule is written down rather than left standing over nothing. It read

     .nu-rec{ border-block-start: 1px solid var(--rule);
       padding-block-start: .75em; margin-block-start: .75em; }

   under the argument that "the flow sentence, the character chain, the routing
   note and the edge list are all true of the whole record rather than of one
   strip", plus a MEASURED reason for the band being under the board rather
   than inside a cell: "a `<select multiple>` of eleven chips is eight rows
   tall, and inside the `its own gear` cell it took that row from 334px to
   653px". Three of the four moved out over the following days, and on
   2026-08-27 the fourth — Character — was retired by Paul ("We can get rid of
   Character right?") and dealt to the strips, which left a top rule under an
   empty div. The measurement stands as the reason a tall multiselect never
   goes back in a table cell; the class has nothing left to paint. */

/* THE SENTENCE BETWEEN THE TWO BOARDS — DELETED 2026-08-27, and the reversal
   is written rather than the rule left standing. It read:

     .nu-flow{ margin-block: .9em; padding-inline-start: .7em;
       border-inline-start: var(--bw-hard) solid var(--rule-strong); font-size: .9rem;
       line-height: 1.35; max-inline-size: 46em; }

   with the argument "The split took away the thing that used to say where a
   send goes — a channel and its return were adjacent columns of one grid and
   now they are two tables — so the relationship is words". The two tables
   retired with the one-board round and NOTHING HAS DRAWN THIS ELEMENT SINCE:
   grepped across every .js and .html under nukernel/, the only `.nu-flow` node
   built anywhere is the rack's series connector (ui/engineer.js `flow()`),
   which has its own rule 140 lines up — grid, centered, a triangle and a
   label. Two rules for one class is two owners of one fact, and this one won
   the cascade: MEASURED on the rendered genre plate, every connector was
   wearing a 3px black rule down its left edge and .7em of left padding it
   never asked for, which read as a stray vertical bar beside a centered
   arrow. Found by looking at the plates once they were tabbed. */

/* THE NOTE STACK (D4): the producer's notes read top to bottom, newest last,
   so the table is left-aligned prose with a rule between takes. */
.nu-notes th, .nu-notes td{ text-align: left; }
.nu-notes td:last-child{ text-align: right; }
/* ...AND `text-align` DOES NOT REACH A FLEX CONTAINER'S ITEMS. The "change it"
   cell holds a `.nu-row` now (one name for a strip of buttons), and a strip is
   `display: flex`, so the line above stopped moving those three buttons the
   moment the class landed. `justify-content` is the flex spelling of the same
   intention and it is stated here, beside the rule it repairs, rather than in
   ui/produce.js — the alignment is this file's. */
.nu-notes td:last-child > .nu-row{ justify-content: flex-end; }

/* THE MAP (D6): an inline <svg> that fills the column and keeps its ratio. */
.nu-map{ display: block; inline-size: 100%; block-size: auto; max-inline-size: 100%; }

/* ONE MOTIF, ONE BLOCK (ui/eight.js motifs()). Paul, 2026-08-24: "The motifs
   should stay with their editors!!!" A motif's staves are direct children of
   #staff, in order, and NOT wrapped in a container of their own: the
   frozen-interface gate counts the written measures at `#staff > p > div` and a
   wrapper would put them one level deeper. So the grouping is done here, which
   is where grouping belongs anyway. The block's name carries a rule across the
   column and the air above it, and that is what makes "one block" a thing you
   can see rather than a thing a comment claims.

   THIS SAID "everything about a motif — the composed staves, the written staff,
   the step grid, the designing buttons — is a direct child of #staff", and on
   2026-08-25 half of that stopped being true: the editor moved one level out,
   into the axis directly after #staff, when the designing buttons became
   pictures and seven <svg> icons per motif started being counted by a gate that
   counts staves (`#staff svg`; ui/eight.js materialAxis says it in full). The
   ORDER is unchanged and so is everything this rule draws — staff, then the
   grid that writes it, adjacent — because the block was always a thing you see,
   not a thing the DOM nests.

   AND THERE IS ONLY ONE OF THEM ON THE PAGE NOW (Paul, 2026-08-25: "organize
   the motifs into a table with tabs, each motif a tab, like 'the band'
   section"), which is why `.nu-first` below is the case that always fires. The
   rule is kept for the record whose bank the strip cannot show — there is none
   yet — and because deleting it would make re-stacking the motifs a CSS change
   as well as a JS one.

   `--rule-strong` and not `--rule`: this is the only division inside an axis,
   and it has to read as a stronger break than the <hr>-weight lines around the
   sheets and panes it separates. */
.nu-motif{ margin-block: var(--s5) var(--s1); padding-block-start: var(--s3);
  border-block-start: var(--bw-hard) solid var(--rule-strong); font-weight: 600; }
/* the first block in the axis has the <h3> and the section strip over it
   already; a rule there would be a division between a heading and the thing it
   heads, which is not a division at all */
.nu-motif.nu-first{ margin-block-start: var(--s2); padding-block-start: 0;
  border-block-start: 0; }

/* THE DESIGNING BUTTONS WEAR A SYMBOL, AND THE DRAWING THEY WORE IS GONE.
   TWO INSTRUCTIONS, THE SECOND REVERSING THE FIRST, BOTH QUOTED:

     2026-08-25 (Paul) — "'backwards shift left shift right upside down up a
     step down a step wider' can be icons".
     2026-08-26 (Paul) — "the icons for motifs -- they're too hard to parse.
     just use arrows pointing in opposite directions for widen and so forth.
     Simple unicode symbols or a few of them arranged."

   WHAT STOOD HERE AND WHY IT LOST. The first instruction was answered with a
   MINIATURE OF THE TRANSFORM: an <svg> per button, five note blocks at their
   pitch heights with the original ghosted behind the result, scaled up to
   40x32.5px because at its own 32px a block is four pixels wide "and ten of
   them read as a smudge". The rule that scaled it is quoted in that sentence
   and it is the rule that condemned it: ten marks in two weights of one ink,
   inside a 40px box, is a smudge at 40px too. The claim written here — "the
   picture is the more legible of the two" — was tested on the reader it was
   drawn for and it failed. So the pictures went and the words behind them,
   which never left, are now behind a MARK instead.

   THE WORD IS STILL IN THE DOM. `.nu-vh` inside every one of these buttons, as
   before: with this stylesheet turned off the three rows read as their words in
   order. That is what makes the symbol an addition rather than a replacement,
   and it is the one thing about this block the reversal did not touch.

   THE 44px TAP FLOOR WAS A WIDTH RULE while the rows lived (an <svg> brought
   its own 40px of width; a glyph brings 10-30px, so `←` in a 2px-padded
   button is a 20px target — under half the floor, and `min-inline-size:
   var(--tap)` restored it). That rule, its row-fitting arithmetic and the
   rows themselves are gone — the tombstone below `.nu-tf` carries the whole
   account — and in the gutter the floor is `icon()`'s, stated once for every
   mark.

   AND THE SYMBOL MAY NOT EAT THE TAP: `pointer-events: none`, the same as the
   <svg> carried, for the same half-hour the globe round lost to a decorative
   stroke swallowing every press. */
.nu-tf{ display: inline-block; pointer-events: none;
  /* 1.2rem, NOT 1.3: MEASURED, not chosen. At 1.3 the widest faces (`×2`,
     `▫▪`) came to 53px and the seven-button rhythm row was 341px of ink
     against 366px of column with six .4em gaps to pay for — two lines. At 1.2
     every face fits inside the 44px tap floor it already had to have, the row
     is 7×44 and one line, and the glyph is still half again the page's own
     type size. */
  font-size: 1.2rem; line-height: 1; letter-spacing: -.02em;
  /* the glyphs are geometry, not prose: a serif ↕ next to a sans ↑ is two
     different arrows, and the page's own face is not guaranteed to have any of
     them. `--sym` — nu.css's ONE non-Plex face token, argued at :root — keeps
     all three rows in one hand. (It read "a system-ui stack" and named that
     stack inline here until 2026-09-06; the stack is unchanged, its owner
     moved.) */
  font-family: var(--sym); }
/* ===== `.nu-tf-row` IS A DEAD SELECTOR AND ITS RULES ARE GONE (2026-08-29).
   What stood here: `.nu-tf-row{ gap: .3em }` and `.nu-tf-row button{
   min-inline-size: var(--tap); border-width: 2px }`, plus two paragraphs of
   measured arithmetic — "seven buttons at 332px of ink in a 366px column had
   34px for six gaps … six .4em gaps are 38.4. At .3em they are 28.8 and the
   row is one line" and the border argument "the Enamel button border is 3px
   … fourteen more pixels of border wraps the row to two lines, an 88px strip
   under every motif editor". Every number in that was measured and right,
   ABOUT ROWS THAT NO LONGER EXIST: the two `<p class="nu-row nu-tf-row">`
   bands moved into `#nu-tray` as the `motifops` and `tempo` levels on
   2026-08-28 (ui/eight.js tombstones them at `motifTabRow` and `tempoRow`),
   where a column of 44px marks has no such sum to make. This round's spacing
   audit found the rules by asking the rendered page — zero elements matched
   `.nu-tf-row` at 390 and 1280 — which is the same way it found the export
   cards' styles pointing at `#scoredeck`. A selector with no subject is not
   harmless: the next hand reads it as a promise about the page and re-argues
   its arithmetic, which is exactly what happened for half an hour today.
   `.nu-tf`, `.nu-tf-was` and `.nu-tf-is` below are ALIVE — the glyph faces
   ride inside the gutter's own marks (ui/eight.js `face()`) — so the face
   rules stay and only the row's geometry dies with the row.
   ...AND ONE OF THE TWO ROWS CAME BACK, 2026-09-02, SO THE GEOMETRY DOES TOO.
   Paul, B7: *"The left nav elements for tweaking tempo should be brought
   inside tempo."* The eight tempo operations are a `<p class="nu-row
   nu-tf-row">` in the Tempo panel again, so this selector has a subject once
   more and the audit that found it empty would now find eight marks.
   (`ui/eight.js timeAxis` NAMED THE BUILDER and is deleted, 2026-09-06: the
   row is `tempoNode()` and it is a FIELD of the table's TIME row. Same class,
   same nine marks, same geometry — what it lost is the panel around it.) The rules are NOT restored here — they are written at the
   foot of this file under the slice-2a banner, beside the tap button they
   share a row shape with — and the arithmetic above is re-measured there for
   a panel column rather than for the motif editor's 366px. The `motifops`
   half of the tombstone stands: that row is still a gutter level. */
/* WHERE THE TUNE WAS, AND WHERE THIS BUTTON PUTS IT — the one idea worth
   keeping out of the drawing. A face may be a PAIR (`∧∨`, `■□ → □■`, `♪↑`),
   and the first half is the quiet ink and the second the full one, so the pair
   reads left to right as "this becomes that" instead of as two marks. Two
   weights of ONE ink and never two hues: the pair has to survive a
   forced-colours mode and a monochrome screen, and `--rule` is the same
   38%-of-CanvasText mix the whole file draws its quiet lines with, so both
   track the ground into dark mode with no media query. */
/* ...AND A HAIR OF AIR BETWEEN THE TWO HALVES, which is not decoration: with
   the halves touching, `□■` read as one run of two marks and not as "this
   becomes that". Measured on the row at 390px. */
.nu-tf-was{ color: var(--rule); margin-inline-end: .1em; }
.nu-tf-is{ color: currentColor; }

/* THE CLOCK'S OWN SENTENCES (2026-08-27). The [data-live] law already says
   the clock writes text only inside elements carrying the attribute; Enamel
   adds: those words are CLOCK RED in the machine voice, everywhere, so a
   reader can tell at a glance which text on the page will move without them.
   No size is set — a live span inside the form list's count cell stays the
   cell's size, the score caption stays a caption — only the paint changes.
   NOT THE MONO, AND THAT IS A MEASUREMENT (2026-08-27): the design page sets
   its live specimens in the machine voice, but the audition say-line reserves
   its own minHeight in JavaScript by measuring a sample sentence, and in mono
   that sample wraps at 390 — the reserve went 16px -> 33px and the staff
   block grew 19px, which is the geometry this wave is forbidden to spend.
   The red is what carries the meaning; the face stays the page's — and the
   WEIGHT stays the page's too, for the same 390px measurement: at 600 the
   reserve sentence was one letter over the wrap. Red alone is the signal. */
/* ===== AND IT IS RED ONLY WHEN IT IS SAYING SOMETHING (2026-09-08, §24) =
   Paul: *"The orange is a strange highlight color."*

   IT WAS EVERY SECTION NUMBER IN THE GRID, PERMANENTLY. `[data-live]` is the
   attribute that says *"the clock may write here"*, and the section row head
   wraps its number in one (`<span data-live="count">`) so the playhead can put
   a `<mark>` in it. This rule painted the ATTRIBUTE rather than the STATE, so
   all thirteen numbers on a thirteen-section record stood in the playhead's
   red at rest — a highlight highlighting everything, which is a highlight
   highlighting nothing. And when a row actually began sounding there was no
   colour left to say so.
   THE STATE IS THE `<mark>`, and it always was: `mark()` is the one write the
   clock is allowed to make, and `:has(mark)` is how the row already reads its
   own playhead two hundred lines down. So the red goes where the mark is. A
   live element with nothing to say is INK, like every other resting readout on
   this page, and the day it has something to say it turns.
   NOTHING IN THE JAVASCRIPT CHANGED, which is the same sentence the row's own
   tint made when it was written: this is paint following a write that was
   already happening. */
[data-live]:has(mark), [data-live] mark{ color: var(--clock); }
/* ...and a hint INSIDE a live element is still the clock's sentence — the
   score caption is `.nu-hint`, whose dim would otherwise beat the inherited
   red. A live subtree holds no control by law, so the selector cannot leak. */
[data-live] .nu-hint{ color: var(--clock); }
/* …BUT THE ENGRAVING IS NOT A SENTENCE (2026-08-27, the deck wave, measured
   on the rendered page): abcjs draws in currentColor, so the rule above was
   painting the WHOLE score — every staff line, every notehead — clock red,
   which drowned the one red that means something. score-deck.html decision 1
   is binding: the ink is ink, and the sounding note's clock red is the ONLY
   "now" on the surface. The ribbon (music + pinned gutter) takes the ink
   back; the caption and the syllable line above and below it stay the
   clock's. */
.nu-score .nu-ribbon{ color: var(--ink); }

/* THE ENGRAVING. abcjs draws an <svg> at whatever width it was handed; on a
   phone that is wider than the page, and an overflowing staff would put the
   sideways scroll straight back on the body — the one thing this file exists
   to prevent. */
#staff svg, #app svg{ max-inline-size: 100%; }
/* ...AND IT SITS ON A PANEL (2026-08-27). abcjs engraves in black ink and
   nothing tells it about a section wash, so the staff and the score box are
   white plates: full contrast for the engraving whatever the ground under
   them is doing. `--paper` is re-declared the way the tables do it, so the
   score's pinned gutter — which must be OPAQUE in exactly the box's colour or
   the music slides through the voice names — follows without a second rule.
   Backgrounds only: their heights are reserved in JavaScript (scoreReserveTo)
   and a border here would be a geometry change this wave is not allowed. */
#staff, .nu-score{ background: var(--panel); --paper: var(--panel);
  /* ...at the OLD leading (2026-08-27): the say-lines between staves have
     JavaScript-reserved minHeights measured under the browser's default
     line-height, and Enamel's 1.45 body leading grew the staff block 33px at
     390 — the clock moving the page, exactly what the reserves exist to
     forbid. 1.2 is the default leading said out loud. */
  line-height: 1.2; }

/* THE SCORE (2026-08-25, Paul: "add a section ABOVE motifs which is the current
   playing music, two measures at a time, but ALL", and the same evening:
   "scroll the score horizontally as the song progresses"). One system of
   staves, one per voice of the record, MOVING, and NOT ONE CONTROL — so there
   is almost nothing for a stylesheet to do here and that is the point.

   THE RULE UNDER IT is the same division `.nu-motif` draws over a motif: the
   score and the motifs are two different things to look at, and the eye needs
   to be told where one stops. It is drawn BELOW the block rather than above,
   because the <h3> already opens it.

   THE ROOM IS RESERVED IN JAVASCRIPT AND NOT HERE, and that is deliberate: how
   tall a system of seven staves is, is a fact about this record at
   this width that no stylesheet can know (ui/eight.js `scoreReserveTo`, and
   the same argument `reserveCaption` makes one surface down). What this file
   guarantees is only that the reserve has somewhere to land: the host is a
   block, and the <svg> inside it never exceeds the column. */
.nu-score{ margin-block: var(--s1) var(--s5); padding-block-end: var(--s3);
  border-block-end: var(--bw-hard) solid var(--rule-strong); }
/* AND THE BOX IS SEALED OFF FROM THE PAGE'S OWN LAYOUT. `contain: layout paint`
   is the browser's word for "nothing inside this element can change anything
   outside it", and here it is simply TRUE: ui/eight.js gives the host a fixed
   height (`scoreReserveTo`) and the system is scaled to fit inside it, so the
   engraving cannot move a pixel of the page even in principle. Saying so lets
   the browser skip re-laying-out a 16,000px document every time four new bars
   are drawn — measured 2026-08-25 on the eight-voice record at 390px, an
   engraving is where this page's only long main-thread tasks live. */
.nu-score > div{ display: block; contain: layout paint; }
/* ---------- THE SCROLLING SCORE (2026-08-25 evening, rewritten 08-26) ------
   Paul, 2026-08-25: "Switching measures is super jumpy so I thought you could
   [neatly] render it and scroll the score horizontally as the song
   progresses." Paul, 2026-08-26: "i think you probably have to fully render
   the whole score each time something changes… you can pop up a loader" and
   "Just scroll at a steady speed BUT highlight notes in red. Get rid of the
   playhead."

   WHAT THESE RULES REPLACED, kept because its measurement is still the reason
   for `contain` above. Two hosts were stacked in one grid cell — one visible,
   one hidden — and the barline was a class swap between them:

     *".nu-score .nu-stack{ display: grid } … > div{ grid-area: 1/1 } …
     > div.is-off{ visibility: hidden }". The selector had to beat
     `.nu-score > div`'s `display:block` (0,1,1 against 0,1,0) and it cost a
     measurement to find: laid out as blocks the two buffers sat one UNDER the
     other, the score box went 165px -> 329px the first time the second one
     engraved, and `#ax-band` was pushed 164px down the page WHILE PLAYING.*

   That box became a WINDOW rather than a stack: one fixed-size hole in the
   page with the engraved music inside it, moved by a transform. THE MUSIC
   INSIDE IT WAS A ROW OF TILES for one day, and `.nu-tile` was this file's
   name for one of them; that is gone, and the rule that cropped their margins
   with it, because separately-engraved tiles are what "the music gets out of
   alignment as it scrolls" WAS (ui/eight.js has the 161.9px / 213.8px
   measurement). There is one picture now, `.nu-paper`, and one crop.

   The height law is unchanged and is still the reason this block exists —
   ui/eight.js gives the box its height and the picture is scaled into it, so
   nothing here can move a pixel of the page. THE LOADER OBEYS IT TOO: it is
   positioned out of the flow, so a box that is waiting is exactly as tall as a
   box that is not.

   THERE IS NO SCROLL CONTAINER, AND THAT IS DELIBERATE. This repo has paid
   twice for a horizontal pane inside a vertical page: the atlas's
   `touch-action: pan-y` left 28 of 62 places unreachable, and the grid
   rotation existed partly to delete a horizontal pane that made tapping snap.
   `overflow: hidden` (not `auto`, not `scroll`) means a finger dragged over
   the score — in any direction — is a finger dragged over a picture, and the
   page scrolls under it exactly as it does everywhere else. The score's own
   motion belongs to the clock.

   WITH THE STYLESHEET OFF, the picture falls back to a static block and the
   whole record's notation is simply laid out down the page, which reads.
   Nothing here carries meaning: the caption above the box says which section
   and which bars, in words, and the sounding notes are red by an attribute on
   the notehead rather than by anything in this file. */
.nu-score .nu-ribbon{ position: relative; overflow: hidden;
  min-block-size: 3em; contain: layout paint; }
.nu-score .nu-run{ position: absolute; inset-block-start: 0; inset-inline-start: 0;
  /* the picture is only ever moved, never re-laid-out: `will-change` lets the
     browser keep it on its own compositor layer, which is what makes a frame
     cost a matrix multiply instead of a paint */
  will-change: transform; }
/* …AND IT DIMS WHILE THE BOX IS ENGRAVING. The music under a loader is the
   PREVIOUS record's — true a moment ago and not true now — so it is greyed by
   the same means every other refusal on this page is greyed, and the word on
   top says why. No transition: the render is about to block the main thread
   and an animation that cannot run is a stutter. */
.nu-score .nu-run.is-waiting{ opacity: .3; }
.nu-score .nu-paper{ position: absolute; inset-block-start: 0;
  /* THE CROP. Everything left of the first notehead — the voice names, the
     clef, the key, the meter — is paper with no time in it; ui/eight.js pushes
     the drawing left by exactly that margin and pins a copy of it in the
     gutter, and this is what hides the original. */
  overflow: hidden; }
/* …AND THE MARGIN ITSELF, DRAWN ONCE AND PINNED TO THE LEFT EDGE. A clone of
   the engraving's own left margin (ui/eight.js `gutterFrom`), so the clefs it
   shows are the clefs the music behind it is written in — and the one thing on
   this surface that never moves. On a picture that is one system long it is
   also the ONLY clef there is after the first four bars have scrolled away. It
   needs the page's own paper behind it, or the music would slide through the
   voice names. */
.nu-score .nu-gutter{ position: absolute; inset-block-start: 0;
  inset-inline-start: 0; overflow: hidden; background: var(--paper);
  pointer-events: none; }
#app .nu-score .nu-gutter > svg,
#scoredeck .nu-score .nu-gutter > svg{ display: block; max-inline-size: none; }
/* AND THE PICTURE MAY BE WIDER THAN THE COLUMN, which is the one place on this
   page where that is true and has to out-shout `#app svg` (an id, so it wins
   on specificity unless this one carries the id too). It is a whole record
   engraved as one line — tens of thousands of pixels of it — and it is the box
   that clips, not the picture. */
#app .nu-score .nu-paper > svg,
#scoredeck .nu-score .nu-paper > svg{ display: block; max-inline-size: none; }
/* THE LOADER: A WORD, NOT A BOX. It says the page is working and it is the one
   thing on this surface that is neither music nor a sentence about music, so
   it is set in the page's own quiet ink over the page's own paper, centred in
   the hole. `pointer-events: none` because there is nothing to press — a
   finger dragged across a waiting score still scrolls the page, exactly as it
   does across a playing one. */
.nu-score .nu-load{ position: absolute; inset: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--t2); opacity: .85;
  background: color-mix(in srgb, var(--paper) 70%, transparent);
  pointer-events: none; }
/* …AND `hidden` HAS TO BE SAID HERE, WHICH IS NOT PEDANTRY. The `hidden`
   ATTRIBUTE is only a UA-stylesheet rule (`[hidden]{ display: none }`), and any
   author `display` beats it — so the line above, which is what centres the
   word, was ALSO what stopped `load.hidden = true` from ever hiding it.
   Measured on the rendered page 2026-08-26: `__eightScore().loading` said
   false, `load.hidden` said true, and the screenshot said "engraving the whole
   score…" across the middle of a picture that had finished engraving nine
   seconds earlier. The DOM was right and the page was wrong; this is the line
   that makes them agree. */
.nu-score .nu-load[hidden]{ display: none; }
/* ===== THE SCORE DECK (2026-08-27, nukernel/ideal/score-deck.html) ========
   The engraved score MOVED to the foot of the page — every .nu-score rule
   above still applies to it verbatim at the new address (#scoredeck) — and
   these rules are only what the deck ADDS: the motif-bracket layer riding the
   moving paper, the vertical piano roll's fixed window, and the export cards.
   The tabs need nothing: they are plain buttons, and the page's own
   `button[aria-pressed="true"]` hand-cap is the whole of "which view is up". */
/* (`#scoredeck > .nu-ax{ --sec: 3 }` stood here — "the score deck wears Sheet
   music's wash", from the day the score moved out of the Material axis and
   wanted to look like it had not. The ordinal lives on the PANEL now and the
   score deck is the eighth of nine, so it has its own hue and needs no
   borrowed one: see the wash block above.) */
#scoredeck .nu-decktabs{ align-items: center; margin-block: var(--s2) var(--s3); }
/* ...AND IT SHRINKS RATHER THAN OVERHANGS, 2026-09-09. The row got its two
   view buttons back (they were a level of the deleted gutter for eleven days),
   so the legend is no longer the only item on the line: at 390 the pair takes
   96px and `margin-inline-start: auto` pushed a 300px legend off the right of
   the sheet. `min-inline-size: 0` is what lets a flex item narrower than its
   content wrap instead of overflowing — the legend already wraps INSIDE
   itself, it simply was never allowed to be narrow enough to need to. */
#scoredeck .nu-decklegend{ display: inline-flex; flex-wrap: wrap; gap: var(--s4);
  margin-inline-start: auto; min-inline-size: 0; flex: 0 1 auto;
  font-size: var(--t2); opacity: .85; }
#scoredeck .nu-legv{ display: inline-flex; align-items: center; gap: var(--s2); }
/* THE LEGEND CHIP IS THE PAGE'S FIRST CATEGORY SURFACE, AND ITS HUE IS WRITTEN
   BY THE JAVASCRIPT (`ui/eight.js:4001`, `chip.style.background =
   voicePaint(vi)`) — an inline style, which no rule in this file can reach.
   That is the correct division as long as `voicePaint` is the one owner of
   "which player is which colour", and it is; what this rule owns is the chip's
   SHAPE, and the shape is what makes an arbitrary hue safe to put on this
   page: 11px square, ink-bordered on all four sides, with the voice's own NAME
   in the text node beside it. See the CATEGORY family at the top of this file
   — rule 1 is exactly this chip.
   THE SEAM IS CLOSED (2026-09-02, slice 2c). It read: *"`voicePaint` still
   returns the FOUR PAINTS (ink for drums, meter for bass, hand/flag
   alternating for the melodic lanes), which is why the roll currently draws
   two players in the same cobalt. Pointing it at `--v0..--v3/--vb/--drum` is
   one function in `ui/eight.js` and it is that file's agent's edit, not this
   one's; the tokens and `.nu-vpaint` are here and waiting."* It is that one
   function: `ui/eight.js paints()` reads all six tokens and `voicePaint(vi)`
   answers `cat[vpaintOf(vi)]`, off the SAME slot table the `[data-vi]` rules
   below use — so the roll's blocks, the legend's chips, the score's caps, the
   roster's boxes and the motif panel's reader chips are one colour per player
   on five surfaces. */
#scoredeck .nu-legv > i{ inline-size: 11px; block-size: 11px; border-radius: var(--r2);
  border: var(--bw) solid var(--ink); display: inline-block; }
/* THE MOTIF NAMES, UNDER EACH PART (2026-08-27, Paul: "The names of the
   motifs should appear in the score below the parts when they are used as
   notes"). A layer INSIDE the moving run, so a label travels with the bars it
   names. Text is EXTRACTED (a material.cells key, ui/eight.js `motifLabels`)
   and the layer holds no control — it lives inside [data-live="score"].

   WHAT CHANGED FROM THE BRACKET IT REPLACES: the layer is the full height of
   the run instead of a 16px strip across the top, because a label is now
   placed at its own PART's staff (ui/eight.js sets `top` per label off
   abcjs's own staff geometry) rather than over the system; and the mark is
   lighter — a 2px rule with the cap hanging under it, 15px in all, which is
   what fits between two staves and what SCORE_MOT_H reserves under the last
   one.

   ONE PAINT FOR EVERY PART — REVERSED 2026-09-02. What stood here: *"ONE PAINT
   for every part, deliberately: the label hangs 2px under its own staff and
   some 34px above the next one, so position already says whose it is, and the
   voice paints do not all take white text (--flag is a yellow; the roll's
   legend chips can wear it because they carry ink borders)."*

   BOTH HALVES OF THAT ARGUMENT WERE TRUE AND THE SECOND ONE IS WHY IT IS
   REVERSABLE NOW. Paul, 2026-09-01: *"The design system is not consistent. It
   uses very little color."* The reason the score could not colour its caps by
   voice was that the only palette available was the FOUR PAINTS, and `--flag`
   is a yellow that white text disappears into — a real measurement, and it is
   what kept five surfaces monochrome. There is a CATEGORY family now (see the
   top of this file): six hues chosen for exactly this job, every one of them
   dark enough to take white ink, none of them a state. The second half of the
   old argument stands unchanged and is why the cap can afford a hue at all:
   position already says whose the label is, so the colour is a SECOND reading
   of the same fact and nothing is lost if a reader cannot tell violet from
   magenta at 9px.

   HOW IT ARRIVES, AND IT HAS ARRIVED (2026-09-02, slice 2c). The paragraph
   here read: *"The cap reads `var(--vpaint, …)` with the old `--hand` as its
   fallback, so the moment an ancestor (or the cap itself) carries `data-vi`,
   the score is coloured by player and this file needs no further edit.
   `ui/eight.js motifLabels` writes `b.dataset.v = v.name` today and not the
   INDEX; adding `b.dataset.vi = vi` on the same line is the one edit that
   turns this on… Until it lands every cap is hand-blue."* `motifLabels` writes
   it now, on that same line, and what it writes is the SLOT (`vpaintOf`) and
   not the raw index — so the kit is graphite here and graphite in the roster.
   The fallback stays, because a cap drawn before a record has voices still has
   to have an ink. */
#scoredeck .nu-mots{ position: absolute; inset-block-start: 0;
  inset-inline-start: 0; pointer-events: none; }
#scoredeck .nu-mot{ position: absolute; block-size: 12px;
  border-block-start: var(--bw-hard) solid var(--vpaint, var(--hand));
  border-inline-start: var(--bw-hard) solid var(--vpaint, var(--hand));
  border-inline-end: var(--bw-hard) solid var(--vpaint, var(--hand)); }
/* the cap's `left` is written per cap by ui/eight.js (the name is repeated
   every screenful along the rule), so nothing here may state one */
#scoredeck .nu-mot > b{ position: absolute; inset-block-start: 1px;
  white-space: nowrap;
  font: var(--fw-label) 9px/1.4 var(--mono); letter-spacing: .04em;
  background: var(--vpaint, var(--hand)); color: var(--on-fill);
  padding: 0 4px; border-radius: var(--r2); }
/* …and the last cap on a rule hangs from the section's END rather than its
   start, so it cannot collide with the next section's first cap. A translate
   and not a measurement: how wide a word is set is the browser's fact. */
#scoredeck .nu-mot > b.nu-end{ transform: translateX(-100%); }
/* THE KEY, ABOVE THE SCORE (Paul, 2026-08-27: "maybe we put the key above the
   score"). One quiet line stating the RECORD — key, mode and meter in the
   Alphabet and Time axes' own words (ui/eight.js `keyLine`). It is NOT inside
   [data-live], so it does not take the clock's red the way the caption under
   it does: it is the same sentence for the whole record, and it takes its one
   line of height at creation like everything else on this surface. */
#scoredeck .nu-keyline{ margin-block: var(--s1) .1em; font-weight: 700;
  color: var(--ink); }
/* THE PIANO ROLL: time runs DOWN through a fixed window; the canvas repaints
   on the clock inside [data-live="roll"], so the box is sealed exactly the way
   the score's is — fixed height, contain, no scroll container, no control. */
#scoredeck .nu-roll{ margin-block: var(--s1) var(--s5); padding-block-end: var(--s3);
  border-block-end: var(--bw-hard) solid var(--rule-strong); }
#scoredeck .nu-rollwin{ position: relative; block-size: min(420px, 60vh);
  background: var(--panel); border: var(--bw) solid var(--rule-strong);
  overflow: hidden; contain: layout paint; }
#scoredeck .nu-rollwin > canvas{ display: block; inline-size: 100%;
  block-size: 100%; }
/* THE EXPORT ROW: four cards, each button wearing its true state — a live
   button, or `disabled` beside its .nu-why (the no-dead-controls law).
   ===== `#exportdeck`, NOT `#scoredeck` (2026-08-29) ======================
   Every selector in this block read `#scoredeck` until today, and the cards
   have not rendered there since 2026-08-27, when Paul named Export its own
   tab and ui/eight.js started building them into `#exportdeck`. The styles
   never followed the move, so the shipped Export tab was six naked <b>s run
   into their titles — the rendered page read "URLthe link", "MP3MP3" — while
   every rule that would have drawn the cards sat here matching nothing.
   Classic seam: correct in source, dead on the artifact (memory: "test the
   artifact"). Measured before this edit at 390px: zero elements matched
   `#scoredeck .nu-exp`.
   ===== AND THE CAPS LOST THEIR THREE PAINTS, the same day ================
   What stood here: `.nu-cap-hand{ background: var(--hand) }`,
   `.nu-cap-meter{ … --meter }`, `.nu-cap-flag{ … --flag }` — URL and ALS
   wore the hand, WAV the meter, MID and MP3 the flag. The palette's own law
   (":root — the four paints: semantic, never decorative, never mixed") says
   the hand is a value you set, the meter is measured signal, the flag is
   about-to-matter; a file format is none of those, so five caps in three
   state-paints were teaching the reader a false vocabulary at the exact
   moment the page wants those colours to mean something. A cap is a NAME
   PLATE, and the page already has a paint for a name plate: full ink, white
   text — the same plate the board's `.nu-busname` wears. One rule, no
   classes to keep in step. */
/* `minmax(min(230px, 100%), 1fr)` AND NOT `minmax(230px, 1fr)` (2026-09-02,
   wave 4). A bare 230px track floor cannot shrink below 230px, so on a column
   narrower than that the grid is wider than its parent and the DOCUMENT
   scrolls sideways — which is the one thing this page may never do. It was
   invisible until the gutter grew: Paul, *"if it needs to be wider that's
   fine"*, `--tray-w` floors at 96px, and a 320px phone's axis is 198.4px.
   Measured before: `documentElement.scrollWidth - clientWidth` = 19px on the
   Export tab at 320x568. `min(230px, 100%)` says "230, or the whole column if
   that is narrower", which is what "at least this wide" always meant. */
#exportdeck .nu-exports{ display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  margin-block: var(--s4); }
#exportdeck .nu-exp{ border: var(--bw) solid var(--rule-strong); border-radius: 6px;
  background: var(--panel); padding: var(--s3) var(--s4); display: flex;
  flex-direction: column; gap: var(--s2); }
#exportdeck .nu-exp-head{ display: flex; align-items: center; gap: var(--s3);
  font-weight: var(--fw-label); }
#exportdeck .nu-glyph{ display: inline-flex; align-items: center;
  justify-content: center; inline-size: 44px; block-size: 32px;
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  font: var(--fw-block) 11px/1 var(--mono); flex: none;
  background: var(--ink); color: var(--on-fill); }
#exportdeck .nu-exp > button{ margin-block-start: auto; }

/* THE PLAYHEAD LINE IS GONE (Paul, 2026-08-26: "Get rid of the playhead"), and
   with it `.nu-score .nu-head` — a 2px `color-mix` rule with a
   `forced-colors` fallback that drew a vertical line at 28% of the box. It was
   honest only while the scroll was the music's own; over a STEADY scroll a
   fixed line points at a place, not at a note. What says "here" now is the red
   notehead, which ui/eight.js writes as a `fill` ATTRIBUTE on the note itself —
   presentation carried on the element, the way SVG has always coloured itself,
   and one less thing in this file that can lie. */

/* ===== THE ATLAS (D6) ==================================================
   Paul, 2026-08-24: "a slider for time and a world map on top." Every rule
   below is either a tap target, a scroll container or a system colour; the
   <svg> already carries presentation attributes for fill and stroke, so with
   this stylesheet disabled the map still draws — it just stops being the
   width of the column. */
/* WHAT WENT, AND THE SENTENCE THAT TOOK IT. Paul, 2026-08-24: "Get rid of all
   ux for navigating except for the 'when' slider which should go across the
   whole screen and the 3d globe. get rid of the era select boxes, the look at
   select box, the 'nearby' select box, the genre list, etc." Seven rules stood
   here for controls that no longer exist and are not hidden but DELETED:
   `#atlas summary` (the disclosure — "Don't make 'Details' collapsible", so
   index.html is a <section> now), `#atlasCtl label`, `#atlasCtl #atlasYear`,
   `#atlasPlace h3`, `#atlasPlace button`, `#atlasPlace .inview` and
   `#atlasList`. What is left below is two controls. */

/* THE SLIDER GOES ACROSS THE WHOLE SCREEN — Paul, in the same sentence: "the
   'when' slider which should go across the whole screen". Full-bleed the way
   .nu-bar already does it at :112 — cancel the body gutter and re-pay it as
   padding — and NOT 100vw: vw includes the scrollbar gutter, and a sideways
   scroll on the body is the one thing this file exists to prevent.
   THIS SAID "UNDER the globe, not over it: the globe is the figure and the
   slider is its scale, and on a phone your hand rests at the bottom of the
   screen." That was a good argument about a thumb and it lost to a MEASURED one
   about a keyboard, so the slider ships ABOVE the globe: DOM order is reading
   order is tab order, and with the slider second a reader met the earth and its
   nineteen places BEFORE the control that decides which nineteen — measured,
   Tab from the slider landed on "another take" with every place behind it,
   reachable only by Shift-Tab. The ergonomic point survives anyway: this section
   sits in a long scrolling document, so where the slider falls under your thumb
   is decided by the scroll and not by this rule. Nothing here depends on the
   order — it is full-bleed either way — and the reason is written down so the
   next hand does not swap them back for the argument that was already tried. */
/* ===== THE WHEN SLIDER'S FOUR RULES ARE GONE, 2026-08-29 ===============
   Paul: *"Get rid of the time slider."* `#atlasWhen`, its full-bleed
   margin/padding pair, `#atlasWhen label`, `#atlasWhen #atlasYear` and
   `#atlasYearOut` are deleted with the control, and so is the 52px/34x32
   thumb block that stood at the foot of this file under THE TWO SLIDERS THAT
   ARE THE BROWSER'S.

   WHAT THEY BOUGHT, KEPT AS THE HISTORY OF THE DECISION: the label and the
   readout sat ABOVE the track so the input could go across the whole screen
   ("the 'when' slider which should go across the whole screen" is a sentence
   about the track, and a label and an output beside it were eating 38% of it —
   measured at 390px, the range was 226 of 366 CSS px). The full bleed was the
   same cancel-and-re-pay the body gutter gets everywhere else on this page.

   THE YEAR IS STILL A FACT AND STILL PRINTED — it opens #atlasSay's sentence,
   "1969 · 32 records within ten years · …" — and what moves it is `#atlasIndex`
   below, which is the instrument now. ui/atlas.js carries the argument. */

/* THE 760 WAS A WIDTH, THEN A SCALE, AND NOW IT IS NEITHER — the second
   reversal of the same rule, and both belong in it.
   FIRST: "350 degrees of longitude under 760px is unreadable, so the map keeps
   a floor and the sideways scroll happens HERE." The floor did make the map
   readable and it also put 13 of the 62 places past the right edge of a 390px
   phone with no way to reach them — `touch-action: pan-y` moved that box 0px on
   a real horizontal drag, measured (STATE.md item 19).
   THEN: ui/atlas.js kept the floor's SCALE (z = max(1, 760 / rendered width))
   and dropped its WIDTH, so the map fit the column and the ink did not shrink.
   NOW: the scale dies too, because the globe has `arc`. A plate carrée had to
   choose between fitting the column and being legible; a globe is always
   exactly its own box, and you get closer by ZOOMING — which is the control
   Paul asked for and the reason the workaround existed at all. Every tap box
   and label is stated in CSS px in ui/atlas.js and converted through the
   renderer's own units-per-pixel, so there is no scale factor left to keep
   honest. `overflow-x: auto` stays as the safety net it always was, and it
   never fires: measured scrollWidth - clientWidth === 0 at 320 / 375 / 390 /
   430 / 760 / 1280. If a future view ever does overflow, the scroll is at the
   element and never at the body (this file's own third failure mode). */
/* ---- THE EARTH RUNS TO THE GLASS (2026-09-08) -------------------------
   Paul: *"See if you can get the map to 100% width and height right now it has
   padding we don't need."*

   THE PADDING IS THE PANEL'S, NOT THE MAP'S. `#atlas` is a `.nu-pan` and pays
   `--s4` down both sides so its TEXT does not smash into the nav (2026-09-01);
   the globe is not text, and a picture of the earth inset by 12.8px on a
   390px phone spends 26px of the one view whose whole job is to be looked at.
   MEASURED before: the map 364.4px wide inside a 390px page.
   SO IT CANCELS THE INSET, the way `#atlasIndex` does two screens down and for
   the same reason — one negative margin either side, no new layout — and its
   own `--s4` of block padding goes with it. What is NOT cancelled is the list
   under it: the years stay inset, because a column of text at the glass is the
   defect the panel's padding was added to fix. */
#atlasWrap{ overflow-x: auto;
  margin-inline: calc(-1 * var(--s4)); }

/* THE GLOBE IS A TAP SURFACE, so it makes the same promise the pointer handler
   does. `pan-y` gives a VERTICAL swipe to the page (test/atlas.js G13) and the
   horizontal drag arrives as pointermove for ui/atlas.js's 8px axis lock to
   claim. The measurement that reads as an argument against it is not one: this
   morning's "pan-y moves that box 0px on a real horizontal touch drag" was
   about the BROWSER'S OWN horizontal pan of an OVERFLOWING element, which
   pan-y correctly forbids — and this design never overflows, so there is no
   browser pan to lose. pan-y is the reason both halves work. It is not
   sufficient on its own either: an unlocked build scrolled 345px AND spun the
   globe 39 degrees on the same gesture, which is what the 8px lock is for. */
#atlasMap{ touch-action: pan-y;
  /* AND A DRAG ON THE EARTH DOES NOT SELECT THE PLACE NAMES. Measured: a mouse
     drag across the globe left "Kingston", "Austin" and "Cincinnati" sitting in
     a blue selection highlight, because SVG <text> is text. The names are
     aria-hidden decoration painted beside a button that carries the real name,
     so there is nothing here worth selecting and the highlight was only ever
     the drag leaking through. */
  -webkit-user-select: none; user-select: none; }
#atlasMap .place{ cursor: pointer; }
/* THE FAR SIDE IS TRANSPARENT AND INERT, AND IT IS NEVER `display: none` OR
   `visibility: hidden`. Both of those remove an element from the FOCUS TREE
   (measured), and a place behind the earth must stay reachable by Tab, because
   focus is what flies the camera to it — that is the whole keyboard path now
   that the fallback listbox is gone (G11). */
#atlasMap .place[data-far="1"]{ opacity: 0; pointer-events: none; }
/* TWO REMOVALS LIVE ON THESE MARKS AND THEY ARE OPPOSITE ON PURPOSE. The one
   above is the FAR SIDE: the place is there, you cannot see it from where you
   are standing, and focus turns the earth to it — so it must stay in the focus
   tree. The other is THE YEAR, and it is not here: ui/atlas.js writes
   `display: none` and `data-when="0"` straight onto the <g>, because a place
   the slider's year does not hold is not there at all and nothing may reach it.
   Paul, 2026-08-24: "Don't show ghost genres when the time isn't right. Just
   show genres that align with time." It is a presentation attribute rather than
   a rule in this file so that the removal cannot be defeated by a stylesheet;
   `data-when` rides along because a display:none element measures 0x0 and
   test/atlas.js G12 must be able to select the marks that are really drawn. */
/* AND A FOCUSED MARK IS RINGED. The UA outline on an <svg> <g> is kept, not
   suppressed — :focus-visible at :106 is the page's own indicator and a
   keyboard user should get the same one here as everywhere else — and the
   mark's own <circle class="ring"> is drawn as well, because an outline around
   a bounding box is hard to find on a moving globe. A CSS property beats the
   presentation attribute ui/atlas.js writes, so this wins while focus lasts. */
#atlasMap .place:focus-visible .ring{ opacity: 1; }

/* ---------- THE NAMES, WHICH TRAVEL WITH THEIR DOTS ---------------------
   Paul, 2026-08-25: "The labels on the map don't move with the map. You could
   hide them but it would be better if you moved them as I moved the globe."
   The moving is in ui/atlas.js — each name is a <g class="lab"> handed the same
   translate as its mark, on the same line, from the same cache. Three rules
   here are what the move needs from a stylesheet, and nothing else is:

   1. `pointer-events: none`. The names used to be safe from eating a tap by
      LAYERING alone — #atlasNames is painted before #atlasMarks, so a mark was
      always on top of a name. That is still true and it is no longer the
      argument, because a name now sits wherever its dot is and can land over a
      NEIGHBOUR'S dot. Declaring the layer untouchable is the promise itself
      rather than a consequence of paint order. (The hit test picks the nearest
      mark in SCREEN space and never reads e.target, so this changes no tap that
      was already working; it stops one that would have started to hover wrong.)
   2. THE FAR SIDE, said the same way the marks say it. A place rotating behind
      the earth must take its name with it — measured on the shipped page,
      mid-drag at 390x844: one far-side place (San Francisco) still had its name
      lying over the Atlantic. `opacity: 0` and not `display: none`, to match
      the mark exactly: these are aria-hidden decoration, but a rule that
      differed from the mark's would be a second story about where a place is.
   3. THE HALO, which is how a black name stays legible on a grey coastline
      WITHOUT COLOUR ("keep it black and white"). `paint-order: stroke` puts the
      stroke down first and the fill on top of it, so a Canvas-coloured stroke
      becomes a halo around a CanvasText glyph rather than a fat outline through
      it. Both are system colours, so it is right in a dark theme and in forced
      colours, where a light stroke behind dark text is exactly what the mode
      asks for. The WIDTH is not here: stroke-width in a stylesheet is in USER
      units and this viewBox is 1000 units across the column, so ui/atlas.js
      writes it in CSS px through the renderer's units-per-pixel (HALO_PX). */
#atlasMap .lab{ pointer-events: none; }
#atlasMap .lab[data-far="1"]{ opacity: 0; }
/* AND THE SELECTOR COVERS BOTH LINES BECAUSE THERE IS ONE CLASS FOR BOTH.
   2026-08-28, Paul: *"Put the names of the genres under the locations on the
   map."* A `.lab` now holds two <text> nodes — `.name` (the place) and
   `.name.gname` (the genre the when-slider is answering for it) — and the
   second one is a `.name` rather than a class of its own SO THAT IT CANNOT
   MISS THIS RULE. The halo is the only thing making a word legible over the
   10% land wash in a black-and-white picture; a second line added to this
   selector by hand is a second line that gets forgotten one round later and
   paints raw ink across a coastline. Its stroke-width, its size (11.5 CSS px
   against the place's 13) and its 13px of leading are all written by
   ui/atlas.js through the renderer's units-per-pixel, for the reason stated at
   HALO_PX there: a stroke-width declared here would be in USER units and this
   viewBox is 1000 units across whatever the column happens to be. */
#atlasMap text.name{ paint-order: stroke; stroke: var(--paper);
  stroke-linejoin: round; stroke-linecap: round; }
/* THE GENRE LINE IS THE SECOND VOICE, and it says so in weight rather than in
   colour ("keep it black and white"): the place is the mark's own name and
   does not move while you look at it, the genre under it is what the SLIDER is
   currently answering, and the lighter face is the one that changes. */
#atlasMap text.gname{ font-weight: var(--fw-body); }

/* ---------- THE YEAR, STAMPED ON THE EARTH (2026-09-06) -----------------
   Paul: *"Get rid of 'where' and the line above and the output that goes
   '33000 BC · 1 record within ten years · Hohle Fels'."* The line went; the
   one fact in it that nothing else on this surface carried — WHICH YEAR the
   globe is drawing, which is what decides how many marks are on it — moved
   INTO the picture. It is the globe's own ink at half strength, in the
   start-bottom corner of its own box (outside the sphere at the whole earth,
   over it when you are close), and it wears `text.name`'s halo because of
   that second case. `aria-hidden` in ui/atlas.js: the year is already spoken
   by every mark, by every index row and by `#atlasMap[data-year]`, and a
   fourth announcement on every scroll is the live region that was just
   deleted. Its size and its stroke-width are written by ui/atlas.js in the
   renderer's own units-per-pixel; only the ink is here. */
#atlasMap text.nu-globeyear{ fill: var(--ink); opacity: .45;
  font-weight: var(--fw-label); font-variant-numeric: tabular-nums;
  letter-spacing: .04em; paint-order: stroke; stroke: var(--paper);
  stroke-linejoin: round; stroke-linecap: round; }

/* ---------- THE INDEX: EVERY GENRE, OLDEST FIRST ------------------------
   Paul, 2026-08-28: *"Let me click to see a big list of all the genres in
   chronological order."*  And, 2026-08-29, three sentences that change what
   this thing IS: *"Get rid of the time slider. Make the genre list permanent
   and always expanded. As I slide it light up the map with places."* /
   *"Add Wikipedia links to the genre list in a column."*

   THE DOOR IS GONE. `#atlasIndexBtn` (a full-width plate with a rotating
   caret) and `.nu-ixcaret` and its `prefers-reduced-motion` guard are deleted
   with the button they drew. What they said is worth keeping because it is
   still the reason a ROW does not look like a button: "199 ENAMEL PLATES IS
   NOT A LIST, IT IS A WALL. So a row gives up the plate — the same exception
   the stylesheet already makes 2,400 lines up, `td button{ box-shadow: none }`
   — a button seated in a table cell is already framed by the grid. A row in an
   index is framed by the rule under it and by the two rows either side; the
   frame is the LIST. What a button may never give up is its BEHAVIOUR: the
   press still travels (`:active` takes the flag), hover still warns, focus
   still shows, and the tap target is still `min-height: var(--tap)` from the
   base rule."

   IT IS ALSO THE TIME INSTRUMENT NOW, which is why the scroller is not a
   convenience any more but the control itself. A scroll of this box moves the
   year and lights the marks for the rows you can see (ui/atlas.js `sweep`), so
   its height is the width of the gesture: a taller box is a coarser sweep with
   more names visible, a shorter one is finer. 60vh, capped at 560px, is what
   the list already had and it is kept — one screen of catalogue that scrolls
   under your finger and stops there, with the globe it feeds still on screen
   above it. `overscroll-behavior: contain` is what stops the flick that ends
   at the bottom of the list from carrying on down the page. */
/* ---- AND IT IS THE WHOLE WIDTH OF THE GLASS (2026-09-08, TABLE.md §23) --
   Paul: *"The genre list should be 100% wide with no left or right borders."*

   FULL BLEED, THE WAY `.nu-bar` ALREADY DOES IT (:112) — cancel the panel's
   gutter and do NOT re-pay it, because the rows pay their own. `.nu-pan` is
   `padding-inline: var(--s4)` (12.8px measured), so a negative margin of that
   on each side puts the box's edges ON the screen's edges; `100vw` is refused
   for the reason it is refused everywhere in this file — vw includes the
   scrollbar gutter, and a sideways scroll on the body is the one thing this
   sheet exists to prevent.
   THE SIDE BORDERS COME OFF WITH THE GUTTER, and that is the same decision
   said twice: a rule down the left of a box whose left edge is the screen is
   a line drawn 0px from nothing. What is KEPT is the block borders — a rule
   above and below — because those are what still say where the catalogue
   starts and stops in a scrolling column. `border-radius` goes too: a corner
   is a thing a box has, and this one has run out of box to have it on.
   MEASURED: 364.4px at x 12.8 -> 390 at x 0 on a 390 phone, 320 at 0 on a
   320, with `documentElement.scrollWidth === innerWidth` at both. */
#atlasIndex{ margin-block-start: var(--s4);
  margin-inline: calc(-1 * var(--s4)); max-block-size: min(60vh, 560px);
  overflow-y: auto; overscroll-behavior: contain;
  border-block: var(--bw) solid var(--ink);
  border-inline: 0; border-radius: 0;
  background: var(--panel); }
.nu-ix{ list-style: none; margin: 0; padding: 0; }
/* ===== THE ROW IS THE <li>, AND THE BUTTON IS A PLATE UNDER IT ==========
   Paul, 2026-08-29: *"Replace the slug for genre with the Wikipedia link so
   everything is on one line."*

   THREE COLUMNS, NOT FOUR, AND THE GENRE COLUMN IS THE LINK. This grid had
   four tracks for six hours this morning — year, genre, place, article — and
   the genre track held the genres.js KEY ("zema", "sizhu") while the article
   track held the same music's real name ("Television news music"). Two names
   for one row is what made the row too wide to fit, so the key comes off the
   page and the title moves into its column. `minmax(0, …)` on every text track
   is what keeps a 17-character place name ("Greenwich Village") inside a 390px
   phone instead of pushing the page sideways: a `1fr` track has a min-content
   floor and would have overflowed. The genre takes 1.6 of those parts against
   the place's 1 (it was 1.1 to 1 when it held a KEY): an article title is
   longer than "dub" and it is now the word the row is FOR, so the slack goes
   to the name rather than being shared evenly with a place that is usually one
   short word. AND THE YEAR TRACK CAME DOWN FROM 5ch TO 4ch, measured rather
   than guessed: the widest year on the page is a four-digit one and it wants
   33.7 CSS px, against 50.9 that 5ch was buying — 17 px a row of nothing,
   which at 320 is 7% of the whole line. 4ch is 40.7, seven px of slack for a
   fallback face, and every px it gives back goes to the title.

   THE PARAGRAPH THIS REPLACES IS QUOTED BECAUSE HALF OF IT IS STILL TRUE:
   "THE GRID MOVED FROM THE BUTTON TO THE <li> because a link cannot live
   inside a button — invalid HTML, and ambiguous to a thumb and to a screen
   reader both. So the button spans the first three columns and the anchor
   stands in the fourth, and the two are siblings in one grid rather than one
   control pretending to be two." — still the law. What is dead is the rest:
   "BELOW 560px THE ARTICLE TAKES ITS OWN LINE, and that is the one place this
   grid stops being four columns. At 320 the row has 240px of column to spend;
   a fourth track leaves the genre and the place about 6ch each, which is not a
   phrase any more, it is two truncations. So under 560 the <li> is one column
   and the article sits under the three facts… two controls, one above the
   other, each its own tap target." THERE IS NO FOURTH TRACK TO DROP. The
   `@media (max-width: 559px)` block that did the dropping is deleted with it,
   and every row is one line at every width — which is the sentence Paul
   actually asked for.

   THE BUTTON SPANS ALL THREE TRACKS WITH `subgrid`, so the year and the place
   sit in the <li>'s OWN columns. The alternative was to repeat the three track
   sizes on the button and hope the two copies stayed equal; they would not
   have, because the anchor is positioned by the <li>'s tracks and the year by
   the button's, and a page whose two grids disagree by 2px is a page whose
   link does not line up with the column it is supposed to be in. One
   definition, one place, and the <li>'s `padding-inline` is what insets the
   row (the button carries none, so its content box IS the <li>'s and the
   subgrid lines land exactly on the parent's).

   THE ANCHOR IS PAINTED OVER THE PLATE and wins the hit test on its own word
   by SOURCE ORDER — it comes after the button in the DOM, so no z-index and no
   stacking context is needed. `justify-self: start` shrink-wraps it to the
   width of the title, so the rest of the genre column is still plate: a tap on
   "Dub music" opens Wikipedia, a tap two centimetres to its right plays the
   record. (ui/atlas.js argues why there is no `stopPropagation`.)

   AND THE STATE PAINT MOVED TO THE <li> WITH THE GRID. It was
   `.nu-ixrow[aria-current="true"]` and `.nu-ixrow:active`; the button no
   longer reaches the row's edges (the <li> holds the padding now), so a tint
   painted on the button would stop 12px short on both sides and the "what is
   playing" bar would float inside the row instead of running down its edge.
   `:has()` keys the same fact off the same attribute — one fact, still one
   owner (ui/atlas.js `syncIndex` writes `aria-current` and nothing else) —
   painted on the element that is now the row.

   WHAT IS PLAYING, SAID THE WAY THE GLOBE SAYS IT: the mark on the earth wears
   a ring; the row wears the hand — the page's own colour for "a value you
   set". One fact, two pictures.

   THE ARITHMETIC AT FIVE WIDTHS, measured on the rendered page in the gate's
   own chromium with all 201 rows built (2026-08-29):

                    320     375     390     430    1280
     the <li>        44      44      44      44      44
     its button      44      44      44      44      44   (the tap floor)
     the genre       44      44      44      44      44
     sideways         0       0       0       0       0   (page and list both)

   ONE LINE COSTS AN ELLIPSIS, AND HERE IS THE BILL, counted on the rendered
   page over all 213 rows rather than described:

                              320px    390px    1280px
     titles that ellipsise      116       72         2
     places that ellipsise       64       22         0

   RE-COUNTED 2026-08-30, the ↗-mark round (the icon track below costs the
   words a --tap square per row, the catalogue is 297 rows now, and the gate
   prints these on every run as cutT/cutP so this table can never silently
   age again):

                              320px    390px    1280px
     titles that ellipsise      196      130         2
     places that ellipsise      174       69         0

   That is the trade Paul asked for in the sentence — a phone shows the start of
   a name instead of showing the whole of a name twice on two lines — and it is
   written down rather than discovered. The WHOLE string is the anchor's
   `aria-label` (so nothing is lost to a screen reader), the reason the article
   was chosen is in `title`, and above 700px the genre takes a fixed track with
   the place following it immediately, so the three facts stay a phrase instead
   of a sweep of the eye across 590px of nothing — and on a desktop the two
   still cut are the two longest names in the catalogue and nothing else. */
.nu-ixli{ margin: 0; display: grid; align-items: center;
  /* THE FOURTH TRACK IS THE ↗ MARK'S (2026-08-30). Paul: "Put the link in a
     new icon on the right that isn't underlined." One --tap square on the
     right edge, at every breakpoint below, [data-bc] included — a fixed track
     because a tap target may not shrink, so what yields on a narrow phone is
     the PLACE column's fr, never the thumb. A row with no article leaves the
     track empty (absent, not disabled) and the plate spans it like any other
     hole. */
  grid-template-columns: 4ch minmax(0, 1.6fr) minmax(0, 1fr) var(--tap);
  /* THE EIGHT ROWS BEFORE THE YEAR ONE (2026-08-30): "33000 BC" does not fit
     a 4ch year track — measured, it wrapped the row to 51.4px and broke the
     one-line law at every width. The deep-time round marked its rows
     `data-bc` and shimmed the track inline because this sheet was outside
     its fence; the rule below ADOPTS that handoff at the sheet's own three
     breakpoints, and the inline shim in ui/atlas.js idxRow can now retire. */
  gap: 0 var(--s3); padding-inline: var(--s4);
  border-block-end: var(--bw) solid var(--rule); }
.nu-ixli[data-bc]{ grid-template-columns: 8ch minmax(0, 1.6fr) minmax(0, 1fr) var(--tap); }
.nu-ixli:last-child{ border-block-end: 0; }
.nu-ixli:has(> .nu-ixrow:active){ background: var(--flag); }
.nu-ixli:has(> .nu-ixrow[aria-current="true"]){ background: var(--hand-tint);
  box-shadow: inset 3px 0 0 var(--hand); }
.nu-ixrow{ grid-column: 1 / -1; grid-row: 1;
  display: grid; grid-template-columns: subgrid;
  align-items: center; inline-size: 100%; text-align: start;
  border: 0; border-radius: var(--r0);
  box-shadow: none; background: transparent;
  padding-inline: 0; padding-block: var(--s2); }
.nu-ixrow:active{ transform: none; }
/* AND ON A WIDE SCREEN THE GENRE COLUMN STOPS STRETCHING. Measured at 1280:
   two proportional tracks put "Kingston" 590 px to the right of "reggae" with
   nothing in between — a row you read by sweeping your eye across a gap rather
   than at a glance. Above 700 px the genre takes a fixed 26ch (18ch was the
   number when this column held the KEY, and the longest key is
   "singersongwriter"; it holds article titles now, and "Bulgarian State
   Television Female Vocal Choir" is 46 characters) and the place follows it
   immediately, so the three facts stay a phrase at every width. Below 700 the
   tracks stay proportional, because 26ch of a 390 px phone would leave the
   place nowhere to go. */
@media (min-width: 700px){
  .nu-ixli{ grid-template-columns: 4ch minmax(0, 26ch) minmax(0, 1fr) var(--tap); }
  .nu-ixli[data-bc]{ grid-template-columns: 8ch minmax(0, 26ch) minmax(0, 1fr) var(--tap); }
}
/* AND THE DESKTOP GETS A WIDER ONE, because 26ch on a 1280 was still cutting
   words with 700px of empty place column beside them — "Music of Punjab · the
   broa…" next to "Chandigarh" is an ellipsis that has no excuse. Measured over
   all 213 titles at 1280: the median wants 104px, the 90th 237, the 98th 314,
   and the longest ("Bulgarian State Television Female Vocal Choir · the
   artist") 515. 26ch is 264 and cuts 11; 34ch is 346 and cuts 2; 46ch is 468
   and cuts 1, at the price of putting the place 470px from the year on every
   row of the list. 34ch is where the curve flattens — two long tails ellipsised
   instead of eleven, and the place still lands within a glance of the name. */
@media (min-width: 1100px){
  .nu-ixli{ grid-template-columns: 4ch minmax(0, 34ch) minmax(0, 1fr) var(--tap); }
  .nu-ixli[data-bc]{ grid-template-columns: 8ch minmax(0, 34ch) minmax(0, 1fr) var(--tap); }
}
/* AND ON THE NARROWEST PHONE THE ROW GIVES BACK ITS OWN SLACK BEFORE IT ASKS
   THE WORDS TO. 320px is 238px of line after the 56px gutter, and the two
   gaps and the two paddings are 40 of them — 17% of the row spent on air. At
   .35em and .5em they are 27, and the 13px goes to the title and the place:
   measured, 130 -> 116 titles and 93 -> 64 places ellipsised. The 44px floor,
   the one line and the zero sideways scroll are all unchanged; what shrinks is
   whitespace, which is the right thing to spend first. */
@media (max-width: 359px){
  .nu-ixli{ gap: 0 var(--s2); padding-inline: var(--s3); }
}
/* THE CELLS ARE PLACED, NOT FLOWED. (2026-08-30: this read "The genre is not
   the button's child any more, so the button's subgrid has a HOLE at column 2
   … the button holds the first and third facts and the second is a link lying
   over it." The genre came back into the plate as plain text and the LINK
   moved to the ↗ mark, so the hole is column 4 now on a linked row — and
   still column 2 on a refused one, whose key-span stays the overlay. Explicit
   placement is what lets one plate serve both shapes; auto placement would
   slide the place into whichever hole the row has.) */
.nu-ixy{ grid-column: 1; font-family: var(--mono);
  font-variant-numeric: tabular-nums; color: var(--dim); }
.nu-ixp{ grid-column: 3; color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-inline-size: 0; }
/* ---------- THE GENRE IS THE ARTICLE (2026-08-29) ----------------------
   Paul: *"Replace the slug for genre with the Wikipedia link so everything is
   on one line."*

   IT IS THE ROW'S NAME NOW, NOT A FOOTNOTE TO IT, and that is a REVERSAL of
   the paragraph that stood here this morning: "IT IS A FOOTNOTE TO THE ROW AND
   NOT A SECOND TITLE, which is the sentence `.nu-wiki` used to make beside the
   <h1> and makes here instead: smaller than the genre it sits beside." There
   is no genre beside it to be smaller than — it IS the genre. So `.8rem` goes
   and it takes the column's own size and weight (`.nu-ixg` held the slug and
   wore `font-weight: 600; overflow-wrap: anywhere`; the rule is DELETED with
   the cell, and its weight is inherited here — a title does not `anywhere`,
   it ellipsises, because wrapping is the two-line bug this round closed), with
   the browser's own underline left alone, because that underline is the
   affordance for "this leaves the page", and hand-blue from the base `a` rule
   like every other link on this page. A link that had been quietly shrunk to
   footnote size while being the only name on the row would be exactly the
   silent demotion this stylesheet's other notes keep refusing.

   THE WORD STOPPED BEING THE LINK (2026-08-30). Paul: *"In the genre list get
   rid of the Wikipedia link but leave the text. Put the link in a new icon on
   the right that isn't underlined."* That is a REVERSAL of this block's two
   claims as they stood, quoted so the reasoning stays findable:
     · "IT IS ITS OWN 44px TAP TARGET, LYING ON ANOTHER ONE. Two controls
       sharing a 44px row is the case the tap floor exists for: the anchor
       takes `min-block-size: var(--tap)` and centres its text in it … so the
       thumb that lands on the word gets Wikipedia and the thumb that lands
       anywhere else on the row gets the record." The word is a SPAN inside
       the plate now — a thumb that lands on it gets the record, and the tap
       height left this rule with the anchor: `.nu-ixw-no` keeps it (the
       refusal is still an overlaid pressable word) and `.nu-ixgo` carries it
       for the link. A 44px-tall child inside a plate that also has
       `padding-block` would push the row past the 46px ceiling, which is why
       the height may not stay here.
     · "the browser's own underline left alone, because that underline is the
       affordance for 'this leaves the page', and hand-blue from the base `a`
       rule". A span takes neither, which is the ask: the word reads as the
       row's name in the row's own ink, and 'this leaves the page' is now the
       ↗ mark's whole shape, one column over.

   `text-overflow: ellipsis` AND `min-inline-size: 0`: the title is the only
   text in this row whose length nobody controls, and a column that widened for
   it would push the place name off a phone. The full name is in `aria-label`
   and the reason the article was chosen is in `title`.

   AND IT IS A BLOCK, NOT A FLEX BOX, WHICH IS A BUG THIS ROUND FOUND BY
   LOOKING (2026-08-29). The rule read `display: flex; align-items: center;
   min-block-size: var(--tap)` and its note promised an ellipsis — but
   `text-overflow` applies to a BLOCK CONTAINER'S inline content, and inside a
   flex box the text is an anonymous flex ITEM, so the browser clipped the
   title mid-letter with no ellipsis at all and had been doing it since the
   column shipped. Screenshotted at 390px: "Bhangra (mus". The centring flex
   was buying is bought by `line-height: var(--tap)` on a single line, which is
   the same 44px and does not take the ellipsis away with it. (memory: "test
   the artifact" — every count was green while the word was being cut.) */
.nu-ixw{ grid-column: 2; grid-row: 1; justify-self: start;
  font-weight: 600; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; min-inline-size: 0; max-inline-size: 100%;
  display: block; padding-inline-end: var(--s3); }
/* A REFUSED CELL — the two MISSES and the six roles. IT USED TO BE AN EM DASH
   and it is THE ROW'S OWN KEY now (2026-08-29): the dash sat in a fourth
   column next to a genre word, and with the fourth column gone a dash would
   leave the row with no name at all. What it prints is the only name the
   catalogue holds for these eight — "retrofunkpop", "simple" — and it is
   dressed as what it is rather than as what it is not: no underline, not
   hand-blue, dim, and in the machine face this page keeps for machine words.
   It still carries `data-why`, which is this page's refusal idiom everywhere
   else: a reason on the artifact, spoken by the same delegated explainer,
   never a silent grey. */
.nu-ixw-no{ color: var(--dim); font-family: var(--mono); font-weight: 400;
  font-size: .9em; cursor: help; text-decoration: none;
  /* the tap height that used to live on `.nu-ixw` (see the reversal above):
     this word is still an OVERLAY the explainer answers, so it is still its
     own thumb-height target lying on the plate. */
  block-size: var(--tap); line-height: var(--tap); }
/* ---------- THE ↗ MARK — THE LINK'S OWN CELL (2026-08-30) ---------------
   Paul: *"Put the link in a new icon on the right that isn't underlined."*
   It is the fourth track's whole occupant: a real <a> (middle-click,
   long-press, new tab all work) lying over the plate exactly the way the
   word-anchor used to lie over column 2, sibling not child, after the button
   in source order so it wins the hit test on its own square with no z-index.
   `--tap` in both axes — a mark is a thumb target or it is decoration.
   NOT underlined and NOT hand-blue: the base `a` rule paints both, and both
   are the affordance for a link that is a WORD among words. This link is a
   MARK, and the mark's own shape — an arrow leaving — is the affordance, in
   the ink like the tray's marks and the tab strip's glyphs (ui/glyph.js: "a
   glyph is text — it inherits the ink"). The symbol face is glyph.js's own
   stack, repeated here for its reason: "a serif ↕ next to a sans ↑ is two
   different arrows." A row with NO article renders NO mark at all — absent,
   not disabled; the refusal's reason lives on the word span, not here. */
.nu-ixgo{ grid-column: 4; grid-row: 1; justify-self: stretch;
  display: block; block-size: var(--tap); line-height: var(--tap);
  min-inline-size: var(--tap); text-align: center;
  color: var(--ink); text-decoration: none;
  font-family: var(--sym); }
/* `#atlasActs button { min-block-size: var(--tap); margin-inline-end: .4rem }`
   STOOD HERE AND BOTH HALVES ARE GONE (2026-08-26). The tap floor was already
   `button { min-height: var(--tap) }` at the top of this file and had been for
   two days — a second, higher-specificity opinion about the same pixel. The
   margin was this section's private spelling of a spacing three other button
   strips spelled two other ways, and it is `.nu-row` now (ui/atlas.js carries
   the class). Measured after: `another take` is the same computed button as
   `play`, which it never was before.
   (AND THE BUTTON ITSELF LEFT ON 2026-08-27 — it is `#rewrite` in the .nu-bar,
   beside the `play` it was measured against. `#atlasActs` is gone with it, so
   this tombstone now buries two things; the measurement is kept because the
   lesson is about a second opinion on a pixel, not about that button.) */

/* ---------- THE VOICE'S OWN KNOBS (VOICE.md) -----------------------------
   Almost nothing, on purpose. The block is the same <table> in the same
   `.nu-pane` the board's channel strip already is — <th> the parameter, <td>
   the control and its readout, a third cell for the derived value — so it
   inherits the rules two hundred lines above and needs no vocabulary of its
   own. What IS here is the two places 22 rows on a 390px phone are different
   from 5 rows on a channel strip.

   1. THE THIRD COLUMN IS THE ONE THAT MUST NOT WIN. It holds a sentence ("the
      record's own, 0.06") beside a slider, and a table lays out to its widest
      cell — so without a cap the derived text pushed the slider column under
      the pane's scroll and the control you came for was off the edge of the
      phone. Measured at 390x844 on the tract's 22 rows: 476px against the
      366px column. A `max-inline-size` in `ch` (the width of a "0") is the
      right unit for a column that holds a number and three words.
   2. THE SYLLABLES WRAP. Eight <select>s of one letter each is a sequence, not
      a set, so they stay in order and in one flow — `flex-wrap` puts them on as
      many lines as the phone has room for and the ORDER READS DOWN THE LINES,
      which is how a word is read anyway. They are the narrowest select on the
      page (one letter), so `td > select`'s 9em cap is not what sizes them. */
.nu-knobs td:last-child{ max-inline-size: 22ch; }
.nu-knobs td:last-child .nu-why{ display: inline; }
.nu-vow{ display: inline-flex; flex-wrap: wrap; gap: var(--s1); margin-inline-start: var(--s2); }
.nu-vow select{ min-inline-size: 4.5em; }
/* A DISABLED SLIDER IS GREYED THE SAME WAY A DISABLED OPTION IS — `.is-off`'s
   own opacity, reached through the attribute the browser already sets, so
   there is one opinion about "refused" on this page and not two. The REASON is
   a `.nu-why` in the row under it (ui/eight.js `rowFor`), never a title
   attribute: a tooltip is not readable on a phone and not readable at all in a
   screen reader that is announcing the control's name. */
.nu-knobs input[disabled]{ opacity: .45; }

/* ===== THE PAD: A THROAT, DRAWN ========================================
   Paul, 2026-08-26: *"a nice radial graph structure for editing the voice so I
   can work on multiple dimensions"*, and *"I'm fine with the pad idea"*.

   WHICH OF THE SEVEN KINDS IT IS, because the design system above says the
   burden is to answer that here. IT IS NOT AN EIGHTH KIND: it is a PICTURE of
   eleven controls that all remain kind 6, A QUANTITY, in the table directly
   under it — `<input type=range>` with an `<output>`, every one of them, and
   the pad writes through the same one store. What it could have been is what
   it already is: eleven sliders. Why that was worse is the whole reason it was
   asked for — two of the eleven are a POSITION and a DIAMETER at that
   position, one constriction with two coordinates, and the five vowels sit at
   particular places in that plane. On two sliders the plane is invisible and
   "move the constriction forward while opening it" is two gestures.

   SO IT IS A DIAGRAM, and the page already has two of those and one rule for
   them: `.nu-circ` and `.nu-map`, which take the whole column and keep their
   ratio. This is the third and it follows both. It is `aria-hidden` in the
   markup and it takes no focus, because an SVG you drag is not a control; the
   accessible page is the table below it, unchanged. With this stylesheet off
   the picture is an inert drawing and nothing is lost — which is the same
   promise the top of this file makes, tested the same way.

   TWO WEIGHTS OF ONE INK AND NO HUE, exactly as `.nu-tf` argues: `--rule` for
   what the record already is (the vowels' own places, the driver's territory,
   an empty track) and `currentColor` for what your hand has done (the handle,
   the knots, the silhouette). The pair survives forced colours, a monochrome
   screen and dark mode with no media query, and it means the section wash
   under it can never be mistaken for a state.

   AND THE ONE PROPERTY THAT IS LOAD-BEARING RATHER THAN DECORATIVE:
   `touch-action: none` on the surfaces that ARE the control, and nowhere else.
   ui/atlas.js measured the alternative on the globe — with touch-action left
   alone the compositor decides whose a drag is before any JS runs, and
   `preventDefault` cannot take it back. A pad needs both axes, so the pad's
   face and every LIVE grip wear `.nu-pad-grab` (ui/eight.js puts it on) and
   decline the browser's scrolling; the ring's empty air and the margins do
   not, and a swipe there still scrolls the page. `touch-action` INTERSECTS
   down the ancestor chain, so a child saying `none` inside a page that says
   nothing is the whole of the mechanism — and it DOES apply to an SVG element,
   which is the part that had to be measured rather than assumed: at 390 with a
   real CDP touch, a vertical drag inside the face moves the window 0px and the
   same drag on the ring's air scrolls it 277. A grip that is GREYED does not
   get the class, so a swipe over a disabled knot scrolls the page like any
   other piece of the drawing. */
/* `#app .nu-pad` AND NOT `.nu-pad`, AND THE ID IS DOING REAL WORK. Two hundred
   lines up, `#staff svg, #app svg{ max-inline-size: 100% }` stops an abcjs
   engraving putting the sideways scroll back on the body — and it is an ID
   selector, so a bare `.nu-pad` cap loses to it and the pad drew 1256px wide on
   a 1280 screen, measured. The cap has to out-specify that rule to exist at
   all. 34em is the width past which a diagram stops being a control and starts
   being a poster; on a phone `100%` is what binds. */
#app .nu-pad{ display: block; inline-size: 100%; max-inline-size: 34em;
  block-size: auto; margin-block: var(--s3);
  /* AND A DRAG ON THE THROAT DOES NOT SELECT THE WORDS ON IT. Paul,
     2026-08-26: *"I can't grab everything because it keeps selecting text."*
     Measured on the rendered page at 1280, one mouse drag apiece: a drag
     across the face left "a" highlighted, a drag off a ring knot left "open",
     and a drag from the ring's empty air left "breath na" — every word on the
     picture is an SVG <text> and SVG <text> is text. There is nothing here
     worth selecting: the whole picture is `aria-hidden` decoration painted
     beside the sliders that carry the real values, so the highlight was only
     ever the drag leaking through.
       ON THE ELEMENT AND NOT ON `.nu-pad-grab`, which is the shape the globe
     already settled (`#atlasMap`, 150 lines up, for the same complaint about
     "Kingston" and "Austin"). `user-select` INHERITS, so one declaration on
     the <svg> covers the labels a drag crosses on its way out of the grip it
     started in — a rule on the grips alone would not have caught two of the
     three drags above, because neither of them started on one. */
  -webkit-user-select: none; user-select: none; }
.nu-pad-grab{ touch-action: none; }
/* the two rings and an empty spoke: the furniture, at the same weight as every
   other quiet line in this file */
.nu-pad-ring, .nu-pad-track{ fill: none; stroke: var(--rule); stroke-width: 1; }
.nu-pad-track{ stroke-width: 2; stroke-linecap: round; }
.nu-pad-face{ fill: none; stroke: var(--rule-strong); stroke-width: 1.5; }
.nu-pad-tick{ stroke: var(--rule); stroke-width: .75; stroke-dasharray: 2 3; }
/* WHERE THE VOWEL TABLE PUTS THE TONGUE — the record's own, so it is `--rule`,
   and the ones this record actually says are the ink instead. */
.nu-pad-word{ fill: none; stroke: var(--rule); stroke-width: 1;
  stroke-dasharray: 3 3; }
/* EVERY PLACE THE SEEDED DRIVER CAN PUT THE TONGUE. Twenty-five segments, and
   the opacity is `babble` itself (ui/eight.js), so a record that does not
   babble draws none of it. */
.nu-pad-web line{ stroke: var(--rule); stroke-width: 1; }
/* the line from a vowel's place to yours IS `artic`, and the dot on it is
   where the tongue goes */
.nu-pad-artic{ stroke: currentColor; stroke-width: 1; stroke-dasharray: 2 2;
  opacity: .55; }
.nu-pad-eff{ fill: currentColor; }
.nu-pad-hand{ fill: none; stroke: currentColor; stroke-width: 3; }
.nu-pad-knot{ fill: currentColor; }
.nu-pad-shape{ fill: color-mix(in srgb, var(--ink) 6%, transparent);
  stroke: currentColor; stroke-width: 1; pointer-events: none; }
/* A WORD ON THE RING IS THE DOCUMENT'S OWN KEY (ui/eight.js says why), at the
   one size on this page that is smaller than a `.nu-why` — a diagram's label
   is not prose and the row's full sentence is in the table under it. */
/* EVERY WORD ON THE PICTURE IS HALOED IN THE PAPER, and it is a legibility fix
   with a measurement behind it rather than a flourish. `paint-order: stroke`
   draws the glyph's outline FIRST and the fill over it, so a 3px paper-coloured
   stroke is a cut-out that lets a letter be read over whatever it lands on.
   Measured on the rendered page: at `artic` 0 — the one setting where the whole
   claim of the drawing is "the vowel owns the tongue" — the effective dot sits
   exactly on the vowel's letter, and a currentColor letter on a currentColor
   dot is invisible. The same halo carries "back"/"front"/"wide"/"closed" over
   the ring they have to cross, and every letter over the driver's web.
   `var(--paper)` and not white: it is `Canvas`, so it tracks dark mode and
   forced colours with no media query, like everything else in this file. */
.nu-pad text{ font-size: 11px; fill: currentColor; opacity: .8;
  dominant-baseline: middle; paint-order: stroke;
  stroke: var(--paper); stroke-width: 3px; stroke-linejoin: round; }
.nu-pad .nu-pad-vlab{ fill: var(--rule-strong); font-size: 13px; opacity: 1; }
.nu-pad .nu-pad-vlab.is-said{ fill: currentColor; font-weight: var(--fw-label); font-size: 15px; }
/* the two ends of each axis, which is how a diagram says what a sentence in
   the table below says in words */
.nu-pad .nu-pad-edge{ font-size: 9.5px; fill: var(--rule-strong); opacity: 1;
  letter-spacing: .04em; }
/* GREYED, WITH THE REASON PRINTED — the same `.is-off` the whole page uses for
   "refused", reaching an SVG element for the first time. The reason is a
   `.nu-why` under the picture AND the row's own in the table, which is one
   fact (knobs.js `gate.why`) said in both views rather than two facts. */
.nu-pad .is-off{ opacity: .3; }

/* ===== THE BENCH (2026-08-27) ==========================================
   The step rows, rebuilt to nukernel/ideal/composer.html. Paul, 2026-08-27:
   "The original button structure with sliders was more novel and
   comprehensible" — then, later the same day: "Tighten it up. play/hold/rest,
   pitch offset −12 to 12, velocity 0 to 7, tightened to one line."

   REVERSAL, WRITTEN WHERE THE OLD NUMBERS LIVED: `.nu-hr-deg` (84px) and
   `.nu-hr-vel` (52px) sized the two range sliders the Bench retires; their
   rules are removed at their own site (tombstoned there) — `range()` still
   serves the chord chart through its inline `wide`, and no step grid wears a
   slider any more. Each motif step is ONE 52px line — count,
   one segmented kind button, a bipolar pitch bar, a weight bar — and each kit
   cell is a button whose fill's width IS its velocity.

   THE GEOMETRY LAW: the 52px row height is FIXED, not a minimum. A rest or a
   hold fades its bars (opacity, pointer-events) and prints one measured line
   saying why; nothing appears, nothing vanishes, so a kind change moves no
   pixel of any other row and playback moves nothing at all
   (test/bench.test.js measures both).

   THE PAINTS ARE THE PAGE'S OWN, SEMANTIC AS EVER: --hand for a value you
   set (the fills), --well for the trough, ink for the lattice ticks and the
   marked zero, --faint only for a ghost's fill (a quiet hit is a quiet
   paint), --flag only on an accent's edge (about to matter). */
/* THE COLUMN WIDTHS ARE STATED ON THE BODY ROW'S OWN CELLS (rewritten
   2026-08-28). They read `th:nth-child(2)` and `th:nth-child(4)` — the header
   row of words, `m2 | kind | pitch | vel`, which is deleted (Paul: *"Let's get
   rid of the label strings on the pitch sliders. held and rest and the stuff
   that appears on top."*). `table-layout: fixed` takes its widths from the
   FIRST ROW whatever its cells are, and every step row is `<th>` count,
   `<td class="nu-kindTd">`, `<td class="nu-pitTd">`, `<td class="nu-velTd">` —
   so the same three numbers are stated by NAME instead of by position, which
   is the more durable spelling anyway: a column that moves takes its width
   with it. The count column stays `th:first-child` because that is what it was
   in the body rows all along. MEASURED ON THE RENDERED PAGE, by putting the
   deleted header row back into the live table and taking it out again: the
   columns are 34 / 118 / 85 / 72 at 390 and 34 / 118 / 927 / 120 at 1280 —
   three stated, the pitch column taking whatever is left, which is what
   `table-layout: fixed` does with an unstated column and is what it did
   before. The table is 834px tall without the header and 857px with it, so
   the row of words cost 23px of every motif on the page. */
/* ...AND A FLOOR UNDER THE PITCH COLUMN, 2026-09-02 (wave 4). `inline-size:
   100%` alone is a PREFERRED width and the three stated columns are not: 34 +
   118 + 72 is 224, so on any axis narrower than that the pitch column — the
   one unstated column, the one the note above says "takes whatever is left" —
   is left NOTHING. Measured on the rendered page at 320x568 with the wider
   gutter: `td.nu-pitTd` 0.0px, and its control 0.0px with it. It was 14.4px at
   the old 56px gutter, which was already a control nobody could work; the
   gutter change is what made it worth measuring rather than what broke it.
   314px = 34 + 118 + 90 + 72, and 90 is where the pitch control draws its own
   name without clipping. The table is inside a `.nu-pane` now (ui/eight.js,
   same wave), so on a phone this is a swipe on the table rather than a
   document that scrolls sideways — the page's standing answer for a table
   wider than its column, and the reason the pane went back on. */
/* ---- AND IT IS THE WIDTH OF THE BOX IT IS IN (2026-09-08) -------------
   Paul: *"the motif has a border on the right but buttons jut out over that —
   why don't you just make the editor 100% inside its bounding box."*

   THE FLOOR WAS 426px AND THE BOX IS 354. `min-inline-size: 426px` is the sum
   of the five columns at their written widths, so inside a phone's motif sheet
   the table stood 72px wider than the pane and the last column's marks were cut
   by the card's own right border — a button sliced down the middle reads as a
   control sticking OUT of the sheet, which is exactly what it looks like.
   The pane's sideways swipe was the answer the last wave gave, and it is the
   wrong one HERE: the instrument picker scrolls sideways because it is a table
   of 149 rows against 17 columns (§19d), while this is five columns of one
   step and every one of them can give a few pixels.
   SO EVERY COLUMN GIVES: the count 34 -> 22 (it holds `a`, `e`, `&`, `4`), the
   kind 118 -> 92 and the marks 112 -> 100 (their buttons 36 -> 30, which is the
   same face both columns wear and keeps them reading as one control), the
   weight 72 -> 60 under 620px and its own 120 above. MEASURED at 320, 390 and
   1280 with a motif open: the table is exactly the pane's width at all three,
   nothing crosses its right edge, and the pane no longer scrolls at all.
   THE BLOCK SIZE IS UNTOUCHED — 40px marks in a 52px row, the tap floor this
   editor has always kept, and the axis a thumb misses on. */
.nu-bench{ inline-size: 100%; min-inline-size: 0; table-layout: fixed; }
.nu-bench th:first-child{ inline-size: 22px; }
.nu-bench td.nu-kindTd{ inline-size: 92px; }
.nu-bench td.nu-velTd{ inline-size: 60px; }
@media (min-width: 620px){ .nu-bench td.nu-velTd{ inline-size: 120px; } }
/* ---- THE MARK COLUMN (2026-09-05, the review's items 6+7) -----------------
   Three cycling buttons per step — accent, articulation, accidental — in one
   cell. 3 x 36px + two 2px gaps = 112px, the same 36px face the kind's own
   segments wear, so the column reads as a second segmented control and the
   thumb finds the same target size it already knows. The table's floor goes
   314 -> 426 with it, which is a swipe inside the pane it has been in since
   wave 4 and not a page that scrolls sideways. */
.nu-bench td.nu-markTd{ inline-size: 100px; white-space: nowrap; }
.nu-mkb{ inline-size: 30px; block-size: 40px; padding: 0; min-height: 0;
  margin-inline-end: 2px; vertical-align: middle;
  border: var(--bw) solid var(--rule-strong); border-radius: var(--r2);
  background: var(--panel); color: var(--faint); cursor: pointer;
  font: var(--btn-fw) 15px/1 var(--sans);
  display: inline-flex; align-items: center; justify-content: center; }
.nu-mkb:last-child{ margin-inline-end: 0; }
.nu-mkb:hover:not(:disabled){ background: var(--flag-tint); }
/* WRITTEN IS INK, DEFAULT IS QUIET — 3's law ("blank = default, bold =
   written"), said as weight rather than as a second glyph. */
.nu-mkb[aria-pressed="true"]{ color: var(--hand); border-color: var(--hand);
  font-weight: var(--fw-block); }
.nu-mkb:disabled{ opacity: .35; cursor: default; }
.nu-mkb[data-why]{ border-style: dashed; }
.nu-mkg{ pointer-events: none; }
/* one 52px line per step — fixed, the thumb's map of the bar holds */
.nu-bench td{ block-size: 52px; padding: 3px 2px; }
.nu-bench td.nu-pitTd{ position: relative; }
/* the kind: one segmented button, three 36px segments */
.nu-seg{ display: inline-flex; vertical-align: middle;
  border: var(--bw) solid var(--rule-strong); border-radius: var(--r1);
  overflow: hidden; background: var(--panel); }
.nu-segb{ inline-size: 36px; block-size: 40px; border: 0; border-radius: var(--r0);
  border-inline-start: var(--bw) solid var(--rule-strong);
  background: var(--panel); color: var(--ink); cursor: pointer;
  font: var(--btn-fw) 15px/1 var(--sans); padding: 0; min-height: 0;
  display: inline-flex; align-items: center; justify-content: center; }
.nu-segb:first-child{ border-inline-start: 0; }
.nu-segb:hover{ background: var(--flag-tint); }
.nu-segb[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill); }
.nu-segb:disabled{ color: var(--faint); background: var(--well); cursor: default; }
/* ---- THE TWO LAID-DOWN SLIDERS ---------------------------------------
   THE ANATOMY IS `.nu-vs-track`'S AND IT IS NOT RESTATED HERE — the trough's
   well and the cap's plate come from the two shared rules up there, and what
   follows is only this axis's geometry and this control's own facts. Before
   today these were a different species: a 40px trough holding a 36px flat
   fill, a floating number, and nothing to grab (measured on the rendered
   page). Now: a 20px well down the middle of the 40px line with a 38px cap
   standing 9px proud of it on both sides — the standing fader, laid down.

   NOTHING IN THE ROW MOVED, and that is the point of doing it this way: the
   line stays `--sl-line` (40px) tall inside the fixed 52px row, every part is
   absolutely positioned inside it, and the cap at 38px still clears the row's
   46px of content by 8px. Measured before and after: `.nu-pit` 40px,
   `.nu-velA` 40px, `.nu-bench td` 52px, at 390 and at 1280.

   THE TOUCH LAW IS ON THE CONTROL — `touch-action` on the control and only
   the control, the pointer captured on pointerdown, the native input inside
   the keyboard channel.
   ...AND IT IS `pan-y` NOW, WHICH THE LAST WAVE DEFERRED IN WRITING ("it reads
   `none` on both bars, not `pan-y`… this wave does not move it"). Paul,
   2026-09-08: *"when I'm inside a motif editing it my vertical scroll no
   longer works."* MEASURED with a motif open at 390: SIXTY-FOUR of these bars
   — 32 pitch, 32 weight — declare `touch-action: none`, at 40px tall in a 52px
   row, so they cover nearly the whole of a sheet that is 2,618px long inside a
   596px window. A finger that lands anywhere on the note grid is a finger the
   browser has been told not to pan with.
   `none` WAS ALWAYS TOO MUCH FOR THESE TWO. Both are HORIZONTAL — `cursor:
   ew-resize`, and `benchPitch`/`benchVel` read `e.clientX` against the bar's
   own rect and nothing else — so the vertical axis carries no value to lose.
   `pan-y` gives the browser back the one gesture the editor needs and keeps
   the one the control uses: a sideways drag still sets the degree, a tap still
   lands on a detent, the pointer is still captured, and a finger dragged DOWN
   scrolls the sheet it is standing on. (The `.nu-vs` fader and the pads keep
   `none`: they are not inside a long scroller.) */
.nu-pit, .nu-velA{ position: relative; display: block;
  block-size: var(--sl-line); min-inline-size: 0;
  touch-action: pan-y; cursor: ew-resize; }
/* the trough, laid down: `--sl-trough` ACROSS, centred in the line */
.nu-pb, .nu-vab{ position: absolute; inset-inline: 0; display: block;
  inset-block: calc((var(--sl-line) - var(--sl-trough)) / 2);
  overflow: hidden; }
/* THE PITCH BAR keeps BOTH ITS FACTS — the marked zero the fill grows from,
   and the ink ticks that ARE the record's lattice (ui/eight.js benchEnv).
   They are re-fitted to the 20px well and not dropped: the well's content is
   18px, so the fill takes the top 10 and the tick lane the bottom 7, with
   1px between them. The zero mark is the one edge in here that is allowed to
   be `--bw-hard`: it is a fact about the scale, and a hairline zero on a
   bipolar bar is a bar with no zero. */
.nu-pbt{ position: absolute; inset-block-end: 1px; inline-size: var(--bw-hard);
  block-size: 7px; margin-inline-start: calc(var(--bw-hard) / -2);
  background: var(--ink); opacity: .4; display: block; }
.nu-pbf{ position: absolute; inset-block: 0 8px; background: var(--hand);
  display: block; }
.nu-pbz{ position: absolute; inset-inline-start: 50%; inset-block: 0;
  inline-size: var(--bw-hard); margin-inline-start: calc(var(--bw-hard) / -2);
  background: var(--ink); display: block; }
/* THE PITCH BAR'S CAP IS ITS BADGE. ui/eight.js already parks the badge at the
   value (`insetInlineStart` clamped to 10..90%, `translate(-50%,-50%)`), so
   the thing sitting exactly where a handle belongs was always there — it just
   read as a label. It is a cap now, and the value is typed inside it. */
.nu-pbb{ inset-block-start: 50%; transform: translate(-50%, -50%);
  z-index: 4; pointer-events: none;
  block-size: var(--sl-cap); min-inline-size: var(--sl-grab);
  display: inline-flex; align-items: center; justify-content: center;
  padding-inline: 4px; white-space: nowrap;
  font: var(--num-fw) .75rem var(--mono); color: var(--ink); }
/* (`.nu-pbb small` STOOD HERE — the semitone offset typed small and dim beside
   the degree name inside the cap. Deleted 2026-08-28 with the cap's text:
   Paul, *"Let's get rid of the label strings on the pitch sliders."* The cap
   itself keeps every declaration above, because the cap is the HANDLE — an
   empty plate parked at the value is still the thing your thumb is on, and
   `aria-valuetext` still says the degree and the semitones to a reader.) */
/* THE KEYBOARD CHANNEL, INVISIBLE — and `opacity: 0` here is (0,1,0), which
   is NOT enough on its own: `input[type=range]:disabled` (0,1,1) beat it and
   drew the native cap over the painted one on every rest and hold row (Paul,
   2026-09-03: "Doubling like this in the motif editor"). The rule that keeps
   a refused channel at 0 lives beside `:disabled` itself, at the head of this
   file's one range block, where the collision is. Do not restate it here. */
.nu-pbin, .nu-vain{ position: absolute; inset: 0; inline-size: 100%;
  block-size: 100%; margin: 0; opacity: 0; pointer-events: none;
  min-height: 0; min-block-size: 0; }
.nu-pit:focus-within, .nu-velA:focus-within{ outline: var(--bw-hard) solid var(--hand);
  outline-offset: 2px; border-radius: var(--r2); }
/* THE WEIGHT BAR — 0..7; width is the level; tap cycles ghost(1)/hit(4)/
   accent(7) and never lands on 0 (ui/eight.js benchVel)

   AND THE FILL IS ON THE LEVEL RAMP NOW (2026-09-02). It was three states in
   three unrelated paints — `--faint` for a ghost, `--hand` for everything from
   2 to 6, a `--flag` inset edge for an accent — so a bar at 2 and a bar at 6
   were the same colour and the only difference a reader could see was 4/7ths
   of a 67px box. The ramp is `--q1..--q4` (see LEVEL in `:root`): four steps
   of one hue, ascending, ending on `--hand` itself, so the loudest bar is the
   colour every bar used to be and nothing at the top of the range moved.

   THE WIDTH IS STILL THE VALUE and the paint is a SECOND reading of the same
   number, which is the only honest way to add a colour to a control that
   already says the thing — a reader who cannot tell #7A92EA from #4A6AE5
   still has the bar. `data-v` (0..7) is `ui/eight.js benchVel`'s own
   attribute, already published for the cap's `--f` below, so the ramp arrives
   through a hook that exists rather than through a new coupling.

   THE ACCENT KEEPS ITS FLAG EDGE. `--flag` is "armed, about to matter", which
   is what an accent is, and it is an EDGE rather than a fill — the one place
   the four paints and the level ramp sit on the same control, doing two
   different jobs a step apart. */
.nu-vaf{ position: absolute; inset-inline-start: 0; inset-block: 0;
  background: var(--q4); border-inline-end: var(--bw) solid var(--rule-strong);
  display: block; }
.nu-velA[data-v="1"] .nu-vaf, .nu-velA[data-v="2"] .nu-vaf{ background: var(--q1); }
.nu-velA[data-v="3"] .nu-vaf, .nu-velA[data-v="4"] .nu-vaf{ background: var(--q2); }
.nu-velA[data-v="5"] .nu-vaf, .nu-velA[data-v="6"] .nu-vaf{ background: var(--q3); }
.nu-velA[data-v="7"] .nu-vaf{ background: var(--q4); }
/* (`.nu-velA.gh .nu-vaf{ background: var(--faint) }` STOOD HERE and is gone
   with the ramp: `--faint` is "refused, and only refused" everywhere else in
   this file, and a ghost note is not refused — it is quiet. That was the
   clearest single case of a paint being spent on the wrong kind of fact, and
   `--q1` is what it meant.) */
.nu-velA.acc .nu-vaf{ box-shadow: inset -6px 0 0 var(--flag); }
/* THE WEIGHT BAR'S CAP, AND WHY IT IS DRAWN AND NOT WRITTEN. The pitch bar
   had a badge to promote; this bar has only a number that ui/eight.js parks
   with an inline `insetInlineStart`, and an inline style outranks every rule
   in this file — a token cannot reach it. So the cap is `::after` here, and
   it finds the value WITHOUT ASKING THE JAVASCRIPT FOR A PIXEL: `benchVel`
   already writes `wrap.dataset.v` (0..7), so eight declarations turn the
   value into a fraction and the fill's own published formula
   (`calc(8px + (100% - 8px) * f)`, ui/eight.js benchVel paint) turns the
   fraction into the fill's leading edge. One owner of the number, two
   readers of it, and no new coupling.

   THE CAP IS OFFSET 3px BEHIND THAT EDGE ON PURPOSE, so the number lands
   inside it: eight.js parks the number at `edge + 5px` and it measures ~7px
   wide, so a 22px cap starting at `edge - 3px` covers it with room either
   side. MEASURED, all eight values at both widths, cap span against number
   span: at 390 (bar 67px) v0 6..28 / 13..20.2 … v7 44..66 / 51.8..59; at 1280
   (115px) v0 6..28 / 13..20.2 … v7 92..114 / 99.8..107. The number is inside
   the cap at every value and the cap never leaves the bar. The clamp's upper
   arm bites from v5 at 390 and v6 at 1280 — which is where eight.js has
   already re-anchored the number to `inset-inline-end: 8px`, so the two
   agree at the end of the travel without either knowing about the other. */
.nu-velA::after{ content: ""; z-index: 3; pointer-events: none;
  inset-block: calc((var(--sl-line) - var(--sl-cap)) / 2);
  inline-size: var(--sl-grab);
  inset-inline-start: clamp(var(--bw),
    calc(var(--bw) + 5px + (100% - 2 * var(--bw) - 8px) * var(--f, 0)),
    calc(100% - var(--bw) - var(--sl-grab))); }
.nu-velA[data-v="0"]{ --f: 0; }
.nu-velA[data-v="1"]{ --f: .142857; }
.nu-velA[data-v="2"]{ --f: .285714; }
.nu-velA[data-v="3"]{ --f: .428571; }
.nu-velA[data-v="4"]{ --f: .571429; }
.nu-velA[data-v="5"]{ --f: .714286; }
.nu-velA[data-v="6"]{ --f: .857143; }
.nu-velA[data-v="7"]{ --f: 1; }
.nu-van{ position: absolute; inset-block-start: 50%; transform: translateY(-50%);
  z-index: 4; font: var(--num-fw) .75rem var(--mono); color: var(--ink);
  pointer-events: none; }
/* REVERSED 2026-08-27, with the cap: this said `.nu-velA.hi .nu-van{ color:
   #FFFFFF; background: transparent }`, and it was right for a number floating
   on top of a full blue fill — white was the only legible ink there. The
   number no longer floats: at every value it sits on the cap's white plate,
   so white ink would be white on white. Ink at every level now, and the
   number's own 80%-panel plate (`background: color-mix(...)`, `border-radius:
   2px`, `padding: 0 3px`) goes with it — the cap IS the plate, and two plates
   under one number was the smear this wave is removing. */
.nu-velA.hi .nu-van{ color: var(--ink); }
/* a silent row goes FAINT and it never vanishes — the fade IS the sentence
   now. (`.nu-mutewhy` stood under this rule: a `position: absolute; inset: 0`
   span lying across the pitch cell and printing "rest — nothing sounds" /
   "held — 3̂ still rings" over the two sleeping bars. DELETED 2026-08-28. Paul:
   *"Let's get rid of the label strings on the pitch sliders. held and rest and
   the stuff that appears on top."* — and "the stuff that appears on top" is
   this rule's own `z-index: 5` said in English. WHERE ITS INFORMATION IS: the
   row's kind is the pressed segment of the kind button, three cells to its
   left, and the fade below says the bars are not writable. The REFUSAL did not
   go with the sentence: ui/eight.js `sync` puts it on each refused control as
   `data-why` + `title`, which is this page's refusal idiom and what
   test/text-diet.js T3 reads.) */
.nu-bench tr.is-rest .nu-pit, .nu-bench tr.is-rest .nu-velA,
.nu-bench tr.is-hold .nu-pit, .nu-bench tr.is-hold .nu-velA{
  opacity: .3; pointer-events: none; }

/* ==== THE LOOP STRIP (2026-08-30, the sampling round — ui/eight.js
   loopStrip). The pitch bar's own grammar one control over: the same
   --sl-line height, the same trough, the same hidden native inputs as the
   keyboard channel, and THE TOUCH LAW's paint half — `touch-action: none`
   on the strip and only the strip. Drawn only on sampled chairs (the JS
   decides; there is no greyed variant of this control on purpose). ==== */
.nu-loop{ display: flex; align-items: center; gap: 8px;
  margin-block: 6px; min-inline-size: 0; }
/* ...AND IT WRAPS AT 320, WHICH IS THE ONE WIDTH IT DOES NOT FIT (2026-09-02).
   test/loopstrip.browser.js S6 — *"nothing scrolls sideways"* — has been red at
   320 since the tray widened, and a DOM walk of the rendered page named the
   element: `button[data-k="loopresetvoice"]`, 100px wide, standing at x 225 to
   327 against a 320px viewport. The arithmetic is four items on one line — the
   label, a strip with a 60px floor, the looping word and the way back — plus
   three 8px gaps, and at 320 that is 7px more than the line.
   IT WRAPS RATHER THAN SHRINKING, and each of the three alternatives is
   refused by a rule this block already states. The strip cannot give up its
   60px: below that the two handles meet and the control cannot be worked. The
   two words cannot ellipsise: they are the control's own name and "the way
   back is VISIBILITY-hidden, never display-none … a control may not resize
   because you are using it" — a word that changed width with its own text
   would be that failure said sideways. And the gap cannot go to 5.6px, which
   happens to cover exactly 7.2px, because a number chosen to make one
   measurement pass at one width is the kind of fix that comes back.
   WRAPPING COSTS NOTHING IT PROMISED. `visibility: hidden` still holds the way
   back's seat, so the second line does not appear and disappear under a
   finger; above 320 the row never wraps at all (measured: one line at 375, 390,
   430 and 1280); and at 320 the strip gets the whole first line, which makes
   the drag EASIER rather than harder. */
.nu-loop{ flex-wrap: wrap; }
.nu-lplab{ font: var(--num-fw, 500) .75rem var(--mono);
  color: var(--ink); opacity: .7; flex: none; }
.nu-lps{ position: relative; display: block; flex: 1 1 auto;
  block-size: var(--sl-line); min-inline-size: 60px;
  touch-action: none; cursor: ew-resize; }
.nu-lpb{ position: absolute; inset-inline: 0; display: block;
  inset-block: calc((var(--sl-line) - var(--sl-trough)) / 2);
  background: var(--faint); }
.nu-lpf{ position: absolute; inset-block: 0; background: var(--hand);
  display: block; }
/* the two handles: caps a thumb can own, standing ON the value. The 2px
   translate splits the pair optically when they meet at the GAP. */
.nu-lph{ position: absolute; inset-block: -4px; inline-size: var(--bw-hard);
  min-inline-size: 3px; background: var(--ink); display: block; z-index: 2;
  transform: translateX(-50%); }
.nu-lph.b{ background: var(--ink); }
/* absent = the zone's own points: a reading, not a statement */
.nu-loop.abs .nu-lpf{ opacity: .25; }
.nu-loop.abs .nu-lph{ opacity: .45; }
.nu-lpin{ position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
  margin: 0; opacity: 0; pointer-events: none; min-height: 0; min-block-size: 0; }
.nu-lps:focus-within{ outline: var(--bw-hard) solid var(--hand);
  outline-offset: 2px; border-radius: var(--r2); }
.nu-lpon, .nu-lpr{ flex: none; font: var(--num-fw, 500) .75rem var(--mono); }
/* the way back is VISIBILITY-hidden, never display-none: hiding it with
   display re-lays the flex row and the strip changes width — which it did,
   measured 2026-08-30 at 320, UNDER A MOVING FINGER (the first landed value
   showed this word and the bar shrank 110px mid-drag). A control may not
   resize because you are using it, so the word always holds its seat. */
.nu-lpr.off{ visibility: hidden; }
/* (`.nu-benchbar` stood here — the flex line that held the lattice pointer and
   the refused `♯ accidentals` toggle above every motif's rows. DELETED
   2026-08-28 with the control: Paul, *"accidentals need the chromatic alphabet
   - not wired; the bar locks to the scale -- get rid of that"*. ui/eight.js's
   tombstone at `benchRefusal` carries the argument, including which of the two
   permitted answers it is — the control is REMOVED, not made to work.) */
/* (`.nu-wisdom` STOOD HERE — the rail under every motif's rows, reserved at
   its own WORST sentence per width (`calc(3lh + .6em + 2*--bw)`, re-measured
   twice: 69.3px at 390, 49.6px at 1280) so that speaking a row moved nothing
   below it. DELETED 2026-08-28. Paul: *"The box that says tap a row - the step
   is read here should just go."* ui/eight.js carries the tombstone that says
   where each fact in its sentence already was; what this rule contributed —
   the reserve — is the measurement that goes with the deletion: every motif
   block on the page is 69.3px shorter at 390 and 49.6px shorter at 1280, and
   there is one block per open cell.)

   ===== THE STEP LABEL IS THE ROW'S IDENTITY, AND THE ROW GOES DARK AS IT
   SOUNDS (2026-08-28) ======================================================
   Paul: *"The step labels should be big and centered and the entire box should
   go dark as they play."*

   THE LABEL. The count column was `.nu-grid`'s: 0.8rem, left-aligned, 4px of
   padding — a caption beside the row. It is the row's NAME now, so it is the
   machine face at 1.25rem, centred in its 34px column, and it is the only text
   left on a bench row (the header words, the pitch badge, the mute sentence
   and the rail all went today). Nothing about the GEOMETRY moves: the column
   is still 34px and the row is still the promised 52px — a 20px face in a 52px
   line has 16px of room either side, measured.

   THE DARK, AND WHY IT IS DRAWN BY `:has()` RATHER THAN BY A CLASS. The clock
   may write inside `[data-live]` and nowhere else (test/motif-frozen.js A3
   compares `#app` byte for byte across playback with every `[data-live]`
   emptied), and `window.__eightFrozen` empties a live element's CHILDREN while
   keeping the element AND ALL ITS ATTRIBUTES. A `class` toggled on the `<tr>`
   — or on the `<th>`, which is the live element itself — is therefore part of
   the frozen picture and would fail A3 on the first beat. The `<mark>` INSIDE
   the count cell is not: it is a child of `[data-live="count"]`, it is what
   `mark()` has always written, and it is emptied out of the snapshot. So the
   row reads its own playhead: `tr:has(> th mark)` is exactly "the step in this
   row is sounding", written once, with no second writer and no new clock path
   at all. THE PLAYHEAD'S JAVASCRIPT DID NOT CHANGE A CHARACTER for this.

   WHAT A BROWSER WITHOUT `:has()` GETS, said plainly rather than left to be
   discovered: the row does not darken, and the count cell still wears the
   browser's own `<mark>` highlight, which is what this page had yesterday. The
   class-based fallback this file uses elsewhere (`.is-on` beside
   `:has(:checked)`) is NOT available here — a class is exactly the thing the
   frozen law forbids the clock to write — so the fallback is the honest one:
   less paint, never a wrong picture. Chrome 105+, Safari 16.4+, Firefox 121+
   have it (this file's own note at the top).

   AND THE SELECTOR NAMES THE LIVE DECLARATION, `th[data-live="count"]`, for
   two reasons and the second one is arithmetic. It reads better — the paint
   depends on the cell the page DECLARED the clock may write in, which is the
   same fact `mark()` refuses to write without — and it carries the specificity
   the rule needs. MEASURED, because it was wrong for an hour: `.nu-grid > tr:
   nth-child(even) > td { background: none }` is (0,2,2) and
   `.nu-bench tr:has(> th mark) > td` is (0,1,4), so the zebra-suppressing rule
   won on EVERY EVEN ROW and the playhead darkened only the odd ones — a bug
   that passes any check that happens to land on step 1. `:has()` takes the
   specificity of its most specific argument, so naming the attribute makes it
   (0,2,4) and the row goes dark whichever step it is.

   THE PAINT IS THE PAGE'S OWN INK, at full strength, because a playhead that
   is a tint is the thing this file already refused twice ("a tint in a step
   grid fights the playhead"). The controls keep their identity ON the dark:
   the two caps are the panel plate and stand out as the handles they are, and
   the kind segments give up their white ground for a 12% wash so the row reads
   as one dark band rather than as three white boxes on a dark ribbon. Measured
   for contrast: --paper on --ink is 18.02:1 (this file's own number, the other
   way round), and the pressed segment stays --hand + white, 7.10:1. */
.nu-bench th:first-child{ text-align: center; padding-inline: 2px; }
.nu-bench th span, .nu-bench th mark{ font: var(--num-fw) 1.25rem/1 var(--mono); }
.nu-bench tr:has(> th[data-live="count"] mark) > th,
.nu-bench tr:has(> th[data-live="count"] mark) > td,
.nu-bench tr:has(> th[data-live="count"] mark) > th:first-child{
  background: var(--ink); color: var(--paper); }
/* the browser's own highlight has served its purpose the moment the whole row
   carries it — the label is ink on paint either way, so it gives up its plate */
.nu-bench tr:has(> th[data-live="count"] mark) th mark{ background: transparent; color: var(--paper); }
.nu-bench tr:has(> th[data-live="count"] mark) .nu-seg{ background: transparent;
  border-color: color-mix(in srgb, var(--paper) 45%, transparent); }
.nu-bench tr:has(> th[data-live="count"] mark) .nu-segb{
  background: color-mix(in srgb, var(--paper) 12%, transparent);
  color: var(--paper);
  border-inline-start-color: color-mix(in srgb, var(--paper) 45%, transparent); }
.nu-bench tr:has(> th[data-live="count"] mark) .nu-segb[aria-pressed="true"]{
  background: var(--hand); color: var(--on-fill); }
.nu-bench tr:has(> th[data-live="count"] mark) .nu-segb:disabled{
  color: color-mix(in srgb, var(--paper) 40%, transparent);
  background: transparent; }
/* THE KIT CELL — a button whose fill's width is its velocity. `pan-y` is the
   whole touch story: sideways writes the value, vertical is still the page. */
.nu-kcTd{ padding: 0; }
.nu-kc{ position: relative; display: block; inline-size: 100%;
  block-size: var(--cell); min-height: 0; border: 0; border-radius: 0;
  background: var(--well); cursor: pointer; padding: 0;
  touch-action: pan-y; }
.nu-kc:hover{ background: color-mix(in srgb, var(--flag-tint) 60%, var(--well)); }
/* THE KIT CELL'S FILL TAKES THE SAME RAMP (2026-09-02) — same argument as the
   weight bar one block up, same `data-v` (0..7, written by `paintKc`), same
   four steps, and the same `--flag` edge left on the accent. Sixteen steps of
   a lane side by side is where a ramp earns its keep: the shape of a groove is
   readable down the column now instead of being one blue against one grey. */
.nu-kf{ position: absolute; inset-inline-start: 3px; inset-block: 6px;
  border-radius: var(--r2); background: var(--q4);
  border: var(--bw) solid var(--rule-strong); pointer-events: none; display: block; }
.nu-kc[data-v="1"] .nu-kf, .nu-kc[data-v="2"] .nu-kf{ background: var(--q1); }
.nu-kc[data-v="3"] .nu-kf, .nu-kc[data-v="4"] .nu-kf{ background: var(--q2); }
.nu-kc[data-v="5"] .nu-kf, .nu-kc[data-v="6"] .nu-kf{ background: var(--q3); }
.nu-kc[data-v="7"] .nu-kf{ background: var(--q4); }
/* (`.nu-kf.gh{ background: var(--faint); border-color: var(--dim) }` stood here
   and went with the ramp — see the weight bar's tombstone for why `--faint`
   was the wrong paint for a quiet note. The ghost keeps its own class, which
   `paintKc` still writes and which nothing now reads; it is left on the
   element because writing it is `ui/eight.js paintKc`'s line, not this file's,
   and a stylesheet does not get to delete a class it merely stopped reading.
   Checked before writing this: no gate in test/ reads `.gh`.) */
.nu-kf.acc{ box-shadow: inset -5px 0 0 var(--flag); }
.nu-kn2{ position: absolute; inset-inline-end: 3px; inset-block-start: 50%;
  transform: translateY(-50%); font: 700 10px var(--mono); color: var(--ink);
  pointer-events: none;
  background: color-mix(in srgb, var(--panel) 80%, transparent);
  border-radius: 2px; padding: 0 2px; }

/* ---- THE LANE SAYS ITS DRUM'S NAME, DOWN THE COLUMN — 2026-09-03 ---------
   Paul: *"in the drum editor, fully label the names of the parts of the
   kits."* The header was one letter with the word in a `title`, which on a
   touch surface is no label at all.

   VERTICAL IS ARITHMETIC HERE, NOT TASTE. Steps run DOWN this grid (the
   rotation of 2026-08-25), so a lane is a `--cell`-wide COLUMN: 36px. "closed
   hat" is 10 characters — about 68px at the 0.8rem `.nu-grid th` size — so set
   ACROSS the column it is either clipped or it doubles the table's width, and
   a twelve-lane kit is already 20 + 12 x 36 = 452px against the 296px a 320px
   phone leaves. Set DOWN it costs the header its height and the table nothing,
   and the longest word in the table fits at every viewport with no ellipsis.
   `vertical-rl` + `rotate(180deg)` is the pair every browser this page names
   supports; `sideways-lr` says it in one declaration and Chrome only learned
   it in 130, so it is not used.
   `vertical-align: bottom` sits the words on the grid's first rule, so twelve
   names of different lengths are aligned where they meet the drums and ragged
   at the top, which is how a column header reads. */
.nu-grid th.nu-lanehead{ vertical-align: bottom; padding-block: 6px 4px; }
.nu-grid th.nu-lanehead > span{ display: inline-block; white-space: nowrap;
  writing-mode: vertical-rl; transform: rotate(180deg);
  text-align: start; }

/* ---- ...AND THE REST OF THE KIT, OFFERED UNDER IT ------------------------
   `+ kick / + snare / + ride` under the grid. It was `.nu-memadd`'s rule
   verbatim — the band pane's own "hire another player" row — because it is the
   same gesture on a different roster, and ui/eight.js `laneAdd` says so at
   more length. It was written as its own selector rather than added to that
   one so the two could part company; the roster's went with the pane on
   2026-09-04 and this is the surviving copy, which is what that choice was
   for. */
.nu-laneadd{ display: flex; flex-wrap: wrap; gap: var(--s2);
  margin-block: var(--s3); }/* THE FACE. Same size and same stack as `.nu-tf` and for the same two reasons
   written there — 1.2rem was measured against the 44px floor, and `--sym`, the
   symbol stack, keeps every row in one hand rather than letting a serif ↕ sit
   beside a sans ↑. `pointer-events: none` so the picture can never eat the
   press, which is the half-hour the globe round lost to a decorative stroke
   swallowing every tap. */
/* ...AND IT IS A CONTAINING BLOCK, WHICH IS A MEASUREMENT (2026-09-05).
   `.nu-vh` is `position: absolute` with no offsets, so it lands at its static
   position inside whatever the nearest POSITIONED ancestor is. Everywhere this
   face was used before today that ancestor was near and the page was not
   scrolled; the table's cells put one inside a `.nu-pane` that is 294px wide
   and 957px of table across, and an absolutely positioned box is NOT clipped
   by an ancestor that is merely `overflow: auto` — so the hidden word of a
   cell nine columns to the right sat at x=900 against the initial containing
   block and the WHOLE PAGE scrolled sideways: measured 320 -> 547 at a 320px
   viewport, on a table whose pane was clipping correctly. One `relative` puts
   the word back inside the cell that owns it. */
.nu-ic{ display: inline-flex; align-items: center; gap: .06em;
  position: relative; pointer-events: none; }
.nu-g{ font-size: 1.2rem; line-height: 1; letter-spacing: -.02em;
  font-family: var(--sym); }
/* A DRAWN GLYPH IS A BOX, NOT A LETTER (2026-09-08, ui/glyph.js `drawGlyph`).
   It carries no baseline of its own, so it is centred by the flex it sits in
   and nothing else — which is the whole point of drawing it: `⚄` sat small and
   low because a symbol font decided where a die belongs among letters. */
.nu-g-drawn{ display: inline-flex; align-items: center; justify-content: center;
  line-height: 0; }
.nu-g-drawn > svg{ display: block; }
/* THE NUMBER, AND IT IS THE ONLY TEXT THAT SHOWS. Paul: "more symbol plus the
   number 2." Smaller than the mark and bolder than the page, so it reads as an
   index on a picture rather than as a word beside one. Tabular figures because
   a strip of ten voices may not shuffle sideways at voice 10. */
.nu-n{ font-size: .78rem; font-weight: var(--fw-label); line-height: 1;
  font-variant-numeric: tabular-nums; }
/* THE OPEN TAB OF THE NINE KEPT ITS WORD, AND IN THE STRIPE IT CANNOT
   (2026-08-28). What stood here was `#toptabs > button[aria-pressed="true"]
   .nu-vh { position: static; … }` and its argument was: "the nine are two
   lines at 390 whatever they wear, so the word is FREE there — and one
   labelled tab in a row of pictures is the thing that tells a first-time
   reader what the pictures are." The width was free in a row and is not in a
   47px column: "performance" is 123.7px of type and there is nowhere for it to
   go but out through the rule.
   NOTHING WAS DELETED, ONLY UNREVEALED. Every mark still carries its full word
   as `aria-label` AND as a `.nu-vh` span, so a screen reader still hears
   "Where" and the page still reads as its nine names in order with this
   stylesheet off — which is the promise the reveal was never the load-bearing
   half of. What tells a first-time reader what a picture is, is the hold and
   the hover (ui/glyph.js, the same day), and that reaches a thumb as well as a
   mouse, which a printed word in a row nobody scrolls to never did. */
/* …AND THE FOUR NESTED ROWS TAKE THEIR WORD BACK WHEN THERE IS ROOM FOR IT.
   Above 700px the strips are nowhere near their width and hiding a name there
   buys nothing: the band's open tab says `♪2 schola`, the board's says
   `≋3 reverb`, the deck's says `▤ notation` — and the panel under a voice tab
   is named again, which it was NOT for the first hour of this change and which
   was the one thing lost rather than saved. Below 700px they are marks and
   digits, which is the line Paul asked for back.

   700px AND NOT THE PAGE'S OTHER BREAKPOINT (430, where `.nu-bar` prints
   "room"). MEASURED, and against the WORST case rather than the shipped one:
   the widest thing any of these rows can have open is `◈ performance` at
   123.7px, and the band strip with that open plus its three add buttons comes
   to 440px of ink and gap, which wants a 464px viewport. A breakpoint at 430
   would make the row one line for some tabs and two for others AT THE SAME
   WIDTH — the strip would jump a line as you moved along it, which is worse
   than either state. 700 clears every combination by 200px and is
   unambiguously not a phone.
   AND IT IS A SWEEP, NOT ONE SAMPLE: every tab of every row opened in turn at
   390, 700 and 1280 — band form/performance/cantor/schola, the three motifs,
   the six board tabs, both deck views — and each row holds the SAME number of
   lines whichever of its tabs is open (band 1, motifs 1, board 2, deck 1), with
   `row.scrollWidth === row.clientWidth` and no sideways scroll on the document
   at any of the forty-five. That is the check the number was chosen for: a
   strip that changes height as you move along it is the failure, not a wide
   one. */
@media (min-width: 700px) {
  /* (`#tabs`, `#motif-tabs` and `.nu-decktabs` stood in this list until
     2026-08-28 and are in the gutter now, where there is no width to give
     back at any viewport — see the note above. `#boardtabs` is the one of the
     four still drawn as a row, so it is the one selector left.) */
  #boardtabs button[aria-pressed="true"] .nu-vh{
    position: static; width: auto; height: auto; margin: 0 0 0 var(--s1);
    overflow: visible; clip-path: none; }
}

/* ===== THE EXPLAINER (2026-08-28) ========================================
   Paul: *"Provide a simple long tap or hover tooltip to explain what they
   are."* This REVERSES nukernel/ideal/design-system.html's voice rule 5 ("No
   tooltips. A tooltip is information hidden from a finger") and that rule is
   REWRITTEN THERE rather than deleted, because its own reasoning is what says
   how to obey both: the objection was to the HOVER, so there is a LONG-PRESS
   as well and nothing is hidden from a finger. ui/glyph.js carries the full
   argument and the gesture numbers.

   `position: fixed` IS THE LOAD-BEARING DECLARATION. The box is positioned,
   never inserted into flow, so opening one cannot move a row under a thumb —
   which is the whole difference between an explainer and a layout bug on this
   page. It is clamped into the viewport by ui/glyph.js rather than allowed to
   overhang, because a box hanging off the right edge is A1 (no sideways
   scroll) failing in a place nobody looks.

   IT IS NOT A CONTROL AND IT DOES NOT TAKE A PRESS: `pointer-events: none`, so
   the press that would close it reaches whatever is underneath and the popover
   can never swallow a tap on the row it is explaining. */
/* WHAT IS LEFT OF THE POPOVER (the rest is `.nu-strip-out`'s, 2026-09-02):
   it is the one strip that is NOT a control — `pointer-events: none`, so the
   press that would close it reaches whatever is underneath — it rides one
   layer over the other two because it explains them, and `ui/glyph.js place()`
   writes its `inset-block-start`/`inset-inline-start` per opening, which is why
   it states no anchor here (and why it is the one strip that needs no edit if
   the gutter ever moves: it MEASURES the tray).
   …and it may not be wider than the column it is clamped into: the gutter is
   56px of the viewport since 2026-08-28 and a 30ch box that could not fit
   beside it would be clamped to a negative left. `place()` does the clamping;
   this is the same arithmetic said to the box. */
.nu-say{ z-index: 60; pointer-events: none;
  max-inline-size: min(30ch, calc(100vw - 12px));
  padding: var(--s3); }

/* ===== `.nu-preview` — A PICTURE OF THE THING (2026-09-02) ================
   Paul, 2026-09-01, on the aesthetic: *"classic-era System 8/9 MacOS — lots of
   previews, small widgets, dynamic nav/menus, and flyouts"*; and on the motif
   editor: *"the motif editor should show me previews of the instruments using
   the motif."*

   IT IS THE SMALLEST HONEST PICTURE OF A PHRASE: sixteen bars in a 4ch x 1.2em
   box, one per step, height = velocity. Not an icon (an icon says WHICH KIND
   of thing), not a label (a label says its NAME) — a THUMBNAIL, which says
   what this particular phrase looks like, so two motifs in a list are told
   apart by shape before either name is read. The precedents on this page are
   `#scoredeck .nu-legv > i` and `ideal/composer.html:295`'s `.legend i`; the
   precedent for the SIZE is the tray's mark, which is why it is `em`-sized and
   sits on a text baseline instead of being a block.

   THREE MARKS AND NO FOURTH, because at 4ch there is room for three:
     · a STEP that sounds — a rect whose height is its velocity, 1..7 of 8;
     · a REST — a 1px floor, `--faint`, so the grid's rhythm is still visible
       (sixteen columns with holes in them is unreadable at this size);
     · a HOLD — the continuation of the step before it, at .45 opacity, so a
       long note reads as one shape rather than as two hits.
   No pitch. A 1.2em box has ~9 usable pixels of height and it is spending all
   of them on loudness; a contour drawn in the same 9px is a smudge, which is
   the lesson `.nu-tf`'s deleted SVG faces already paid for.

   COLOUR: `--hand`, the paint of a thing you set — or the PLAYER's hue if the
   preview sits inside a `[data-vi]` element, which is the whole point of
   putting a preview beside a member box. `fill: currentColor` is what makes
   that one declaration instead of one per rect.

   THE BUILDER IS `nukernel/ui/preview.js` and it has no caller yet; the tray,
   the roster and the motif editor are wave 2's. */
.nu-preview{ --pv-base: 4ch; display: inline-block; vertical-align: -.2em;
  inline-size: 4ch; block-size: 1.2em;
  color: var(--vpaint, var(--hand)); overflow: visible; }
.nu-preview rect{ fill: currentColor; }
.nu-preview .nu-preview-rest{ fill: var(--faint); }
.nu-preview .nu-preview-hold{ opacity: .45; }

/* ---- THE VIDEO DECK (2026-09-01) --------------------------------------
   A film cut to the record's own form, over a strip that says what section
   is on screen. Deliberately plain: the picture is the loud thing here and
   chrome around it would compete with the one element that is doing the
   work. Same tokens as every other deck, no new colours.

   ...AND IT IS ON THE TOKENS NOW, WHICH IT WAS NOT (2026-09-02). Written on
   2026-09-01, this block was the newest code on the sheet and the most
   off-system: `.6rem` / `.25rem` / `.1rem` / `.2rem .45rem` / `.4rem` /
   `.78rem` typed against a five-step spacing scale and a six-step type scale
   that already existed, and `1px solid var(--rule)` where every other frame on
   the page says `var(--bw)`. Nothing here CHANGED its mind; it just never got
   pointed at the tokens. Substituted, one for one:
     .6rem -> --s3 · .25rem -> --s2 · .1rem -> --s1 · .2rem .45rem -> --s1 --s3
     · .4rem -> --s2 · .78rem -> --t1 · 1px -> --bw · 600 -> --fw-label
     · 3px/6px radii -> --r2/--r1.
   `#000` and `#0c0a1a` STAY, and they are the exemption this block is allowed:
   they are inside the two STAGES, where a black picture ground is the subject
   rather than a decision about the chassis. */
.nu-video { display: flex; flex-direction: column; gap: var(--s3); }
.nu-video-canvas {
  inline-size: 100%; block-size: auto; aspect-ratio: 4 / 3;
  background: var(--stage); display: block; border: var(--bw) solid var(--rule);
}
.nu-video-form {
  display: flex; flex-wrap: wrap; gap: var(--s2);
  list-style: none; margin: 0; padding: 0;
}
.nu-video-sec {
  display: flex; flex-direction: column; gap: var(--s1);
  padding: var(--s1) var(--s3); border: var(--bw) solid var(--rule);
  border-radius: var(--r2);
  font-size: var(--t1); line-height: 1.15; opacity: .55;
}
.nu-video-sec b { font-weight: var(--fw-label); }
.nu-video-sec span { font-variant-numeric: tabular-nums; opacity: .75; }
.nu-video-sec.on { opacity: 1; border-color: currentColor; }
.nu-video-cap { margin: 0; font-size: var(--t1); opacity: .7; }

/* the stage is what goes fullscreen, so the controls stay reachable inside it */
.nu-video-stage { position: relative; background: var(--stage); line-height: 0; }
/* both spellings: Safari needs the -webkit- pseudo-class, and a selector list
   containing one it does not know would drop the WHOLE rule, so they are
   written as separate rules on purpose */
.nu-video-stage:fullscreen { display: flex; align-items: center; justify-content: center;
  inline-size: 100vw; block-size: 100vh; }
.nu-video-stage:-webkit-full-screen { display: flex; align-items: center;
  justify-content: center; inline-size: 100vw; block-size: 100vh; }
.nu-video-stage:fullscreen .nu-video-canvas {
  inline-size: auto; block-size: auto; max-inline-size: 100vw; max-block-size: 100vh; }
.nu-video-stage:-webkit-full-screen .nu-video-canvas {
  inline-size: auto; block-size: auto; max-inline-size: 100vw; max-block-size: 100vh; }
/* THE SCREENSAVER (2026-09-01, "a new view like the video view"). Same shape
   as the video deck — stage, canvas, the video deck's own control bar reused —
   but the canvas is 16/9 and in fullscreen it FILLS the screen instead of
   letterboxing: a screensaver with black bars is a window, not a sky. The
   background is the old chart's #0c0a1a, not #000.

   2026-09-02 — THE CANVAS IS WEBGL NOW, AND NOT ONE RULE HERE CHANGED. Paul:
   "It should be the little aliens dancing, not the infinite wandering", and
   "Why not three js? It's fine. Don't reinvent." The block below was written
   for a 2D context; it holds for a WebGLRenderer because ui/screensaver.js
   calls `setSize(w, h, false)`, which writes the drawing buffer and leaves the
   style alone — so `inline-size:100%` + `aspect-ratio:16/9` and the two
   fullscreen spellings are still the only things deciding how big the picture
   is. The `background:#0c0a1a` on the canvas is now the colour a reader sees
   for the fraction of a second before the renderer's first frame (and the
   colour of the refusal, if a browser hands back no context), so it stays. */
/* (TOKEN SUBSTITUTION ONLY, 2026-09-02: `.6rem` -> `--s3` and `1px` -> `--bw`.
   Nothing else in the screensaver block moved — it is wave 0d's as it stands
   and its two fullscreen spellings, its `16/9` and its `#0c0a1a` are all
   decisions this round has no quarrel with.) */
.nu-saver { display: flex; flex-direction: column; gap: var(--s3); }
.nu-saver-stage { position: relative; background: var(--stage-saver); line-height: 0; }
.nu-saver-canvas {
  inline-size: 100%; aspect-ratio: 16 / 9; display: block;
  background: var(--stage-saver); border: var(--bw) solid var(--rule);
}
/* both spellings, separate rules — the video stage's own warning: a selector
   list with one unknown pseudo-class drops the WHOLE rule */
.nu-saver-stage:fullscreen { inline-size: 100vw; block-size: 100vh; }
.nu-saver-stage:-webkit-full-screen { inline-size: 100vw; block-size: 100vh; }
.nu-saver-stage:fullscreen .nu-saver-canvas {
  inline-size: 100%; block-size: 100%; aspect-ratio: auto; border: none; }
.nu-saver-stage:-webkit-full-screen .nu-saver-canvas {
  inline-size: 100%; block-size: 100%; aspect-ratio: auto; border: none; }

.nu-video-controls { display: flex; gap: .4rem; }
.nu-video-controls button {
  font: inherit; font-size: .78rem; padding: .2rem .6rem;
  background: transparent; color: inherit;
  border: 1px solid var(--rule); cursor: pointer;
}
.nu-video-controls button:hover { border-color: currentColor; }
/* ---- AND BIGGER AGAIN WHERE THE COLUMN CAN HOLD IT --------------------
   Paul, wave 4: *"Type should be bigger too."* `--t2` for the word and `--t1`
   for the line under it are the FLOOR — the sizes that fit the 87px column a
   320px phone gives the stripe. `--tray-w` reaches its 176px ceiling at 629px
   of glass, and 176 is 134px of word column: "Screensaver", the longest word
   the table holds, draws 88px at `--t3` there, so a step up costs no ellipsis
   and no second line. The query is at 900px rather than at 629 because the
   step is worth taking where a reader is at a desk, and because 629-900 is
   where the tray is at its ceiling but the CONTENT column is still narrow
   enough that the gutter should not be shouting.
   ONE STEP EACH, and they keep their relation: the word takes the body size
   and the second line takes the word's old one, so the row still reads as a
   name over an annotation rather than as two names. */
@media (min-width: 900px) {
  #nu-tray .nu-vh{ font-size: var(--t3, 1rem); }
  #nu-tray .nu-sub2{ font-size: var(--t2, .82rem); }
}
/* ---- THE FLYOUT CHASSIS ----------------------------------------------
   Paul: *"Perhaps we should experiment with strips that fly out to give us
   access to all options, like MacOS system settings used to."*

   THIS IS `.nu-log`'S SIX DECLARATIONS, FACTORED — fixed, anchored at the
   gutter's inner edge, the panel plate, `--bw` of ink, the hard shadow, its
   own scroll, and `[hidden]`. It is written here as `.nu-strip-out` because
   that is the name the design round gives it and the seed flyout is its first
   user; the second agent is factoring `.nu-log`, `.nu-explain` and `.nu-say`
   onto the same class in the same week, at which point this rule is the one
   they land on. NOTHING MAY OVERHANG THE GUTTER (shell A6i): the near edge IS
   `--tray-w`, so the strip grows away from the stripe, which is the only
   direction there is room in. */
.nu-strip-out{ position: fixed; z-index: 55;
  inset-inline-start: var(--gl);
  inset-block-end: calc(var(--bar-h) + 4px);
  inline-size: min(24ch, calc(100vw - 2 * var(--gl)));
  max-block-size: min(70vh, 560px); overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0; padding: var(--s3);
  background: var(--panel); color: var(--ink);
  border: var(--bw) solid var(--ink); border-radius: var(--r1, 6px);
  box-shadow: var(--shadow); }
.nu-strip-out[hidden]{ display: none; }

/* ---- THE SEED: A DIE AND A NUMBER (2026-09-03) -------------------------
   Paul: *"Instead of a popup for seed, just get rid of the word seed and put
   the number. I tap the die and there's a new number. I tap the number and I
   can enter a new number by hand. Instead of stopping the music compose the
   new song then show a countdown until the new version plays."*

   THIS BLOCK WAS `.nu-seedout`, THE FLYOUT'S COLUMN — a `type=number` field,
   a 0..65536 vertical fader, and two 44px actions under it. Its argument
   ("65,536 values over 180px is ~364 a pixel, so the fader is for finding a
   neighbourhood and the field is for landing on a house") was answered by
   Paul the next day: the die IS the roaming, and the field is the landing, so
   the panel had one control that could not be reached faster from the foot.
   ui/eight.js's own note accounts for every part of it.

   THE ROW WRAPS AND NOTHING SHRINKS. `--tray-w` is `clamp(96px, 28vw, 176px)`,
   so the stripe is 109.2px at 390 (101.2 inside its 4px padding) and 176 at
   1280 — measured — and two tap targets fit on one line at every width the
   clamp allows: die 44, number 53 at 390 and 119.8 at 1280. `flex-wrap` is
   there for the floor rather than for the phone: at 96px the number drops
   under the die instead of either target going below 44. `justify-content:
   center` because the seed is the only mark in the foot that shares its
   line. */
.nu-seedrow{ display: flex; flex-wrap: wrap; align-items: center;
  justify-content: center; gap: var(--s1); }
.nu-seedrow > #rewrite{ flex: 0 0 auto; }
/* THE DIE'S WORD IS HIDDEN AND NOT DELETED — `.nu-vh`'s ORIGINAL four
   declarations (:2603), put back for the one mark in the gutter that gives its
   second column to something else. Paul: *"just get rid of the word seed and
   put the number."* The number IS the word's position, made pressable; the
   span stays in the DOM because this page's own rule is that it reads as
   itself with the stylesheet off, and test/shell.js A6g/A6h is where that is
   asserted. Out of flow, so the mark's grid column 2 is empty and the button
   is its 44px and no wider. */
.nu-seedrow > #rewrite .nu-vh{ position: absolute; inline-size: 1px;
  block-size: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%);
  white-space: nowrap; }
/* (SEVEN RULES FOR `#seedval` — the number's pill, its box, its `[hidden]`
   twin and its `.nu-rd` face — STOOD HERE, 2026-09-03 to 2026-09-07. Paul:
   *"Get rid of seed number too."* The control is deleted from the page and so
   are its rules: a selector left behind is not dead code, it is a control that
   stops working in a place the file still describes, which is the sentence
   §18g had to write about six of them.) */
/* THE FIELD STANDS EXACTLY WHERE THE NUMBER STOOD, so committing a number
   does not move the row under the thumb that typed it. 16px is the iOS zoom
   floor and a control this page asks a reader to TYPE in may never be under
   it. */
.nu-seedrow > #seedin{ flex: 1 1 auto; min-inline-size: var(--tap);
  min-block-size: var(--tap); inline-size: 0;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 16px; text-align: center; }
/* (`.nu-seedwait` AND ITS FOUR RULES STOOD HERE. Paul, 2026-09-07: *"Instead
   of a beat countdown when I hit dice replace the die icon with the countdown.
   Will save space."* The countdown is the DIE'S OWN FACE now — ui/eight.js
   `paintDie` writes the beats into `#rewrite`'s `.nu-g` and hangs
   `data-live="pending"` on it, which is what paints a clock reading in
   `--clock` everywhere on this page. One node fewer, one line of the bar
   saved, and the number stands on the control the gesture was made on.) */
/* THE DIE'S FACE WHILE IT IS COUNTING. The glyph slot holds up to two digits
   instead of one 1.2rem mark, so the figures are tabular and a hair smaller —
   nothing else about the button moves, and its 44px floor is `.nu-bar
   button`'s. */
#rewrite .nu-g[data-live]{ font-family: var(--mono);
  font-variant-numeric: tabular-nums; font-size: var(--t2, .82rem);
  font-weight: var(--num-fw); }

/* ---- THE NAME PLATE ---------------------------------------------------
   Paul: *"The name of the genre should be obvious."* One ink plate, used by
   the foot's Where mark (through `.nu-vh`/`.nu-sub2`, which need no rule of
   their own) and by the Rules panel's heading. It is `.nu-busname`'s look,
   named for every surface that prints a name; the second agent factors the
   board's own plate onto it in the same week. */
.nu-namebar{ display: flex; align-items: baseline; gap: var(--s2);
  margin: 0 0 var(--s3);
  padding: var(--s2) var(--s3);
  background: var(--ink); color: var(--paper);
  border-radius: var(--r2, 3px);
  font-size: var(--t4, 1.15rem); font-weight: 800; }
.nu-namebar small{ font-size: var(--t2, .82rem); font-weight: 600;
  opacity: .75; }
/* (A BARE `=======` STOOD ON THE NEXT LINE UNTIL 2026-09-02 — a conflict
   marker left in the tree by the wave-1a/1b merge. It is not harmless in a
   stylesheet: an unexpected token at the top level makes the parser discard
   everything up to and including the next block, so `.nu-video-controls`
   below it was DEAD on the shipped page and the film's two buttons had no
   layout. Found by the slice that appends after it; deleted, and named here
   so nobody re-argues where it went.) */
/* THE CONTROL BAR GETS THE ENAMEL PLATE BACK (2026-09-02). It read
   `background: transparent; color: inherit; border: 1px solid var(--rule);
   cursor: pointer` — three declarations that OVERRODE the button face at the
   top of this file (white plate, jet border, `--r1`, the hard shadow) and one
   that restated it. So the film's own two buttons were the only buttons on the
   page that were not buttons, on the newest deck, which is how a design system
   comes apart: not by decision, but by a block being written before anybody
   read the base rule. All four are gone; `--btn-fs` is what the size was
   reaching for. Only the layout stays here, which is all this bar ever owed.
   (`:hover{ border-color: currentColor }` went with them — the base rule's
   `--flag-tint` hover is the page's one answer to "about to matter".) */
.nu-video-controls { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* ==========================================================================
   SLICE 2a — TEMPO AND KEY (2026-09-02)
   Paul, B7: *"Tap tempo, the tempo editor appears, same for key. The tempo
   editor does not reflect the richness of our tempo options. Key may not
   either. … The left nav elements for tweaking tempo should be brought inside
   tempo."*

   Six rules. Everything else the two panels draw is already a `.nu-field`, a
   `.nu-row` or a `.nu-combo` from wave 1b — which is the point of a design
   system, and the reason a slice that adds nine controls adds six rules.
   ========================================================================== */

/* THE TEMPO, BIG. `--t5` is the display step ("a number you read across the
   room") and `--fw-display` its weight; this is the first surface on the page
   to spend either. `tabular-nums` because it changes under a dragging finger
   and a proportional `1` would make the number JUMP sideways between 111 and
   188 — the same reason the countdown and the clock use it. It sits tight
   under the heading and tight over its own slider: the readout and the control
   are one object, so the air goes round the pair and not between them. */
/* ...AND IT IS AN <input type=number> SINCE 2026-09-05 (DESIGN.md 8: "the
   number printed and typeable beside it"). Everything above still holds — the
   size, the weight, the tabular figures, the air round the pair — and what is
   added is the four rules a number input needs to stop looking like a form
   field: no chrome of its own, the page's own ink and ground, the hairline
   only when it has focus (a control is a hair wider than its word), and no
   spinner, which at --t5 is a 30px pair of arrows over the tempo. The field
   is `--tap` tall like every other control on this page. */
.nu-bpmbig{ display: block; margin: 0 0 calc(var(--s1) * -1);
  font-size: var(--t5); font-weight: var(--fw-display);
  line-height: 1; font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  inline-size: 100%; min-inline-size: 0; box-sizing: border-box;
  min-block-size: var(--tap); padding: 0 var(--s1);
  color: var(--ink); background: transparent;
  border: var(--bw) solid transparent; border-radius: var(--r1);
  font-family: inherit; -moz-appearance: textfield; appearance: textfield; }
.nu-bpmbig::-webkit-outer-spin-button,
.nu-bpmbig::-webkit-inner-spin-button{ -webkit-appearance: none; margin: 0; }
.nu-bpmbig:focus-visible{ border-color: var(--hand); outline: none; }

/* THE EIGHT OPERATIONS, AS A PANEL ROW — the geometry the 2026-08-29 audit
   deleted when the row was a gutter level, re-measured for the column it is in
   now, and MEASURED on the rendered page at six widths rather than argued.
   The row is the panel's width less wave 1b's `--s4` inset — 218px inside a
   243px panel at a 320px window, 1118 inside 1144 at 1280 — and the marks are
   the `icon()` plate at `--ctl` square with `.nu-row`'s `--s2` gaps. Eight of
   them take THREE lines at 320, TWO from 390 to 560, and ONE from 640 up.
   Wrapping is the RIGHT answer here where it was the wrong one in a 366px
   motif editor: nothing under this row is pushed off a screen by a second
   line, and squeezing eight marks onto one phone line would put every one of
   them under the 44px tap floor. So the row keeps `.nu-row`'s own wrap and
   gap and spends its rule on the marks and on the air around itself.
   (The tap is NOT in this row — it is `.nu-taprow`, its own line above the
   eight, because it is a gesture and they are operations. It shares the plate
   rule below because it is the same object.) */
.nu-tf-row{ margin-block: var(--s3); }
/* ---- THE MOTIF OP CHAIN (2026-09-05) --------------------------------------
   Paul: *"I want to be able to do multiple operations in a motif at once."*
   The chain is a strip of CHIPS (DESIGN.md component 7): each is one run of
   the chain with its word and three 44px marks — earlier, later, out. It
   WRAPS rather than scrolls, because nothing on this page scrolls sideways,
   and it is a hairline-separated block under the fourteen marks so the row
   that WRITES and the row that RECORDS read as two things. */
.nu-opchain{ display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); margin-block: var(--s3);
  padding-block-start: var(--s3);
  border-block-start: var(--bw) solid var(--rule); }
.nu-opchip{ display: inline-flex; align-items: center; gap: var(--s1);
  min-block-size: var(--tap); padding-inline: var(--s2);
  border: var(--bw) solid var(--rule); border-radius: var(--r2);
  background: var(--panel); }
/* WRITTEN, NOT INHERITED: a chip in the chain is a thing a hand did, so it
   takes the weight this page gives to a written value everywhere else. */
.nu-opchipword{ font-weight: var(--fw-label); white-space: nowrap; }
.nu-opchip button{ min-inline-size: var(--tap); min-block-size: var(--tap);
  border: 0; background: transparent; color: var(--dim); }
.nu-opchip button:disabled{ opacity: .4; }
.nu-opchain > button{ min-block-size: var(--tap); }
.nu-tf-row button, .nu-taprow button{
  min-inline-size: var(--ctl); min-block-size: var(--ctl); }

/* TAP TEMPO — the ninth mark, and the count beside it. Same plate as the eight
   (above), because it is the same kind of thing: a mark that moves the tempo.
   The `<output>` is the run — "3 taps" — and it is quiet ink: it reports, it
   does not ask. `min-inline-size` on it so the row does not reflow every time
   the count goes from one digit to two under a tapping thumb. */
.nu-taprow{ margin-block: var(--s3); }
.nu-taprow output{ font-size: var(--t2); color: var(--rule);
  min-inline-size: 6ch; font-variant-numeric: tabular-nums; }

/* `+ bar` / `− bar`, under the changes table. A `.nu-row` of two marks; the
   only thing it owes beyond that is the same tap floor every other mark on
   this page has, and air between the table and the sentence under it. */
.nu-progops{ margin-block: var(--s3); }
.nu-progops button{ min-inline-size: var(--ctl); min-block-size: var(--ctl); }

/* THE CAPTION UNDER A FIELD — one measured fact about the value you are
   standing on ("period 12.08"), where the value alone would not tell you. It
   is NOT a `.nu-why`: a refusal says why a control cannot be used, and this
   says what the chosen word MEANS. Same quiet ink, same block placement under
   the label, so the two read as one family and never as one thing. It is drawn
   only where there is something to say (ui/eight.js `tuningSay` answers null
   on nine of the twelve modes), so on almost every record this rule has no
   subject at all — which is why it costs the text diet nothing. */
.nu-cap{ display: block; margin: calc(var(--s1) * -1) 0 var(--s2);
  font-size: var(--t1); color: var(--rule); }

/* ===== THE RULES PANEL (2026-09-02, the composer round, slice 2b) =========
   Paul, B6: *"The genre data is expressed as logical sentences and rules
   derived from the data in the genre. They should be readable to a musician.
   You can edit them, add new rules from a palette, and set thresholds. It's a
   code-editing experience but it feels like simple sentences."*

   SIX RULES, AND NOT ONE OF THEM IS A NEW WIDGET. Every control in the panel
   is already drawn by something on this page — a `.nu-field` even row, a
   `.nu-combo`, a `.nu-opt` chip, a `.nu-why` refusal, the `.nu-namebar` plate
   — because a genre editor built out of its own vocabulary would be a second
   design system inside the first. What is left for this block is the four
   things nothing else on the page has had to say: what an AXIS BLOCK looks
   like, what one RULE ROW looks like between its neighbours, where the reset
   mark sits, and how the plate carries a third line.

   THE TRAY BLOCK IS NOT TOUCHED, and nothing above this banner is edited. */

/* An axis is a stack of rules with the vocabulary's own word over it. The word
   is a control label and not prose (AXES.md is the one owner of the eight), so
   it is an `<h3>` — which is also what keeps it out of the text diet's count,
   the same way every other heading on this page is out of it. Small, tracked
   and quiet: it is a divider you read once, not a title you read every time. */
.nu-rulax{ margin-block-end: var(--s4); }
.nu-axword{ margin: var(--s4) 0 var(--s2);
  font-family: var(--sans); font-size: var(--t1); font-weight: var(--fw-block);
  letter-spacing: .12em; text-transform: uppercase; color: var(--rule);
  border-block-end: var(--bw) solid var(--rule); padding-block-end: var(--s1); }

/* ONE RULE, WITH A HAIRLINE UNDER IT. A rule is a sentence, a control, a tier
   word and sometimes a reset, and at 390px those wrap into a block that has to
   be told where it ends — thirty-eight of them in a column with no rule
   between them is the "uneven" Paul named, one level up from the fields. The
   `.nu-why` under it is the tier ("the record is written again at this seed"),
   which is the same quiet ink every other refusal on the page wears. */
.nu-rule{ padding-block-end: var(--s2);
  border-block-end: var(--bw) solid var(--well); }
.nu-rule:last-child{ border-block-end: 0; }
.nu-rule > .nu-why{ display: block; margin-block-start: calc(var(--s1) * -1); }
/* the sentence over a group of per-key controls (a `paces` map, a `part`
   list). An `<h4>` for the reason the axis word is an `<h3>`: it NAMES the
   controls under it, and a name is a label. */
.nu-rulehead{ margin: var(--s2) 0 0; font-size: var(--t2);
  font-weight: var(--fw-label); }
/* the way back to what the genre says. It sits after the row's own words, at
   the page's tap floor like every other mark, and it is only ever drawn on a
   rule a hand has actually moved. */
.nu-rule > button[data-k^="rule-reset"]{ min-block-size: var(--tap);
  margin-block-start: var(--s1); font-size: var(--t1); }
/* A REFUSED CONTROL'S REASON, ON THE THIRD LINE OF ITS OWN FIELD. `unwrap`
   moves the `<small class="nu-why">` ui/selects.js built into the row's one
   `<label>`, where it would otherwise sit beside the menu on line two and
   squeeze it; a full basis puts it under, which is where every other refusal
   on this page sits. (`.nu-opt .nu-why` says the same thing for a chip.) */
.nu-rule .nu-field > label > .nu-why{ flex: 1 0 100%; }
/* the fx chips, wrapped: `.nu-opt` already draws the chip and the checked
   state, so this is the row and nothing else */
.nu-chips{ display: flex; flex-wrap: wrap; gap: var(--s2);
  margin-block: var(--s2); }
/* THE THIRD LINE ON THE NAME PLATE — the lineage, in one line. `.nu-namebar`
   is a baseline flex row with `inline-size: fit-content` and NO wrap, so a
   third item is a third thing on one line; `.nu-ruleplate` is the one plate
   that wants two lines, and it says so on itself rather than by editing the
   shared rule. MEASURED, and this is why the class exists at all: without the
   wrap the `flex: 1 0 100%` line below demanded a full viewport IN ADDITION to
   the name, and `documentElement.scrollWidth - clientWidth` came out 134 at
   320px, 119 at 375/390/430 and 0 at 1280 — a page that scrolls sideways,
   which shell A6i and test/gutter.js T8 both forbid. Measured after: 0 at all
   five. The ink is the plate's own paper at the same 72% `.nu-namebar-sub`
   uses, for the same reason (it is never the only copy of what it says — the ?
   panel draws the whole flow). */
.nu-ruleplate{ flex-wrap: wrap; max-inline-size: 100%;
  overflow-wrap: anywhere; }
.nu-rulekin{ flex: 1 0 100%; margin-block-start: var(--s1);
  font-size: var(--t1); font-weight: 500; letter-spacing: 0;
  text-transform: none; opacity: .72; }

/* ========================================================================
   BUILD THE BAND — THE ROSTER, THE TRAYS AND THE READERS (2026-09-02, 2c)
   ========================================================================
   Paul, B10: *"List all the band members as separate boxes. I need an obvious
   way to assign multiple motifs to band members. Maybe a tray of motifs that
   pops up"*; B8: *"the motif editor should show me previews of the instruments
   using the motif"*; B14: *"lots of previews, small widgets"*.

   FIVE SURFACES, ONE IDIOM: a box with a CATEGORY edge, a glyph, a name, a dim
   second line, and — wherever the thing has a shape — its `.nu-preview`. The
   idiom is the gutter's own member row said at panel width, which is the
   point: a hand that has learned one has learned all five.

   WHAT THIS BLOCK DOES NOT RESTATE. The colour is `.nu-vpaint` + `[data-vi]`
   (the CATEGORY family at the top of this file), the thumbnail is
   `.nu-preview`, the even row is `.nu-field` / `.nu-sel`, the refusal is
   `.nu-why` and the tap floor is `button { min-block-size: var(--tap) }`. Every
   rule below is layout and nothing else; where one of them sets a colour it is
   `var(--vpaint)` or a token, never a literal.

   THE CATEGORY SLOTS ARE ASSIGNED BY KIND, not by raw voice index — the
   `[data-vi]` table's own note says "the voice's own index in DOC.voices", and
   `ui/eight.js vpaintOf` is what actually writes the attribute: the kit takes
   slot 5 (`--drum`) and the bass slot 4 (`--vb`) on every record, so one
   instrument is one hue across the roster, the score's caps and the roll. The
   melodic lanes take 0..3 and wrap, which is the table's own arithmetic. */

/* ---- (THE ROSTER AND THE MOTIF TRAY STOOD HERE) ----------------------
   Twenty-two declarations across `.nu-roster` / `.nu-member` / `.nu-memopen`
   / `.nu-memglyph` / `.nu-memname` / `.nu-meminstr` / `.nu-memreads` /
   `.nu-memchip` / `.nu-memfoot` / `.nu-memadd` and `.nu-tray-chips` /
   `.nu-traychip`. The band as BOXES and the motif tray as CHIPS are both
   deleted with the Band pane (2026-09-04, nukernel/TABLE.md §6 ¶A): the band
   is a table's header row and a player's motifs are the chips of a CELL's
   own sheet, drawn by ui/table.js through ui/wordgrid.js's strip — which
   already carries the preview, the pressed face and the ink edge these
   rules were spelling a second time. `.nu-vpaint`, `.nu-preview` and
   `.nu-readby` below are untouched; they were never the roster's. */

/* ---- the readers of a motif, as players ------------------------------ */
/* THE ONE PLACE ON THIS PAGE WHERE A CATEGORY HUE IS A FILL rather than an
   edge: these chips are a list of PLAYERS and nothing else is on the line, so
   the hue is the whole of what tells them apart at a glance. `.nu-vpaint`
   already pays for that with an ink border and white text (its own note has
   the contrast measurement); this only lays the three lines out. */
.nu-readby{ display: flex; flex-wrap: wrap; gap: var(--s2);
  margin-block: var(--s3); }
.nu-readchip{ display: inline-grid; grid-template-columns: auto 1fr;
  column-gap: var(--s2); align-items: center; text-align: start;
  padding: var(--s2) var(--s3); max-inline-size: 100%; min-inline-size: 0;
  overflow-wrap: anywhere; }
.nu-readglyph{ grid-row: 1 / span 3; font-size: var(--t3); }
.nu-readname{ font-weight: var(--fw-label); }
.nu-readinstr, .nu-readsecs{ grid-column: 2; font-size: var(--t1);
  font-style: normal; opacity: .85; }

/* ---- a motif's name, as a field -------------------------------------- */
/* IT IS `.nu-motif`'s ONE CHILD NOW, so the block's own name line is a
   control. The width is `ch`-based rather than a percentage because a cell
   name is a WORD (`psalm`, `neume`) and a field the width of the panel reads
   as a place to write a sentence. 16px is the iOS floor for any text input and
   is the one number in this rule that is not a token. */
.nu-motifname{ inline-size: 12ch; max-inline-size: 100%; font-size: 16px;
  font-weight: var(--fw-label); }
.nu-motif > span{ display: inline-flex; align-items: baseline; gap: var(--s2);
  flex-wrap: wrap; }

/* ======================================================================
   WAVE 2d · STRUCTURE — THE SECTION AUTOMATION GRIDS
   Paul, B9: "Make a section automation interface for the manipulation of the
   motifs and put it under structure/sections … Every section I can tweak every
   instrument. … for each question you add per section, you could have a WHOLE
   section automation grid."

   THE ANCESTOR IS `.nu-trims` (section automation on the board, five hundred
   lines up) AND THIS IS NOT A COPY OF IT. What is shared is the geometry — a
   table, sections running down, a scroll pane round it, one 44px target per
   cell — and it is shared by writing the parts that differ and inheriting the
   rest: the row heads and the cells here are ordinary buttons and `<select>`s,
   which this file already dresses, so what follows is the head row, the two
   marks and the dim-is-derived reading, and nothing else.

   THE TWO MARKS ARE THE TWO FACTS A GRID HAS TO SAY AT ONCE, and they are
   already distinguished everywhere else on this page: `.nu-here` is a HAND-BLUE
   rule down the row you are WRITING (the rule above it explains why a rule and
   not a tint), and the playhead is CLOCK-RED and is the browser's own `<mark>`
   inside the row head. They land in the same cell on purpose and they are never
   confusable, which is the distinction 2026-08-24 spent a day undoing. */
.nu-sgrid{ border-collapse: separate; border-spacing: 3px; inline-size: 100%; }
/* THE CAPTION IS THE TABLE'S NAME. `<caption>` is the accessible name of a
   grid, so this is a control label — which is what lets it be one word: the
   heads say the rest. Same well as the board's `.nu-rowlab`, which is the class
   it wears and the one test/text-diet.test.js already excludes. */
.nu-sgrid > caption{ text-align: start; padding-block-end: var(--s2); }
.nu-sgrid thead th{ font: var(--fw-label) var(--t1)/1.3 var(--mono, monospace);
  letter-spacing: .06em; text-transform: uppercase; color: var(--dim);
  text-align: center; vertical-align: end; padding: 0; }
.nu-sgrid tbody th{ text-align: start; padding: 0; }
.nu-sgrid td{ padding: 0; }
/* A ROW HEAD IS A BUTTON AND IT IS THE JUMP. Two lines inside one target: the
   number (which the clock marks) and the section's own name. `start` because a
   column of names read from a centre line is a column you cannot scan. */
.nu-sgrid tbody th > button{ inline-size: 100%; min-block-size: var(--ctl);
  display: flex; align-items: baseline; gap: var(--s2); text-align: start;
  font: var(--fw-label) var(--t2)/1.3 inherit; padding-inline: var(--s2); }
.nu-srowname{ font-weight: var(--fw-body); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* THE SOUNDING ROW, PAINTED OFF THE MARK THE CLOCK ALREADY WROTE. `:has()` and
   not a second class: the clock may not write outside `[data-live]` inside
   #app, so the row's tint has to be a CONSEQUENCE of the one write it is
   allowed to make (the `<mark>` in the row head's live span) rather than a
   write of its own. Nothing here appears or disappears; only the paint moves. */
/* ---- AND THE PLAYHEAD IS A RULE, NOT A BLOCK (2026-09-08, §24) --------
   Paul: *"The playing bar is blocky in the sections."*

   IT WAS A FLOODED HEAD — `background: var(--clock-tint)` with a corner on it
   — so a sounding section arrived as a filled slab moving down a column of
   words, which is the "blocky" in that sentence. And this page already has the
   answer, written down and used one class over: *"`.nu-here` is a rule down
   the row and not a tint"* (nu.css:203). A playhead is the same kind of fact
   as "you are here" and gets the same kind of mark, in its own colour.
   THREE PIXELS OF `--clock` DOWN THE HEAD'S START EDGE, and the number beside
   it already turns red on its own (the `[data-live]` rule above). Two channels,
   both cheap, neither of them a slab: the eye finds the rule and the number
   confirms it. The tint is kept for the ONE case it reads correctly — the
   `<mark>` itself, which is a few characters and not a row. */
.nu-sgrid tbody tr:has(mark) > th{
  box-shadow: inset 3px 0 0 var(--clock); }
.nu-sgrid tbody tr:has(mark) > td > select{ border-color: var(--clock); }
/* THE COLUMN HEAD IS THE PLAYER, AND THE PLAYER IS A COLOUR. `.nu-vpaint` is
   the utility's filled form (white ink on the player's hue, the page's own ink
   border); everything below is the two text lines inside it. */
/* ===== TOMBSTONE: `.nu-scol` / `.nu-scolname` / `.nu-scolinstr` ARE DEAD INK
   SINCE 2026-09-02 (wave 4) ============================================
   The Structure grids' column head was this class and the board's was
   `.nu-colbtn`; ui/wordgrid.js made the two heads ONE, so both grids wear the
   board's three classes now and nothing on the page wears these. They are kept
   rather than deleted because the paragraphs around them are the measurements
   that decided the head — the 12ch box that clipped its own first letter, the
   `--t1`-not-`--t0` argument, the 9ch column — and the rules that replaced
   them inherit every one of those decisions.
   `.nu-scollamp` BELOW IS NOT DEAD: the Structure heads still hang the
   playhead's red lamp in it, as a sibling of the button rather than a child
   (test/motif-frozen A1: a `[data-live]` box inside a control is the shape
   that is forbidden, and the board's meter well has always been a sibling for
   the same reason). */
.nu-scol{ inline-size: 100%; min-block-size: var(--ctl); min-inline-size: 7ch;
  display: grid; row-gap: 1px; justify-items: center; padding: var(--s2);
  font: var(--fw-label) var(--t2)/1.2 inherit; text-transform: none;
  letter-spacing: 0; }
.nu-scolname{ font-weight: var(--fw-block); }
/* `--t1` AND NOT `--t0`: the smallest step is RESERVED for the gutter's label
   at the narrowest width and has one caller by design (see the scale). A
   second line inside a plate is a second line, not a nav label. */
/* THE LINE TAKES THE COLUMN AND ELLIPSES INSIDE IT. `max-inline-size: 12ch`
   stood here and was wrong in the one way a centred box can be: the head is
   `justify-items: center`, so a 12ch box inside a 9ch column overhangs BOTH
   sides and `overflow: hidden` clips the first letter as well as the last
   ("clean guitar" read "ean guita"). Measured at 430px on Kingston. */
.nu-scolinstr{ font-weight: var(--fw-body); font-size: var(--t1); opacity: .9;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  inline-size: 100%; text-align: center; }
/* THE LAMP: a bar that comes and goes inside a [data-live] span while that
   player has an event in the sounding bar. It holds the row whether it is lit
   or not — a head that grew a line on play would be the editing interface
   changing while the record runs (test/motif-frozen A2) — so the WELL is
   always there and only its child moves. Clock red, because this is the
   playhead and not a measurement: `--meter` stays reserved for a number that
   came back from the engine. */
.nu-scollamp{ display: block; block-size: 4px; inline-size: 60%; }
.nu-scollamp > i{ display: block; block-size: 4px; inline-size: 100%;
  background: var(--clock); border-radius: var(--r2); }
/* DIM IS DERIVED, BRIGHT IS SET (2026-08-28), on a menu. An untouched cell is
   at the table's own absent detent — "—", "as written" — and it must read as a
   READING rather than as a setting, the same way `.nu-trimbtn.is-derived` does
   four hundred lines up: no weight of its own, and the ground it sits on
   unchanged. The moment a word is said the class comes off. */
/* A CELL WIDE ENOUGH TO READ ITS OWN WORD. `td > select` (the chord table's
   rule, five hundred lines up) is `inline-size: 100%; min-inline-size: 0`, and
   a table at `inline-size: 100%` then squeezes six columns into the pane until
   every menu is an arrow with nothing beside it — measured at 430px, where
   `intro`/`outro`/`mot` showed no word at all. A floor turns the squeeze into
   an OVERFLOW, which is what the pane is for and what `.nu-trims` gets for
   free from its buttons' own `min-inline-size`. The 9em cap above still wins
   at the top end, so a wide window changes nothing. */
.nu-sgrid td > select{ min-inline-size: 7em; }
.nu-sgrid td > select.is-derived{ font-weight: var(--fw-body); opacity: .78; }
/* AND A CELL THAT IS NOT A CONTROL — one possible answer, or a key another
   open tab owns this second. Text, in the cell, so the grid keeps its shape
   and holds no address. Centred with the column head above it. */
.nu-sgsay{ text-align: center; color: var(--dim); font-size: var(--t1);
  padding: var(--s2); }

/* ======================================================================
   SLICE 2e · MIX — THE COLUMN HEADS, THE ROW JUMPS, AND AN HONEST METER
   (2026-09-02)

   Paul, B11: *"Instead of having four icons on top and section automation
   that should have been five subicons under the 'Mix' icon. One of them is
   section automation. … the columns should list the instrument and when I
   click on the column head let me edit the instrument! Light up which
   instrument is playing, make a little volume meter INSIDE the heading. … I
   need to be able to jump to a section somehow, by clicking on them when in
   automation."*

   FOUR THINGS LAND HERE, and the two paint families do exactly what the
   design system says they mean and nothing else:
     · CATEGORY (`[data-vi]` + `.nu-vpaint`) on a column head — which PLAYER.
     · `--meter` green in the well — MEASURED, and only where a number
       arrived; an unmeasured well draws no bar at all.
     · `--clock` red on a sounding head (`is-sounding`) — a PLAYHEAD, the same
       ink and the same class the gutter's band rows already wear. It is a
       schedule, not a measurement, which is why it is red and not green.
   `#boardtabs` AND ITS `.nu-busgroup` / `.nu-seamlab` / `.nu-tabarrow` RULES
   ARE NOW DEAD (the row is deleted, ui/engineer.js) and are left standing
   rather than pulled out from under the argued blocks they sit inside; they
   are named here so the next reader of this file knows they paint nothing.
   ---------------------------------------------------------------------- */

/* THE COLUMN HEAD. `.nu-trims thead th` above is a dim uppercase word, which
   is what a head was when it held one; this head holds a button and a meter
   and has to stop being type-styling. The instrument LEADS (it is the answer
   to "what is this column") and the player's name is the dim second line —
   the same two-line face `paintIcon` gives a nav row and `scolHead` gives a
   Structure grid, so the three column heads on this page read alike. */
.nu-trims thead th.nu-colhead{ text-transform: none; letter-spacing: 0;
  padding: 2px; vertical-align: bottom; }
.nu-colbtn{ display: flex; flex-direction: column; align-items: stretch;
  gap: 1px; inline-size: 100%; min-inline-size: 56px; min-block-size: var(--ctl);
  padding: var(--s1) var(--s2); cursor: pointer; text-align: start; }
.nu-colinstr{ display: block; font: var(--fw-block) var(--t1)/1.15 var(--sans, inherit);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-colname{ display: block; font: var(--fw-body) var(--t0)/1.2 var(--mono, monospace);
  opacity: .8; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the head keeps its own hue under the pointer — the same argument
   `.nu-trimbtn.w-*:hover` makes four hundred lines up: a plate that already
   says which player it is must not answer the pointer in the flag's yellow. */
.nu-colbtn:hover{ background: var(--vpaint, var(--ink)); }

/* THE LITTLE METER, INSIDE THE HEADING. The tall well above is 16×180 in a
   fader trough; this is the same well laid on its side under the words, and
   the bar grows from the leading edge instead of from the floor. */
.nu-meterwell.nu-meterh{ inline-size: auto; block-size: 6px;
  margin-block-start: 2px; }
.nu-meterwell.nu-meterh .nu-meterbar{ inset: 0 auto 0 0; inline-size: 0;
  block-size: auto; }
/* NOT YET MEASURED IS NOT ZERO. A chair with no tap and no audit draws an
   EMPTY well — no fill, and the well's own ground goes to the page's dim so
   it cannot be read as "measured, and silent". The title says which
   (ui/engineer.js writes it), and green never appears without a number. */
.nu-meterwell.is-unmeasured{ background: var(--panel); border-style: dashed; }

/* LIT UP WHILE IT IS PLAYING. `--clock`, and an edge bar rather than a fill,
   for the reason `.nu-vpaint.is-edge` gives: the head already wears the
   player's own colour and a second fill would fight it. */
.nu-trims thead th.nu-colhead.is-sounding{
  box-shadow: inset 0 -3px 0 var(--clock); }

/* THE ROW HEAD IS A JUMP. It was the section's raw id in dim uppercase type;
   it is a named button now (`verse 2`) with the bars-and-words line under it,
   and the `<small>` rule above still draws that line. Left-aligned and full
   width so a column of section names reads as a column. */
.nu-trims tbody th.nu-srowh{ padding-inline: 0; }
/* ...AND `.nu-corner` TAKES THIS BOX WITHOUT TAKING THE CLASS (2026-09-06,
   §15a). The grid's corner is a fold now, so its `aria-expanded` is TRUE at
   rest, and every "shut whatever is open" gesture that reaches for
   `[aria-expanded="true"].nu-rowjump` would have folded the grid away. One
   box, two classes — `.nu-labelbtn`'s own argument, a third time. */
.nu-rowjump, .nu-corner{ display: block; inline-size: 100%; min-block-size: var(--ctl);
  padding: var(--s1) var(--s2); text-align: start; cursor: pointer;
  font: var(--fw-label) var(--t1)/1.2 var(--sans, inherit);
  text-transform: none; letter-spacing: 0;
  border: var(--bw) solid transparent; border-radius: var(--r2); }
.nu-rowjump:hover, .nu-corner:hover{ border-color: var(--ink); }

/* ======================================================================
   SLICE 2f — THE PRODUCER PAGE (COMPOSER.md §2.9, 2026-09-02)
   Paul, B12: *"The only verb is 'make' from now on. Make X Y. The
   implementation is good but the design is confusing and feels unconsidered.
   Design a good producer interface."*

   Four surfaces, and three of them are shapes this stylesheet already owns
   (the ink name plate, a wrapped strip of buttons, a two-column field row).
   What is new here is a chip that carries a player's colour, and a LEVEL bar
   small enough to sit in a table cell.
   ====================================================================== */

/* ---- THE PLATE. `.nu-namebar` is the page's name plate and the producer is
   its fourth wearer. Two things it needs that a bus name does not: it wraps
   (three parts, and the third is a whole sentence), and the sentence under
   construction is not a dim caption — it is the loudest thing on the plate
   after the name, because it is what the next tap will finish. */
.nu-prodplate{ flex-wrap: wrap; }
/* `.nu-namebar small` (the plate's dim second voice, .75) and `.nu-prodsay`
   are the same specificity and the dim one is written later in the file, so
   the sentence has to be said INSIDE the plate to win it back. It is not a
   caption: it is the thing the next tap finishes. */
.nu-namebar .nu-prodsay{ font-weight: var(--fw-label); opacity: 1; }

/* ---- THE CAST: A LEVEL OF SIBLINGS, ONE PRESSED ----------------------
   A wrapped `.nu-row` of buttons, which is this file's one name for a strip
   of them. Nothing here restates the button face, the pressed plate or the
   44px floor — all three are the base rules at the top of the file, and that
   is the point of a chip row: it is buttons.

   THE PLAYER'S COLOUR IS A SWATCH INSIDE THE CHIP. `[data-vi]` on the chip
   declares `--vpaint`; `.nu-vpaint` on a 10px square inside it does the
   painting, in the utility's own filled form. The chip itself may not wear the
   fill (`aria-pressed` owns it) and may not wear `.is-edge` (it zeroes the
   border and the ground, which is the button's face) — so the colour goes on
   the one element in the chip that is nothing but colour. See `.nu-vpaint`'s
   own contract for why this is the utility used rather than a fifth rule. */
.nu-cast{ align-items: stretch; margin-block: var(--s3); }
.nu-castchip{ display: inline-grid; grid-template-columns: auto 1fr;
  column-gap: var(--s2); align-items: center; text-align: start;
  padding: var(--s2) var(--s3); max-inline-size: 100%; min-inline-size: 0;
  overflow-wrap: anywhere; }
.nu-castdot{ grid-row: 1 / span 2; inline-size: 10px; block-size: 10px;
  border-radius: var(--r2); }
.nu-castw{ grid-column: 2; font-weight: var(--fw-label); }
.nu-castline{ grid-column: 2; font-size: var(--t1); font-weight: var(--fw-body);
  font-style: normal; opacity: .85; }
/* THE TREE SURVIVES AS AN INDENT AND NOT AS A LEVEL. producer.js's subject
   table is two deep — kick / snare / hats / toms / cymbals / percussion under
   the drums, the bass line and the bass sound under the bass — and a wrapped
   row is flat by construction. The child chips are set in one step of the
   spacing scale and read as a group under the parent word that precedes them.
   (The nav's own tree pays for depth in `--s3` per level for the same reason;
   this is one level, so it is one step.) */
.nu-castchip[data-under]{ margin-inline-start: var(--s3); }

/* ---- THE TARGETS: TWO SHEETS SIDE BY SIDE ----------------------------
   (COMPOSER.md §2.9: "two sheets side by side on wide screens, stacked on
   phones".) 800px is the width at which two `.nu-field` rows still hold their
   own word without the value ellipsing — measured against the longest label
   pair the page can draw, `qualities` over `chugging` and `records` over an
   anchor's full "punk · New York 1976". Below it they stack, which is the
   grid's own default and needs no rule. */
.nu-ptargets{ display: grid; gap: var(--s3); align-items: start; }
@media (min-width: 800px){
  .nu-ptargets{ grid-template-columns: 1fr 1fr; column-gap: var(--s4); }
}
.nu-ptargets > .nu-sel{ margin-block: 0; }

/* ---- HOW FAR, AS A LENGTH ---------------------------------------------
   The LEVEL ramp (`--q1..--q4`, "how much") in a table cell. It is the same
   four steps the step grid's velocities wear and it is drawn the same way:
   an ink-bordered well, a fill inside it, the step chosen by the value. The
   well keeps its border at every step so the SHAPE is legible even where the
   quiet end of the ramp is a low-contrast fill — the argument `--q1`'s own
   note makes, applied to the third surface that uses it. */
.nu-pcbar{ display: block; inline-size: 100%; min-inline-size: 42px;
  /* A WIDGET, NOT A STRETCH. `inline-size: 100%` alone made the bar as wide as
     the column, which at 1280 is a 480px trough holding a 40% fill — the eye
     reads the TROUGH's length as the quantity before it finds the fill. Capped,
     the five bars in a stack of five are the same length and only their fills
     differ, which is the comparison the ramp is for. */
  max-inline-size: 120px;
  block-size: 8px; margin-block-end: 2px; background: var(--well);
  border: var(--bw) solid var(--rule); border-radius: var(--r2);
  overflow: hidden; }
.nu-pcfill{ display: block; block-size: 100%; background: var(--q4);
  border-inline-end: var(--bw) solid var(--rule-strong); }
.nu-pcbar[data-q="1"] > .nu-pcfill{ background: var(--q1); }
.nu-pcbar[data-q="2"] > .nu-pcfill{ background: var(--q2); }
.nu-pcbar[data-q="3"] > .nu-pcfill{ background: var(--q3); }
.nu-pcbar[data-q="4"] > .nu-pcfill{ background: var(--q4); }
/* A REFUSED LINE, STRUCK WHOLE. The `<s>` on the percentage is the semantic
   element and stays; this is the other three cells, so the strike reads as a
   fact about the LINE. The row's own buttons are not struck — "more" and
   "take it off" still work on a note that moved nothing, and a struck control
   would read as a dead one. */
.nu-notes tr.is-refused > th,
.nu-notes tr.is-refused > td{ opacity: .7; }
.nu-notes tr.is-refused > td{ text-decoration: line-through; }
.nu-notes tr.is-refused > td .nu-row{ text-decoration: none; }
.nu-notes tr.is-refused .nu-pcfill{ background: var(--faint); }

/* ==========================================================================
   WAVE 3 — THE FIX ROUND OF 2026-09-02
   ==========================================================================
   Every rule below answers a numbered defect in the probe report of that
   morning (`scratch/maps-2026-09-01/probe-report.md`), which drove the
   deployed page as a user at 390x844 and 1280x900. The probe's own sentence is
   quoted on each block, so the next reader gets the measurement rather than
   the conclusion. They are APPENDED rather than woven in for the reason the
   wave-1a block above gives: a second agent is editing this tree in the same
   hour, and a rule that lands at the end is a rule a merge can see.        */

/* ---- 7 · A SLIDER IS A WIDGET, NOT A WALL ------------------------------
   Probe: *"Full-bleed range sliders 1077-1103px wide with the readout orphaned
   at the far right: rule|bpm/bars/maxHold/voices/tone.verb/stress/phrase,
   Tempo's tempo, Motif degree sliders (840). Cap a .nu-field range at ~28ch
   with the output beside the thumb end."*

   THE RULE IT BREAKS IS `flex: 1 1 0`, WHICH WAS RIGHT AND INCOMPLETE. That
   declaration exists (see its own block above) to stop `input[type=range]{
   inline-size: 100% }` pushing the `<output>` onto a third line, and it does —
   but "the line minus the readout" is a whole 1280px desktop line, and a
   1100px trough for a number between 40 and 220 is a control whose precision
   is a hundred times finer than the fact it sets. Worse, it puts the value a
   full screen away from the thumb that is moving it: the eye cannot hold both.
   28ch IS THE SAME REASONING `.nu-combo`'s 30ch CAP ALREADY MAKES one block
   up ("a menu stretched across a 1400px desktop is a 1345px control holding
   one word"), applied to the other widget that stretches. It is a MAXIMUM, not
   a width: below it the slider still takes the line it is given, so nothing
   about a phone changes — measured, the tempo field is 88.0px tall at 320 and
   at 1280, exactly as its own block promises, and the readout is now beside
   the thumb's end at both.
   THE THREE ROW CLASSES ARE NAMED RATHER THAN A DESCENDANT SELECTOR, because
   `.nu-field`, `.nu-rule` (the Rules panel's sentence rows) and `.nu-tf`
   (Tempo's) are the three surfaces that put a slider on a line with words, and
   a bare `input[type=range]` cap would also hit the board's faders, the
   maker grids and the vertical chassis, none of which is on a text line. */
.nu-field > label > input[type=range],
.nu-sel > label > input[type=range],
.nu-rule input[type=range],
.nu-tf input[type=range]{ max-inline-size: 28ch; }
/* ...AND THE READOUT STAYS WITH IT. `flex: 0 0 auto` so the `<output>` is
   never stretched into the gap the cap just opened, and the gap goes AFTER the
   pair rather than between them: `margin-inline-end: auto` on the output
   pushes the empty line-end away, which is what puts the number beside the
   thumb instead of at the far margin. */
/* ...AND IT MAY STILL SHRINK. `flex: 0 0 auto` stood here for an hour and was
   the wrong half of "never stretched": a readout whose `fmt` returns a
   SENTENCE rather than a number ("every fourth bar, a bar early") then had a
   313px flex basis it could not give back, and at 390 one `<output>` in the
   Structure panel ran from 106 to 419 — 29px past the glass, with no scroller
   above it to clip it (measured; test/nav-tree N4 caught the document scroll
   at 320, 375 and 390). `0 1 auto` with `min-inline-size: 0` says the same
   thing the cap above says about the slider: take what you need, give back
   what you must. A long readout wraps inside its own box instead of pushing
   the page sideways. */
.nu-field > label > output,
.nu-sel > label > output{ flex: 0 1 auto; min-inline-size: 0;
  margin-inline-end: auto; overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums; }

/* ---- 2b · (THE FORM LIST'S OWN TWO RULES STOOD HERE) -----------------
   `.nu-barsin` (the bars spinner in a `<td>`) and the four `.nu-form td`
   declarations that stripped a `<select>`'s printed label inside a table
   cell. Both belonged to `formTable`, deleted 2026-09-04 with the Structure
   pane. A section's length and its role are rows of the table's ROW SHEET
   now — words on a strip, not a spinner and a combo in a cell — so neither
   selector matches anything the page draws. */

/* ---- 12 · THE TEMPO OPERATIONS WEAR THEIR WORDS FROM 640px -------------
   Probe: *"Nine naked glyphs in Tempo (⏱ ♪↓ ♪↑ ♪↓↓ ♪↑↑ ←→ →← 1× ↺) — words
   only in aria-label; on a 1100px panel there is room to show the .nu-vh word
   (from ~640px)."*

   IT IS `#nu-tray .nu-vh`'S OWN FOUR DECLARATIONS, NOT A FIFTH SPELLING. The
   gutter already unhides `paintIcon`'s label — one string, one owner, two
   readers — and this is the same unhide on the other surface that draws icon
   marks. Below 640px the words stay hidden and the row stays nine glyphs,
   because nine labelled marks do not fit a phone's line and the accessible
   name has carried the word the whole time.
   THE HINGE IS 640 AND NOT 700: the panel's own content box at 700px of glass
   is ~530px, and nine marks at their labelled width measure 486px — so 640 is
   where the row still fits on one line, which is the number the rule is
   actually about. */
.nu-tf-row button{ min-inline-size: var(--tap); }
@media (min-width: 640px){
  .nu-tf-row .nu-ic{ flex-direction: row; align-items: center;
                     gap: var(--s2); }
  .nu-tf-row .nu-vh{ position: static; inline-size: auto; block-size: auto;
    margin: 0; overflow: visible; clip-path: none;
    white-space: nowrap; font-size: var(--t1, .72rem); line-height: 1.15;
    font-weight: 600; letter-spacing: -.01em; }
}

/* ---- 13 · EVEN ROWS, THREE SURFACES -----------------------------------
   Paul, B4: *"things are uneven based on how text wraps."* Probe: *"Produce
   cast chips row 1 = 44px, row 2 = 49px, ragged staircase at 390; Band roster
   boxes with 7/5/4/2/2 chips put 'play alone' at y 450/350/250/200/200;
   automation column heads 145/195/105/113/119/88/88px wide so the meters are
   seven scales."*

   (a) THE CAST CHIPS BECOME A GRID. They were inline-grid chips in a wrapping
   flex row, so each chip was as wide as its own words and as tall as however
   many lines they took — which is exactly the failure `.nu-field` was written
   for, one surface over. `repeat(auto-fill, minmax(min(13em, 100%), 1fr))` is
   the roster's own track recipe (the surface where equal boxes were first
   solved on this page), and grid's default `stretch` makes every
   chip in a row the height of the tallest — so a two-line chip lifts its
   neighbours instead of stepping past them.
   THE INDENT SURVIVES: `[data-under]` is still a margin, and in a grid cell a
   margin still steps the chip in. What is lost is the tree reading as a
   continuous indented run when it wraps mid-family, which a flex row also lost
   the moment it wrapped. */
   AND THE HEIGHT IS STATED, WHICH IS THE HALF `grid-auto-rows: 1fr` COULD NOT
   BUY. That declaration stood here for ten minutes on the theory that `1fr`
   rows on an auto-height grid all resolve to the tallest; they do not — an
   `fr` needs a DEFINITE size to divide and an auto-height grid has none, so it
   resolves to `auto` and the measurement was unchanged (44 x10 then 49 x7 at
   1280, before and after). The honest fix is the one `.nu-field` already
   makes one surface over: STATE the row and let the content sit in it.
   `--tap` PLUS ONE STEP OF THE SPACING SCALE is what a chip is: a tap row for
   the word, and one line of the detail under it (`.nu-castline`, at `--t1`).
   Every chip is that height whether or not it has a second line to put in it,
   which is exactly what "even rows" means and is why the number is derived
   from the two tokens rather than measured off the tallest chip. Measured
   after: 50 for all seventeen, at 390 and at 1280. */
.nu-cast{ display: grid; align-items: stretch; margin-block: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(min(13em, 100%), 1fr)); }
.nu-castchip{ display: grid; align-content: center;
  min-block-size: calc(var(--tap) + var(--s2)); }
/* ((b) WAS THE ROSTER BOX'S FOOT — `.nu-member` as a column with 'play
   alone' pushed to the bottom. The roster is deleted (2026-09-04, the Band
   table); a player is a COLUMN HEAD now and its ops are rows of its sheet,
   which the accordion already lays out evenly.) */
/* (c) THE AUTOMATION GRID'S COLUMNS ARE EQUAL. `table-layout: fixed` makes
   every unsized column an equal share of the table, which is what turns seven
   meters into one scale; the section column is sized so the row heads keep
   their names. THE TABLE IS ALLOWED TO BE WIDER THAN THE PANE — `--cols` is
   written by ui/wordgrid.js (a count, not a width) and the pane
   around it already scrolls sideways (`pane()`), which is this page's standing
   answer for a wide table. Without the minimum, `fixed` + `100%` would crush
   nine players into 20px each rather than scrolling. */
.nu-sgrid{ table-layout: fixed;
  inline-size: max(100%, calc(13ch + var(--cols, 1) * 9ch)); }
.nu-sgrid thead th:first-child, .nu-sgrid tbody th{ inline-size: 13ch; }

/* ---- 26 · THE COLUMN HEAD'S INSTRUMENT LINE ---------------------------
   Probe: *"`.nu-scolinstr` (Structure column heads) clips its own text by 9px
   (overflow hidden, scrollWidth 111 / clientWidth 102)."* With (c) above the
   columns are equal and 9ch wide at the floor, so the line ellipsises rather
   than cutting mid-glyph — and ui/eight.js now puts the whole name in the
   button's `title`, so the full word is one hover or one long-press away and
   is already the control's accessible name. What this adds is the floor the
   ellipsis needs to be legible at all. */
.nu-scol{ min-inline-size: 0; }
.nu-scolinstr{ min-inline-size: 0; }

/* ---- 14 · THREE CHECKBOXES AND A LINK ---------------------------------
   Probe: *"Raw 26x26 checkboxes under the 44px floor: rubato (Tempo), ontime
   (Structure/performance), diatonic (Key). Also [goto.board] in Tempo is a
   261x15px link."*

   THE LINK IS FIXED AND THE BOXES ARE REFUSED, and the refusal is the older
   decision restated rather than a shrug. Measured on the rendered page at 390
   and 1280 after this round: all three are `check()`'s `.nu-field` — a
   `<label>` wrapping the input, on an 88px two-row field whose control line is
   a full 44px — so the GESTURE is already 44px and always was: the browser
   forwards a tap anywhere on the words to the box, which is exactly what
   test/shell.js's SURVEY measures ("the larger of the control's own box and
   its labelling `<label>`"). What the probe measured is the WIDGET, and this
   file has a dated decision about that number at the TAP TARGETS block: *"26px
   is the native widget, because a checkbox does NOT answer to `min-height` —
   it answers to `inline-size`/`block-size`, which resize the widget itself.
   `transform: scale()` was the other candidate and it is a trap."* `--box` is
   that number, it is one token with fourteen readers, and blowing three of
   them to 44px would put a checkbox the size of a button beside a sentence and
   leave the other eleven at 26 — one fact wearing two sizes, which is the
   drift this file's whole token layer exists to stop.
   WHAT IS ADDED IS THE HAND'S COLOUR AND THE CENTRE LINE: `accent-color` so a
   ticked box is the same cobalt every other set value on this page wears, and
   `align-self: center` so it sits on the middle of its 44px line rather than
   on the text baseline. The row is a switch row; the switch is `--box`. */
.nu-field > label > input[type=checkbox]{
  inline-size: var(--box); block-size: var(--box);
  flex: 0 0 auto; accent-color: var(--hand); align-self: center; }
.nu-field > label:has(> input[type=checkbox]){ align-items: center; }

/* ---- 22 · THE ROUTING POINTER IS A CONTROL, NOT A HYPERLINK ------------
   Probe: *"A raw markdown link 'the fixed wires — docs/BOARD-ROUTING.md' in
   the Mix panel: the app's only blue underlined hyperlink."* It stays an `<a>`
   with the same href and the same printed address, because `desk-gate.js` G14
   reads exactly that element and asserts the address is on the page — the
   pointer is the whole reason the essay was allowed to move to `docs/`. What
   changes is that it now wears the page's own control chassis and the 44px
   floor, so it reads as the one thing on this panel that opens something
   else rather than as the one thing that is underlined. */
.nu-routelink{ display: inline-flex; align-items: center;
  min-block-size: var(--tap); padding-inline: var(--s3);
  color: var(--ink); text-decoration: none;
  border: var(--bw) solid var(--rule); border-radius: var(--r1);
  background: var(--paper); font-weight: var(--fw-label); }
.nu-routelink:hover{ background: var(--flag-tint); }
.nu-routelink:focus-visible{ outline: var(--bw-hard) solid var(--hand);
  outline-offset: 2px; }

/* ---- 16 · THE PHONE SHOWS WHAT THEY PLAY ------------------------------
   Probe: *"On a phone the nav sub-line is never painted: --tray-w at 390px =
   93.6px, under the 96px hinge, so members show no instrument (B10). Move the
   hinge to <= 88px or make the row form kick in at >= 360px viewport."*

   FIXED IN PLACE, AT THE HINGE, and not here — see `THE MARK IS A COLUMN, AND
   FROM 400px IT IS A ROW` above, where the arithmetic that chose 400 now
   carries the arithmetic that chose 360 beside it. A THIRD `#nu-tray` block at
   the foot of this file stood here for an hour and is deleted with the
   measurement that killed it: it turned the row form on at 360 without the
   `white-space: nowrap; overflow: hidden; text-overflow: ellipsis` the 400
   block already carried for `.nu-sub2`, so the instrument name set the row's
   min-content and the stripe scrolled sideways 17px at 390 and 21px at 375.
   Two owners of one row form is one owner too many; the hinge is a NUMBER, and
   a number is changed where it is written. */

/* ---- 17 · THE TITLE COLUMN TAKES THE SLACK ----------------------------
   Probe: *"The genre list truncates names to unreadability at 390 ('Pale…',
   'Gregori…') while the place column has slack ('Ur', 'Rome'); the blank-state
   row reads '— silence the b…'. Rebalance .nu-ixli's tracks at the phone
   breakpoint."*

   THE TWO PROPORTIONAL TRACKS WERE THE BUG. `minmax(0, 1.6fr) minmax(0, 1fr)`
   gives the place 38% of the line WHATEVER IS IN IT, so "Ur" holds 68px open
   beside a name being cut at 116. `fit-content(10ch)` is the honest track for
   a column whose content is usually short and occasionally long: it sizes to
   the place and never past 10 characters, and every pixel it does not need
   goes to the `1fr` beside it, which is the title. Measured over the whole
   catalogue: the median place is 6 characters, the 90th is 9, and the two that
   exceed 10 ("San Francisco", "Rio de Janeiro") ellipsise inside their own
   cell exactly as they did before.
   ABOVE 700px NOTHING CHANGES — the title already has a fixed 26/34ch track
   there and the place takes the rest, which is that block's own decision and a
   good one. This is the phone rule only.

   AND THE REBALANCE WAS NOT ENOUGH — THE NAME TAKES ITS OWN LINE NOW
   (2026-09-04). Paul, reading the list on a phone: *"On mobile put the names
   of genres above the location because many of them cut off now."* He is
   describing a measurement, and the measurement agrees with him: on the
   rendered page, of 478 rows, **476 names were ellipsised at 320px, 407 at
   375px and 245 at 430px** — the name track was 7.7–48.2px wide at 320, which
   is four characters of a title. The three-cells-one-line shape had run out of
   line: 224px of row at 320 minus a 44px mark, a 4/8ch year, a 10ch place and
   the air between them leaves the ONE fact a reader is looking for less room
   than any of the facts around it.

   SO THE ROW IS TWO LINES BELOW 700px, WHICH IS A REVERSAL OF THE ONE-LINE LAW
   AT PHONE WIDTHS ONLY, and it is Paul's own reversal: "everything is on one
   line" (2026-08-29) was said about a row that printed the genre TWICE, and
   the cure was deleting the second copy, not squeezing the first. What was
   worth one line then was the row's three facts; what is worth two lines now
   is the same three facts with none of them cut. The stack:

       ┌──────────────────────────────────────────┬────┐
       │ Paleolithic flute music · the broader    │    │   ← the NAME, wrapped
       ├──────┬───────────────────────────────────┤ ↗  │
       │ 40000 BC │ Africa                        │    │   ← the year, the place
       └──────┴───────────────────────────────────┴────┘

     · the name spans the year and place tracks (columns 1–2) and WRAPS —
       `white-space: normal`, `overflow-wrap: break-word` for the one word
       longer than the line. Wrapping a long name is the cost; cutting one is
       the bug. It does NOT span the mark's track: the ↗ is a real link lying
       over the plate, and text under a link is text a thumb cannot reach.
     · the year keeps its own 4ch/8ch track on line two, so the chronology
       still reads as a column down the list — the thing the list IS.
     · the ↗ mark spans BOTH rows (`grid-row: 1 / -1`, `align-self: center`)
       and keeps its 44px square: one row, one mark, centred on it.
     · the plate button spans both rows through `grid-template-rows: subgrid`,
       so the whole two-line row is one tap target and every cell stays placed
       rather than flowed (nu.css's own law, one screen up).
     · the 3rd track goes: `fit-content(10ch)` was the place's, and on line two
       the place has the whole line after the year and needs no cap.

   MEASURED AFTER, on the rendered page, over all 478 rows the catalogue held
   at the hour (the gate counts them again on every run, so this table ages
   into a record rather than into a lie):

                              320px    375px    430px    820px   1280px
     names that ellipsise        0        0        0       10        1
       (before)                476      407      245       10        1
     places that ellipsise      82        0        0        0        0
       (before)                112       10       10        0        0
     row height             51–92    51–92    51–72    44–45    44–45

   The 44px floor and the zero sideways scroll are unchanged; the CEILING is
   deliberately gone below 700px, which is the one-line law being spent where
   Paul asked for it to be spent. Above 700px not a pixel moves — the wide row
   is still the year, the name and the place read as one phrase, which is that
   block's own decision and a good one.
   AND THE ELLIPSIS BILL DID NOT VANISH, IT MOVED AND SHRANK: at 320 the place
   column is 83px and 82 places still cut ("Hohle Fels", "Rio de Janeiro").
   That is the place column doing on the narrowest phone what it has always
   done inside its own cell, to 30 fewer rows than before — and the fact a
   reader came for, the name, is now whole on every one of the 478. */
@media (max-width: 699px){
  .nu-ixli{ grid-template-columns: 4ch minmax(0, 1fr) var(--tap);
            grid-template-rows: auto auto; }
  .nu-ixli[data-bc]{ grid-template-columns: 8ch minmax(0, 1fr) var(--tap); }
  .nu-ixrow{ grid-column: 1 / -1; grid-row: 1 / -1;
             grid-template-columns: subgrid; grid-template-rows: subgrid; }
  /* THE NAME, LINE ONE. Everything the base rule does to keep a title on one
     line is undone here and nowhere else: no nowrap, no clip, no ellipsis. */
  .nu-ixw{ grid-column: 1 / 3; grid-row: 1;
           white-space: normal; overflow: visible; text-overflow: clip;
           overflow-wrap: break-word; padding-inline-end: 0; }
  /* AND THE KIND BREAKS WITH IT, which is a bug this round found by measuring
     rather than by reading (three rows, 320px). `.nu-kind` is `nowrap` — it
     was a two-word footnote riding the end of a one-line title — and there is
     no break opportunity between the title's last word and the span, because
     the leading space is INSIDE the span. So "improvisation · the broader" was
     ONE unbreakable 217px run in a 129px column: a `justify-self: start` item
     takes its min-content width when min-content exceeds the area, and the
     name overhung its own row by 30px and scrolled the list sideways. The
     `nowrap` earns nothing on a line that is allowed to wrap.
     IT KEEPS THE NOWRAP AND TAKES A BREAK POINT INSTEAD (measured both):
     `white-space: normal` fixed the overflow and split the footnote badly —
     "Neolithic flute music ·" / "the broader", a dangling separator at the end
     of a line. `display: inline-block` was the fix with the footnote kept
     whole. BOTH ARE GONE, 2026-09-08 — the kind is not printed any more (see
     `.nu-kind` above, and Paul: *"get rid of 'The Broader'"*), so there is no
     footnote to break and this rule matched nothing. The row's own wrap is
     unchanged: it is the name alone now, which was the wide case all along.) */
  /* A REFUSED ROW'S WORD IS STILL THE OVERLAY THE EXPLAINER ANSWERS, so it
     keeps a thumb's height — but `block-size: var(--tap)` with
     `line-height: var(--tap)` was a ONE-LINE recipe (44px per line), and this
     word may wrap now like any other. A floor and a real line-height instead,
     centred by a flex box it is safe to be inside because nothing here
     ellipsises any more. */
  .nu-ixw-no{ block-size: auto; min-block-size: var(--tap);
              line-height: 1.3; display: flex; align-items: center; }
  .nu-ixy{ grid-column: 1; grid-row: 2; }
  .nu-ixp{ grid-column: 2; grid-row: 2; }
  .nu-ixgo{ grid-column: 3; grid-row: 1 / -1; align-self: center; }
  /* AND THE ROW'S OWN AIR GOES FIRST, WHICH IS THE 320px RULE'S OWN ARGUMENT
     APPLIED ONE BREAKPOINT WIDER. That rule (`max-width: 359px`, above) says
     it exactly: *"the two gaps and the two paddings are 40 of them — 17% of
     the row spent on air … the 13px goes to the title and the place … what
     shrinks is whitespace, which is the right thing to spend first."* Every
     word of that is true at 390 as well: measured on the rendered page at 390,
     the row is 269px wide, `padding-inline: var(--s4)` takes 40 of them and
     the two gaps take 11, so a quarter of the line is air on the one screen
     where the names are being cut. The narrow rule was fenced at 359 because
     that was the phone in front of the person who wrote it; the fence moves to
     where the fixed-track breakpoint already is. Measured after: the title
     track goes 57px -> 70px at 390 on a BC row, and the 44px floor, the one
     line and the zero sideways scroll are unchanged. */
  .nu-ixli{ gap: 0 var(--s2); padding-inline: var(--s3); }
}

/* ---- 19 · THE MOTIF'S NAME FIELD HAS A GAP ----------------------------
   Probe: *"Motif editor: 'name' label butts against the input with zero gap."*
   `renameField` builds a bare `<label>` (not a `.nu-field`, because it is a
   text input beside its word rather than a question over its control), so it
   inherits no gap from anything. One declaration, and the same `--s2` step
   every other label/control pair on the page uses. */
.nu-motifname{ margin-inline-start: var(--s2); }

/* ===============================================================   WAVE 4 — THE WORD GRID, AND THE NAV'S ALIGNMENT (2026-09-02)
   ======================================================================
   Paul, after using the composer on staging: *"When we go into structure make
   those tables of dropdowns full of tappable grids that change options rather
   than dropdowns — like the other selection table in mix. This is a powerful
   element for editing a whole song — think on it and institutionalize it."*

   ONE TABLE SKIN FOR FOUR SURFACES. `.nu-wordgrid` is worn by every
   ui/wordgrid.js instance — Structure's five grids, the board's section
   automation, the Tempo panel's pace strip, a member's per-section strip — and
   it is deliberately worn TOGETHER WITH `.nu-trims`, which is the surface Paul
   is pointing at ("like the other selection table in mix"). So the cell plate,
   the `w-*` word colours, `is-derived`'s dim reading, the row head's jump
   button and the column head's two lines are all inherited rather than
   restated; what is below is only what the accordion needed and what the two
   old tables disagreed about.
   `.nu-sgrid` IS NOT DELETED. It still dresses nothing on the page — the five
   Structure grids that wore it are word grids now — but its paragraphs carry
   the measurements that decided the geometry (the 9ch column, the equal heads,
   the `:has(mark)` playhead) and this block inherits every one of those
   decisions. Kept as the record of why the numbers are the numbers. */

/* ---- the table: equal columns, and allowed to be wider than its pane ---
   `table-layout: fixed` makes every unsized column an equal share, which is
   what turns seven meters into one scale; without the minimum, `fixed` +
   `100%` would crush nine players into 20px each rather than scrolling.
   `--cols` is written by ui/wordgrid.js (a count, not a width) and the pane
   around it already scrolls sideways, which is this page's standing answer for
   a wide table. */
.nu-wordgrid{ table-layout: fixed;
  inline-size: max(100%, calc(13ch + var(--cols, 1) * 9ch)); }
.nu-wordgrid thead th:first-child, .nu-wordgrid tbody th{ inline-size: 13ch; }
/* THE CAPTION IS THE TABLE'S NAME. `<caption>` is the accessible name of a
   grid, so this is a control label — which is what lets it be one word: the
   heads say the rest. */
.nu-wordgrid > caption{ text-align: start; padding-block-end: var(--s2); }

/* ---- the column head: THE NAME LEADS, the instrument is the second line
   Three column heads on this page said name-over-instrument and one said it
   upside down; the component made them one, so the odd one turns rather than
   the three. The CLASSES keep their meanings — `.nu-colname` is the player,
   `.nu-colinstr` is what they are playing — because a class names a FACT and
   not a position; only their type changes here. */
.nu-wordgrid .nu-colname{ font: var(--fw-block) var(--t1)/1.15 var(--sans, inherit);
  opacity: 1; }
.nu-wordgrid .nu-colinstr{ font: var(--fw-body) var(--t0)/1.2 var(--sans, inherit);
  opacity: .85; }

/* ---- the sounding row, painted off the mark the clock already wrote ----
   `:has()` and not a second class: inside #app the clock may not write outside
   `[data-live]`, so the row's tint has to be a CONSEQUENCE of the one write it
   is allowed to make (the `<mark>` in the row head's live span) rather than a
   write of its own. Nothing appears or disappears; only the paint moves. The
   board drives its own row with `tr.now` (it is outside #app and says so with
   `data-live="trimrow"`), and both spellings land on the same tint. */
/* ---- ...AND IT IS ONE BAND, NOT A LINE OF BLOCKS (2026-09-08, §24) ----
   Paul: *"The playing bar is blocky in the sections."*

   THE CAUSE IS `border-spacing`, AND IT TOOK MEASURING TO SEE. The tint was
   painted on the row head AND on every `<td>` — two rules, both correct on
   their own — and `.nu-trims` sets `border-spacing: 3px`, so the cells cannot
   touch. A lit row therefore drew as five or ten SEPARATE tinted rectangles
   with 3px of untinted gap between each, marching across the screen. That is
   the "blocky": not the brightness and not the height, the GAPS.
   THE PAINT MOVES TO THE `<tr>`, WHICH IS THE ONE BOX THAT HAS NO GAPS IN IT.
   Under `border-collapse: separate` a row's background paints behind the whole
   row, spacing included, so the same tint becomes one continuous band with the
   cells sitting on it. One rule instead of two, and nothing about WHICH row is
   lit changes: `:has(mark)` is still the clock's own write read back.
   AND THE LAW IS KEPT RATHER THAN REVERSED. DESIGN.md component 11 has Paul's
   earlier sentence on it — *"Really light up the sections as you move through
   them — not just a tiny halo around the number"* — so the row stays LIT
   WHOLE; what it stops being is segmented. A leading rule in full `--clock`
   goes down the start edge as well, which is `.nu-here`'s own idiom
   (nu.css:203, *"a rule down the row and not a tint"*) doing the job the
   number's red used to do alone.
   `> td > .nu-trimbtn{ border-color }` GOES WITH THE SECOND TINT. Its own note
   already recorded that the rule it replaced *"stopped meaning anything the
   minute a resting cell lost its border"*; with the band continuous behind
   them the cells need no third mark. */
.nu-wordgrid tbody tr:has(mark){ background: var(--clock-tint); }
/* ...AND THE CELLS GET OUT OF THE BAND'S WAY. The row head carries an OPAQUE
   `--ground` (the seam-closer, `#pan-band .nu-wordgrid tbody th.nu-srowh`), so
   a band painted on the `<tr>` would be covered exactly where the section's
   name is — the band would start at the first cell and the head would sit
   outside it, which is the segmentation this rule is here to end. Both are
   made transparent for the duration, which is the same two lines
   `tr.is-playing` states below and for the same reason. */
#pan-band .nu-wordgrid tbody tr:has(mark) > th,
#pan-band .nu-wordgrid tbody tr:has(mark) > td{ background: transparent; }
#pan-band .nu-wordgrid tbody tr:has(mark) > th.nu-srowh{
  box-shadow: inset 3px 0 0 var(--clock); }

/* ---- a cell: the trim plate, and it may not widen the column -----------
   `.nu-trimbtn` is the whole plate (44px, the word colours, the dim derived
   reading). What a fixed table adds is that a long word — `as written`,
   `yamaha grand piano` — must ELLIPSISE rather than set the column's
   min-content and push the table sideways past its pane. */
.nu-wcell{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding-inline: var(--s1); }
.nu-wcell[aria-expanded="true"]{ border-color: var(--hand);
  border-width: var(--bw-hard); }
/* A REFUSED CELL IS GREY AND STILL OPENS — see ui/wordgrid.js for why. It is
   `aria-disabled` and not `disabled`, so the strip under it can still show the
   shape of the possible; nothing in that strip can write. Dashed, so a refusal
   reads as a refusal and not as an unset cell. */
.nu-wcell.is-refused{ opacity: .55; border-style: dashed; cursor: not-allowed; }
.nu-wcell[disabled]{ opacity: .55; border-style: dashed; cursor: not-allowed; }

/* ---- A CELL IS A CELL, AT REST (TABLE.md §11, RULED 2026-09-05) --------
   Paul: *"less boxes inside the cells and more of the cells just being cells"*
   and *"keep our stuff but make it less chunky and more stylish"*.

   `.nu-trimbtn` — the plate, with its hairline on all four sides and its 3px
   radius — was the whole visual grammar of a table cell, and MEASURED on the
   reggae record at 390 it was 91 boxes drawing 18,200px² of rule around
   580px² of average ink: an ink-to-box ratio of 0.236. A grid is not
   ninety-one buttons. So the body, mix and footer cells wear THIS instead: no
   border, no radius, no ground. The tap target does not move — the button is
   still a button and still 44px tall, which is the one thing the restyle may
   not spend — and what the frame used to carry, TYPE carries:

     WRITTEN is `--fw-label` (700), the page's own weight for a fact a hand
       put there. It was 500, one step off the 400 beside it.
     INHERITED keeps the quiet it already had — 400, tabular figures, .78 —
       so a column of dealt readings still lines up and still recedes.
     SELECTED is the ring (`.nu-wcell.is-sel`, below), and now that the ring
       is the ONLY box on the surface it reads as a position rather than as
       one more border among ninety.
     REFUSED is still dashed, and because there is no border left to make
       dashed it draws its own — a refusal has always been a visible edge and
       this is the one cell that keeps one.
     OPEN (the editor is out) is the hand's tint plus an inset ring: the cell
       being edited is a state, not a plate.

   The word colours (`.w-out` … `.w-lift`) are untouched and still paint the
   cells that carry them; they are a GROUND with a meaning, which is exactly
   the kind of box §11 keeps. */
.nu-cellword{ inline-size: 100%; min-block-size: var(--tap); min-inline-size: 56px;
  border: 0; border-radius: var(--r2); background: transparent; color: var(--ink);
  padding: 0 var(--s1); cursor: pointer;
  font: var(--fw-label) var(--t1)/1.25 var(--mono, monospace); }
.nu-cellword.is-derived{ font-weight: 400; font-variant-numeric: tabular-nums;
  letter-spacing: 0; opacity: .78; }
.nu-cellword.is-refused, .nu-cellword[disabled]{
  border: var(--bw) dashed currentColor; }
.nu-cellword[aria-expanded="true"]{ background: var(--hand-tint);
  box-shadow: inset 0 0 0 var(--bw-hard) var(--hand); }

/* ---- THE STRIP OF WORDS: a card, from 2026-09-08 (TABLE.md §22) --------
   Paul: *"Instead of expanding sections in the editor and inserting them below
   the selected point just make them modals with easy dismissal."*

   WHAT STOOD HERE, kept whole because it is the argument the accordion won
   and because two of its three claims are still the sheet's:
     "THE STRIP OF WORDS: an accordion row, never a popup. Paul: *'tappable
      grids that change options.'* The row you tapped grows one `<tr>`
      underneath holding every word that cell could say. It is a real table
      row, so the grid is still a grid with the stylesheet off and a screen
      reader reads the words immediately after the cell that asked for them.
      IT NEVER SCROLLS INSIDE ITSELF, which is this page's standing law about
      menus. The chips WRAP and the row gets taller; the pane the grid stands
      in is the one scroller and it already was. `position: sticky` on the
      cell would have been the popup by another name and is refused for the
      same reason."
   AND ITS TWO RULES ARE DELETED, BECAUSE NOTHING BUILDS THE ROW ANY MORE.
   `.nu-wopen > td { padding: var(--s2) 0 }` and `.nu-wopen > td { position:
   relative }` stood here and at the foot of the `.nu-vsheet` block. For one
   day they described exactly one grid — the table stopped drawing the row on
   2026-09-08 and `ui/wordgrid.js` still did, so the sentence here read *"the
   accordion has one user left … the day the board's grid moves they go with
   it."* The board's grid moved the same day (§22b, Paul: *"do them"*), and
   they go with it. There is no `.nu-wopen` in this document.
   WHAT THE MEASUREMENT WAS: 779.4px of `<tr>` on an 844px phone, inserted
   between the row you tapped and every row below it. "The row gets taller"
   was true and was the problem.
   WHAT SURVIVES: the sheet is still a real, readable block with the
   stylesheet off (a `<div>` after the table rather than a `<tr>` inside it —
   reading order still puts it after the grid it belongs to), the chips still
   WRAP rather than scroll sideways, and the surface still has exactly ONE
   scroller. The scroller is the CARD now instead of the pane, which is the
   half of the old law that had to give: an eighteen-field vector does not fit
   in the 720px a phone has between the two fixed bands, and "vertical space
   is cheap and abundant" was a claim about a document, not about a phone. */

/* ---- THE SCRIM, WHICH IS THE DISMISS ---------------------------------
   A full-bleed veil at `--scrim` — the token's own words are *"a scrim is a
   depth and not a colour"*, which is exactly this — and a press on it closes
   the card (src/table/grid.ts `modal`, and `armOutside` as the belt).
   IT IS z 57, WHICH IS UNDER BOTH FIXED BANDS AND THAT IS THE DECISION.
   `.nu-topstrip` and `.nu-bar` are 58, so they paint OVER the veil and stay
   live while a sheet is open: the record's name is still readable, and ▶ is
   still under a thumb. That is `#play`'s oldest law on this page — *"a
   permanent play button"*, the one mark on the screen in every state — and a
   modal that took the transport away would be the first surface here to break
   it. The hamburger's plate is 59 and the tooltip 60, so both still open over
   a card, which is the order they had over an accordion row.
   THE PADDING IS THE TWO BANDS, so the CARD is inset out from under them
   while the veil is not: `--top-h` at the head, `--bar-h` at the foot (which
   already carries the safe area), and `--s2` of air on every side. */
.nu-modal{ position: fixed; z-index: 57; inset: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  align-items: stretch;
  padding: calc(var(--top-h) + var(--s2)) var(--s2)
           calc(var(--bar-h) + var(--s2));
  background: var(--scrim); }
/* (`touch-action: none` STOOD ON THIS RULE FOR AN HOUR AND IS REFUSED. It was
   there to stop a drag on the dark scrolling the grid behind it, and the grid
   was never reachable from here: `.nu-modal` is a child of `.nu-modalroot`,
   which is a SIBLING of `.nu-sheetwrap` — `.nu-pane` is not an ancestor of
   the scrim, so a gesture that starts on the veil has no scroller of the
   record to chain into. (Measured: a wheel over the scrim leaves the pane at
   scrollTop 0.) What the declaration WOULD have risked is the card: the
   sheet's own `.nu-sheettrack` is a horizontal scroller (§19) and
   `.nu-modalbody` a vertical one, `touch-action` is intersected up the
   ancestor chain, and the exact stopping rule — at the scroll container that
   handles the gesture — is the kind of clause that is right in the spec and
   wrong in one engine. A property that can only break the two scrollers it is
   not aimed at, to prevent a scroll that cannot happen, is not a property
   this page needs.) */
/* ---- THE CARD --------------------------------------------------------
   BOTTOM-ANCHORED ON A PHONE (`justify-content: flex-end` above), because the
   two things a hand does with an open sheet — read its first line, and shut
   it — are then both inside a thumb's arc, and because a card that grows
   UPWARD from the foot never moves the words already under the eye when it
   gets taller. It is centred on a wide screen (below), where there is no
   thumb and a card pinned to the bottom edge of a 900px window is a strip.
   IT IS THE ONE SCROLLER AND IT SAYS SO IN ONE DECLARATION. `max-block-size:
   100%` against the flex parent (which is the viewport less the two bands)
   plus `overflow: auto` — so a four-field sheet is four fields tall and an
   eighteen-field vector scrolls INSIDE the card while the record behind it
   holds perfectly still. That last clause is the whole dividend of the round:
   the accordion could not promise it, because its scroller was the page.
   THE PLATE IS THE PAGE'S OWN — panel ground, ink border, the hard shadow
   this box draws under anything that has arrived on top of something else. */
/* AND IT NEVER TAKES THE WHOLE BOX, WHICH IS WHAT MAKES "TAP OUTSIDE" REAL.
   MEASURED on the first build: an eighteen-field cell vector is 785px of
   content, `max-block-size: 100%` gave the card 738.4 of the 744 available,
   and the 5.6px left was the horizontal padding — there was no scrim on the
   glass to press. A dismissal you cannot aim at is not a dismissal. `88%`
   leaves **89px** of veil above the card at 390 x 844, which is two thumbs
   wide, and it costs a long sheet 89px of a scroller it was going to scroll
   anyway. A SHORT sheet is unaffected: the cap is a ceiling, and a four-field
   sheet is four fields tall. */
/* AND IT WEARS NO FOCUS RING, WHICH IS THE ONE EXCEPTION THIS PAGE'S
   `:focus-visible` RULE IS ALLOWED. The card is `tabindex="-1"`: it is never
   in the tab order and the only thing that ever focuses it is `landFocus`,
   which lands a hand there so a screen reader reads the dialog's NAME before
   its first control. Chromium matches `:focus-visible` on that script focus,
   so the page's own indicator drew a 2px `--hand` ring around the WHOLE CARD
   the instant it opened — measured, on the TIME sheet: a green box around
   649.8 x 378.8 of editor, saying "this container is the thing you are on",
   which is not what a focus ring means anywhere else on this page.
   NOTHING IS LOST BY TAKING IT OFF. A keyboard cannot reach this element to
   need it; `holdTab` sends every real press to a real control, and those keep
   their rings. */
.nu-modalcard:focus, .nu-modalcard:focus-visible{ outline: none; }
.nu-modalcard{ display: flex; flex-direction: column;
  min-block-size: 0; max-block-size: 88%; overflow: hidden;
  background: var(--panel); color: var(--ink);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  box-shadow: var(--shadow); }
/* ---- ITS HEADER: THE NAME AT THE START, THE × AT THE END --------------
   The same arrangement `.nu-sheethead` gives every full-page sheet on this box
   (ui/eight.js `sheetHead`): one line, a name, a way out. One shape for "a
   surface you are inside of", wherever it opens.
   IT DOES NOT SCROLL WITH THE BODY. The header is outside the scroller (the
   body below owns the overflow), so the × is on the glass at every scroll
   position of an eighteen-field vector — which is what "easy dismissal" has
   to mean on the sheet that is actually long enough to need it. */
/* `align-items: flex-start` AND NOT `center`, WHICH IS A MEASUREMENT. On a
   cell sheet the header IS `.nu-cellhead` (address · undo · redo · copy ·
   paste), and at 390 that wraps to two lines — so a centred × floated in the
   middle of an 88px header, level with nothing. At the start it is level with
   the address, which is the line it belongs to and the corner a hand looks in
   for a close. On the one-line header of the other five sheets the two rules
   draw the same picture. */
.nu-modalhead{ flex: 0 0 auto; display: flex; flex-direction: row;
  align-items: flex-start; gap: var(--s2);
  padding: var(--s1) var(--s1) var(--s1) var(--s3);
  border-block-end: var(--bw) solid var(--rule); }
.nu-modalname{ flex: 1 1 auto; min-inline-size: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font: var(--fw-label) var(--t2, .82rem)/1.2 var(--sans, inherit); }
/* THE × IS A TARGET AND WEARS THE FLOOR, and it is the one control in this
   card that is not the sheet's — so it is a plain mark on the header's own
   ground rather than a plate, exactly as the genre header up in the strip is.
   Its accessible name says WHICH sheet it closes (`sheet.close`). */
.nu-modalx{ flex: 0 0 auto;
  min-inline-size: var(--tap); min-block-size: var(--tap);
  background: none; border: 0; border-radius: 0; box-shadow: none;
  color: var(--ink); font-size: 1.25rem; line-height: 1; cursor: pointer; }
.nu-modalx:hover{ background: var(--flag-tint); }
/* ---- AND THE BODY IS WHAT SCROLLS ------------------------------------
   `min-block-size: 0` is what turns the card's height cap into a scroller on
   a flex child — the same declaration `.nu-sheetwrap` makes about the pane
   (§11c), for the same reason, and the one people leave out. */
.nu-modalbody{ flex: 1 1 auto; min-block-size: 0; overflow: auto;
  /* AND THE SCROLL STOPS AT THE CARD'S OWN EDGES. `overscroll-behavior:
     contain` is what keeps a flick past the end of an eighteen-field sheet
     from handing the rest of the gesture to whatever is behind — the same
     declaration `.nu-pane` makes about the same problem one surface up. It is
     the ONE scroll property this card needs; see the tombstone on `.nu-modal`
     for the `touch-action` that was tried here and taken out. */
  overscroll-behavior: contain;
  padding: var(--s2);
  display: flex; flex-direction: column; gap: var(--s2); }
/* ---- A CARD IN THE MIDDLE, ON A SCREEN WITH NO THUMB ------------------
   700px is this page's own desk/pocket line (`.nu-pan`, and every other rule
   that has had to draw it). Above it the card is centred and capped: 44rem is
   about 700px, which is two of `.nu-vsheet`'s 20rem columns plus its gaps, so
   a wide sheet lays out in two columns and a card three columns wide — a
   1,280px sheet with fields a metre apart — never happens. */
@media (min-width: 700px) {
  .nu-modal{ justify-content: center; align-items: center; }
  .nu-modalcard{ inline-size: min(100%, 44rem); }
}
/* 3px OF GAP AND OF PADDING (2026-09-05) — the same 3px `.nu-trims` spends
   between its own cells (`border-spacing: 3px`), which is the minimum the
   cell-row language allows: a strip of words and the grid above it are the
   same surface and had two different rhythms (5.6px here, 3px there).
   MEASURED before: a strip of eight registers was 8 x 63px in a 250px row and
   wrapped to three lines; the chips carry their own word plus .5ch now. */
.nu-wchips{ display: flex; flex-wrap: wrap; gap: 3px;
  padding: 3px; background: var(--well); border-radius: var(--r2);
  border: var(--bw) solid var(--rule); }
/* A CHIP IS ITS WORD PLUS A HAIR. `min-inline-size: var(--tap)` made a 6.9px
   digit into a 44px plate — Paul, 2026-09-05: *"the dropdowns and cells and so
   forth have tons of padding … make the buttons a tiny bit bigger than the
   words themselves."* The 44px BLOCK size stays: a target's HEIGHT is what
   test/shell.js and T5g measure and what a thumb misses. */
.nu-wchip{ min-block-size: var(--tap); min-inline-size: 0;
  padding-inline: .5ch;
  font: var(--btn-fw) 1rem/1.2 var(--sans, inherit); }
/* the word you are on, in the hand's own paint — the same cobalt the gutter's
   mark and every `aria-pressed` control on this page wears */
.nu-wchip[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill);
  border-color: var(--hand); }
.nu-wchip[aria-pressed="true"]:hover{ background: var(--lamp-hot); }
/* A REFUSAL IS GREY AND CARRIES ITS REASON. `data-why` is on the chip, the
   reason is joined to its accessible name and repeated in its `title`, so
   nothing here is a silent grey — which is the failure this box legislates
   against and the whole reason the strip shows refused words at all instead of
   hiding them (hiding destroys the shape of the possible, avail.js's law). */
.nu-wchip[disabled]{ opacity: .5; cursor: not-allowed; }
/* AN INERT WORD IS NOT A REFUSAL. It would sound the same here — offered, and
   said to be quiet, in the same italic the sheets use for the same fact. */
.nu-wchip.is-quiet{ font-style: italic; color: var(--dim); }

/* ===== THE SHEET, THE OPS AND THE GROUPS (2026-09-04, TABLE.md 2b) ======
   The Band table's accordion opens onto a VECTOR rather than onto one strip of
   words (ui/wordgrid.js says why): one cell-row per field, the label on the
   left, the word that is true on the right, a clear-back where a hand has
   written, and the field's own strip of words growing under its own row.

   IT IS A LIST AND NOT A GRID, and that is the phone-first law rather than a
   preference: two columns at 320px give a label 12ch and a value 12ch, which
   is not enough for "artic · oct · rate · scale · clamp" or for "yamaha grand
   piano". The label WRAPS above the control below 30rem and stands beside it
   above; the control is the 44px plate at both widths, because a target may
   not shrink to fit a layout.
   NOTHING HERE SCROLLS INSIDE ITSELF. `.nu-pane` is still the one scroller
   and the sheet's row is still a `<tr>` in the table, so a long sheet makes
   the TABLE taller and the page scrolls, which is nu.css's standing law about
   menus (2026-08-16, "Don't make me scroll INSIDE a popup"). */
/* `.nu-vsheet` AND NOT `.nu-sheet`: that name is TAKEN. ui/selects.js's
   multi-choice widget has been a `<fieldset class="nu-sheet">` since the sheets
   shipped, with its own grid CSS above and three readers in test/selects.js
   (`.nu-sheet[data-sheet=…] .nu-opt`). The first draft of this block took the
   name and test/selects.js immediately reported thirty-two lit sheets with no
   key — a collision found by the gate whose whole job is that vocabulary. The
   V is for the VECTOR, which is what this one holds. */
/* ---- IT SPENDS THE WHOLE WIDTH, AND THE FIELDS FLOW ACROSS IT --------
   Paul, 2026-09-05, looking at the table on staging: *"Clicking '1 head'
   results in an enormous blank space and a tiny vertical area and nothing is
   readable. You should spread things out to 100% of the screen width and make
   the buttons a tiny bit bigger than the words themselves."*

   MEASURED on the rendered page before this rule, Kingston 1969 at reading 1,
   the first section's row sheet:
     · at 1280 the sheet was 1068px wide and its CONTENT was one column
       268px across — an 88px label plus a 180px plate — so 75% of the sheet
       was blank and the twenty-four fields ran 1,267px DOWN. That is the
       "enormous blank space beside a tiny vertical area", exactly.
     · every plate was the same 180px whatever word was in it: the `bars`
       plate held a 6.9px glyph ("2") in a 180px box — 3.8% ink — because
       `flex: 1 1 12ch; max-inline-size: 26ch` sizes a control by the LAYOUT
       and not by its word. That is the "tons of padding".
     · every word on the sheet was 11.52px (`--t1`), which is what "nothing
       is readable" is measuring.
   AFTER: the sheet is a GRID of `min(100%, 20rem)` columns, so 1,068px holds
   three of them and 263px holds one; a plate is `flex: 0 1 auto` with .5ch of
   padding, so it is its own word plus a hair; and every word on a sheet is
   1rem. The 44px tap floor is untouched — it is a BLOCK size, and none of
   this is about height.
   WHAT SPANS THE WHOLE WIDTH: an ops bar, a seated widget, and a field's own
   open strip of words. All three are answers to one question and a column
   three-fifths of the way across would be a second reading order. */
.nu-vsheet{ display: grid; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: var(--s2) var(--s3);
  padding: var(--s2); background: var(--well); border-radius: var(--r2);
  border: var(--bw) solid var(--rule);
  /* ---- AND IT STAYS WHERE THE THUMB IS ------------------------------
     THE TABLE IS WIDER THAN THE PANE and the pane is what scrolls sideways
     (`.nu-pane`, this page's standing answer for a wide table). An open row
     spans every column, so without this the sheet was as wide as the TABLE:
     at 390px the third op — "copy down the column" — sat 200px past the right
     edge and a thumb had to scroll the grid sideways to reach a control that
     is not about a column at all. MEASURED on the rendered page before the
     rule and after.
     `sticky` on the CELL and a viewport width on the sheet: the row stays a
     real `<tr>` (the accordion's whole argument), the sheet rides the scroll
     to the pane's left edge, and it is never wider than the screen minus the
     stripe. §6 ¶A — "every op in §5 … reachable by tap at 320px" — is a claim
     about where a control IS, not only about whether it exists.
     THE ARITHMETIC IS THE PANE'S OWN, EXACTLY (2026-09-05). It read
     `2 * var(--s3)` for the air around the pane, which is 17.6px against the
     25.6px `.nu-pan` actually pays (`padding: var(--s4)` on each side, .8em),
     so the sheet stood 8px past the right edge of the pane at every width — a
     sideways scroll of its own on a box whose whole argument is that it has
     none. Measured: sheet 263.2 in a 255.2 pane at 390; 1068.4 in a 1078.4
     pane at 1280. The panel's own padding is named now, so the sheet IS the
     pane's width and the gate can say `>= 95%` of it. */
  /* ...AND THE TWO DECLARATIONS THAT PAID FOR IT ARE GONE (2026-09-08, §22
     and §22b). They read `position: sticky; inset-inline-start: 0;
     inline-size: min(100%, calc(100vw - var(--gl) - var(--gr) - 2 *
     var(--s4)))`, and every word of the argument above is a fact about a sheet
     standing INSIDE A TABLE THAT SCROLLS SIDEWAYS. No sheet does: the editor's
     went into a card on 2026-09-08 and the board's went the same day, so both
     stand in a `position: fixed` box that is already capped at the screen (and
     at 44rem on a desk) with no sideways scroll under it to ride. The sticky
     and the 100vw arithmetic were two declarations fighting a scroll that is
     no longer there.
     IT SPENT AN HOUR AS A SCOPED OVERRIDE — `.nu-modalbody > .nu-vsheet` — and
     that was right while `ui/wordgrid.js` still put a sheet in a `<td>`: two
     boxes, one component, which is exactly what a scoped override is for. With
     one box left there is nothing to scope against, and a rule that says the
     same thing twice is a rule that will one day say it differently.
     WHAT THE ARGUMENT WAS PROTECTING IS UNCHANGED AND IS STRUCTURAL NOW: §6
     ¶A — *"every op in §5 … reachable by tap at 320px"* — cannot be broken by
     a horizontal scroll no surface has. The plate also comes off (`padding`,
     `background`, `border`): the CARD is the plate, and a bordered box inside
     a bordered box is the thing §11 spent a round deleting. */
  inline-size: 100%;
  padding: 0; background: none; border: 0; border-radius: 0; }
/* AN OPS BAR, A SEATED WIDGET AND AN OPEN STRIP TAKE THE WHOLE SHEET. */
.nu-vsheet > .nu-sheetops,
.nu-vsheet > .nu-noderow,
.nu-vsheet > .nu-wchips,
.nu-vsheet > .nu-wgroups{ grid-column: 1 / -1; }
.nu-sheetrow{ display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); min-inline-size: 0; }
.nu-sheetlab{ flex: 0 0 auto; min-inline-size: 9ch;
  font: 700 1rem/1.25 var(--sans, inherit); color: var(--dim); }
/* A PLATE IS ITS OWN WORD PLUS A HAIR — `0 1 auto`, never a share of the row.
   `.nu-trimbtn` carries `inline-size: 100%` and a 56px floor for the TABLE,
   where a fixed column has to be filled; inside a sheet there is no column to
   fill and a 180px box around "2" is what Paul is pointing at. */
.nu-vsheet .nu-sheetrow > .nu-wcell{ flex: 0 1 auto; inline-size: auto;
  min-inline-size: 0; max-inline-size: 100%; padding-inline: .5ch;
  font: var(--btn-fw) 1rem/1.2 var(--mono, monospace); text-align: start; }
.nu-sheetsub{ flex: 0 0 auto; color: var(--dim); font-size: .8rem; }
/* ---- AN OP THAT IS A MARK (2026-09-08, TABLE.md §24) ------------------
   Paul: *"This cell—the basic operations should be icons"* and *"Use icons for
   copy paste."* Five word-buttons wrapped to two rows on the cell card at 390
   — `clear to default` alone is 14 characters — and every one of them is a
   gesture a spreadsheet already has a picture for.
   IT IS A SQUARE AND THE WORD IS UNDER THE STYLESHEET, NOT GONE. `.nu-vh` is
   the same visually-hidden span every mark on this page carries: a screen
   reader hears `clear`, a stylesheet-off reading prints it, and only an eye
   with the CSS on sees a mark alone. The `--tap` floor is on both axes because
   a picture is a smaller target than a word and this is where that would be
   felt first. */
/* ===== THE MOTIF FIELD (2026-09-08, TABLE.md §24) ======================
   Paul: *"The motif selector should be visual and let me see motifs and make
   new motifs and assign multiple motifs and click to edit motifs."*

   A GRID OF PICTURES, WRAPPING, NEVER A TRACK. The card is `auto-fill` at a
   `9ch` minimum, so a phone gets three across and a desk gets as many as fit —
   and it WRAPS rather than scrolling sideways, because §24 has just taken the
   sideways axis off this sheet and a field that reintroduced one would be the
   same complaint one level down.
   THE PICTURE IS THE POINT AND IT IS GIVEN THE ROOM. `ui/preview.js` draws an
   `<svg>` of the phrase itself — one bar per step, height by velocity — and it
   gets a fixed band at the top of each card so twelve motifs read as twelve
   SHAPES in a row before a single name is read, which is the sentence that
   file was written under. A motif with no picture (the absent detent) leaves
   the band empty rather than collapsing, so the names stay on one line. */
.nu-morow{ flex-direction: column; align-items: stretch; }
.nu-mogrid{ display: grid; gap: var(--s2);
  grid-template-columns: repeat(auto-fill, minmax(9ch, 1fr));
  inline-size: 100%;
  /* ---- AND A LONG ONE SCROLLS INSIDE ITSELF (2026-09-08) --------------
     Paul asked for the motif selector on the instrument row, and the motif
     bank is nine cards while the instrument list is a hundred and fifty-one.
     MEASURED after that change: the grid stood 2,424px tall and the chair
     sheet showed TWO settings at rest where it had shown six — every other
     subject (the envelope, the register, the entry, the desk) pushed under a
     wall of instruments, which is the defect §19 built the sideways table to
     avoid and which test/table.browser.js T19a caught in the same shape it
     was written for ("more settings are on the glass at rest than the FOUR
     the stack showed": 2 of 12).
     So a grid is capped at TWO ROWS OF CARDS and scrolls inside itself. The
     bank never reaches the cap — nine cards is two rows — so the motif row is
     untouched; the instrument row keeps every option drawn and reachable, and
     the sheet keeps its other subjects on the screen. `overscroll-behavior`
     stops the scroll chaining out into the sheet behind it, which is the same
     clause `.nu-pane` has carried since it was written. */
  /* THE HEIGHT IS A GUESS HERE AND A MEASUREMENT IN grid.ts (2026-09-08).
     This line asked for "two rows of cards" in the only unit CSS has — the
     44px tap floor a card is BUILT on — and a card is 57 to 88px tall
     depending on whether it carries a sparkline, so the box came out one row
     and a half and sliced the second one through the middle of its own words.
     Paul: *"This is a mess do you see it."* `placeGrids()` overrides this with
     `ROWS * card + gap` measured off a real card on the frame after it is
     drawn; what stays here is a sane floor for the frame before that, and for
     any host that never runs the placer. */
  max-block-size: calc(2 * var(--tap) + 1 * var(--s2));
  overflow-y: auto; overscroll-behavior: contain;
  /* AND A HAND'S SCROLL STOPS ON WHOLE CARDS TOO. The placer rounds the scroll
     it performs; this is the same rule for the one a thumb performs, and it is
     `proximity` rather than `mandatory` so a flick still travels — it only
     refuses to STOP halfway through a row. */
  scroll-snap-type: y proximity; }
.nu-mogrid > .nu-mocard{ scroll-snap-align: start; }
/* A CARD IS THE TARGET AND THE ✎ RIDES IN ITS CORNER. `position: relative` on
   the card is what the pencil is placed against; the pick button underneath it
   is the whole card, so the 44px floor belongs to the thing a thumb aims at
   and the pencil is a second, smaller target ON it rather than a slice out of
   it. */
.nu-mocard{ position: relative; display: block; min-block-size: var(--tap); }
.nu-mopick{ inline-size: 100%; min-block-size: var(--tap);
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 2px;
  padding: var(--s1) var(--s2);
  background: var(--well); color: var(--ink);
  border: var(--bw) solid var(--rule); border-radius: var(--r2);
  font: var(--btn-fw) var(--t2)/1.2 var(--sans, inherit); cursor: pointer; }
.nu-mopick[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill);
  border-color: var(--hand); }
.nu-mopick[aria-disabled="true"]{ opacity: .55; cursor: default; }
.nu-mopv{ display: block; min-block-size: 1.2em; line-height: 0; }
.nu-moname{ display: block; max-inline-size: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-moprov{ display: block; color: var(--dim); font-size: var(--t1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-mopick[aria-pressed="true"] .nu-moprov{ color: var(--on-fill); opacity: .8; }
/* THE PENCIL IS A CORNER MARK. It is `--tap` in BLOCK size and narrower in
   line size on purpose: a 44px square in the corner of a 9ch card would cover
   the picture it is about, and the vertical is the axis a thumb misses on. */
.nu-moedit{ position: absolute; inset-block-start: 0; inset-inline-end: 0;
  min-block-size: var(--tap); inline-size: 2.2em;
  display: flex; align-items: flex-start; justify-content: flex-end;
  padding: 2px 3px 0 0;
  background: none; border: 0; border-radius: 0; box-shadow: none;
  color: var(--dim); font-size: .9rem; line-height: 1; cursor: pointer; }
.nu-moedit:hover{ color: var(--ink); }
/* THE `+` CARD IS A CARD, so the grid does not have to know it is different —
   the only thing it lacks is a picture, and it says its word instead. */
.nu-monew{ display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 2px; min-block-size: var(--tap);
  padding: var(--s1) var(--s2);
  background: none; color: var(--dim);
  border: var(--bw) dashed var(--rule); border-radius: var(--r2);
  font: var(--btn-fw) var(--t2)/1.2 var(--sans, inherit); cursor: pointer; }
.nu-monew:hover{ color: var(--ink); border-color: var(--ink); }
.nu-monew > .nu-g{ font-size: 1.1rem; line-height: 1; }

.nu-opbtn.is-mark{ inline-size: var(--tap); min-inline-size: var(--tap);
  min-block-size: var(--tap); padding: 0;
  display: inline-flex; align-items: center; justify-content: center; }
.nu-opbtn.is-mark > .nu-g{ font-size: 1.1rem; line-height: 1; }
/* A ROW WITH A WIDGET IN IT IS A BLOCK, not a label-beside-a-control. The
   channel strip is the one user and it is 207px wide at its narrowest; the
   label stands above it and it takes the sheet's whole width, which is the
   pane's. See ui/wordgrid.js's `nu-noderow` for the measurement. */
.nu-sheetrow.nu-noderow{ flex-direction: column; align-items: stretch; }
.nu-sheetrow.nu-noderow > .nu-sheetlab{ min-inline-size: 0; }
.nu-seatstrip{ inline-size: 100%; min-inline-size: 0; }
/* ...AND SO ARE THE OTHER TWO WIDGETS A COLUMN SHEET SEATS (2026-09-04, wave
   2c): the chair's THROAT (`knobsBlock` — VOICE.md's knob table and its tract
   pad) and its FILES (`crateBlock` — ui/samples.js's crate, filtered to this
   one player). Both were blocks of the deleted Band pane and both bring their
   own `pane()` where they need one, so all this row has to do is give them the
   sheet's whole width and stop the flex row from squeezing them. */
.nu-seatknobs, .nu-seatcrate{ inline-size: 100%; min-inline-size: 0; }
/* A READOUT, AND A REFUSAL IS A READOUT WITH A REASON. `.is-refused` is the
   cell plate's own dashed grey, said again for a span — a field the engine
   cannot reach yet (a cell's mix lane, wave 3) prints its word, wears the
   dash, and carries `data-why` where a gate and a long-press both read it. */
.nu-sheetsay{ flex: 1 1 auto; min-block-size: var(--tap);
  display: flex; align-items: center;
  font: var(--fw-body) 1rem/1.3 var(--sans, inherit); color: var(--ink); }
.nu-sheetsay.is-refused{ opacity: .6; border-block-end: var(--bw) dashed var(--rule);
  cursor: help; }
/* A NAME A HAND TYPES (2026-09-06, wave C item 8 — DESIGN.md §2 component 19).
   The one control on this surface that is a KEYBOARD and not a vocabulary, so
   it is drawn as the sheet's other controls are and not as a form field from
   somewhere else: the row's slack, `--tap` of height, the seated underline
   `#pan-band .nu-vsheet` gives every control in a sheet, and the page's own
   "written is bold, default is quiet" in the weight.

   THE TYPE IS `--t3` AND THAT IS 16px ON PURPOSE. Mobile Safari zooms the
   whole page in on a focused `<input>` whose text is under 16px and does not
   zoom back out, which is the pane's width thrown away for the rest of the
   session — the one measurement that decides a text field's size on a phone.
   (`.nu-numbox` beside it is `--t3` for the same reason.)

   THE PLACEHOLDER IS THE DEFAULT AND IS DRAWN AS ONE: `--dim` at full opacity,
   which is the same ink every inherited value on this page wears, so an empty
   box reads as "the type stands here" rather than as a hint about typing. */
.nu-textbox{ flex: 1 1 12ch; min-inline-size: 0; max-inline-size: 100%;
  min-block-size: var(--tap);
  font: var(--fw-label) var(--t3)/1.2 var(--sans, inherit); text-align: start;
  border: var(--bw) solid var(--rule); border-radius: var(--r2);
  background: var(--panel); color: var(--ink); padding-inline: .5ch; }
.nu-textbox.is-derived{ font-weight: var(--fw-body); }
.nu-textbox::placeholder{ color: var(--dim); opacity: 1; }
/* A REFUSED NAME BOX IS READONLY AND SAYS SO (2026-09-07, the law audit).
   It was `disabled`, which the browser paints grey and which takes no tap —
   the silent grey `src/table/sheet.ts`:158 forbids by name. It is `readonly`
   now: focusable, tappable, copyable, and unable to reach the record. So the
   REFUSED REGISTER has to be drawn here rather than borrowed from the user
   agent, and it is the register every other refusal on this page already
   wears — a dashed edge and the quiet ink, never a fill and never a fade so
   deep the word goes. The dashed edge is what carries the refusal (component
   14's own design); the sentence in the row's say line is what explains it. */
.nu-textbox[readonly]{ border-style: dashed; color: var(--dim);
  cursor: default; }
/* THE CLEAR-BACK (TABLE.md §2), and it is drawn only where there is something
   to clear — so its presence is itself the reading "a hand wrote here". */
.nu-clearback{ flex: 0 0 auto; min-block-size: var(--tap);
  padding-inline: .5ch;
  font: var(--btn-fw) .8rem/1.2 var(--sans, inherit); color: var(--dim); }
/* THE OPS (§5's grammar). Chunky, wrapping, 44px, never a menu: an op is a
   decision and decisions are chips on this page.
   .5ch OF PADDING AND NOT `--s3` (2026-09-05, Paul: *"make the buttons a tiny
   bit bigger than the words themselves"*). `--s3` is .55em, which is 8.8px a
   side at this size; half a character is 4.6px, which is a hair and reads as
   one. The 44px BLOCK size is untouched — the shell gate's law is about the
   height of a target and this is the width of a word. */
.nu-opbar{ display: flex; flex-wrap: wrap; gap: var(--s2); flex: 1 1 100%; }
.nu-opbtn{ min-block-size: var(--tap); padding-inline: .5ch;
  font: var(--btn-fw) 1rem/1.2 var(--sans, inherit); }
.nu-opbtn[disabled]{ opacity: .5; cursor: not-allowed; }
/* A VERB YOU PUSH IS NOT A VALUE YOU SET (TABLE.md §19, 2026-09-07). The
   chair's second op bar is thirty-three qualities — brighter · darker · drier
   · wetter — and at the value's own 1rem they drew a 441px wall on a 844px
   phone. Smaller type and less padding, the 44px thumb floor untouched (it is
   a BLOCK size and none of this is about height), and the bar reads as a
   vocabulary rather than as thirty-three decisions. */
.nu-sheetops.is-verbs .nu-opbtn{ font-size: var(--t1); padding-inline: .4ch; }
.nu-sheetops.is-verbs .nu-opbar{ gap: var(--s1); }
/* THE DRUMMER'S SIX GROUPS, one open at a time, the standing answer pinned
   above them so you can always see the word you are on. */
.nu-wgroups{ display: flex; flex-direction: column; gap: var(--s2); }
.nu-groupbar{ display: flex; flex-wrap: wrap; gap: var(--s2); }
.nu-groupbtn{ min-block-size: var(--tap); padding-inline: .5ch;
  font: var(--btn-fw) 1rem/1.2 var(--sans, inherit); }
.nu-groupbtn[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill);
  border-color: var(--hand); }
.nu-wchips.nu-pinned{ background: transparent; border-style: dashed; }
/* A MOTIF CHIP CARRIES ITS PICTURE AND WHERE IT CAME FROM (§3). The preview is
   ui/preview.js's svg and the provenance is one small word under it — own,
   guest: <genre>, or hand — so two motifs are told apart by SHAPE before
   either name is read and neither is mistaken for the record's own. */
.nu-wchip{ display: inline-flex; align-items: center; gap: var(--s1);
  flex-wrap: wrap; justify-content: center; }
/* ...AND `display` ON A CHIP MEANS `[hidden]` HAS TO BE SAID AGAIN. The
   browser's own `[hidden] { display: none }` is a UA rule of the same
   specificity, so the line above WON and every one of the drummer's
   sixty-eight ops stayed on the screen with one group "open". The gate said
   ten of sixty-eight because it read the `hidden` PROPERTY — a check on the
   plan and not on the artifact, which is the failure this branch legislates
   against; it measures the rendered box now, and this is the rule that makes
   the measurement true. */
.nu-wchip[hidden]{ display: none; }
.nu-chipword{ flex: 0 0 auto; }
.nu-chipprov{ flex: 0 0 100%; font-size: .8rem; opacity: .8; }
/* A REFUSED CHIP PRINTS ITS REASON UNDER ITS WORD (2026-09-07). The same
   `flex: 0 0 100%` second line the provenance above uses, for the same button,
   because it is the same problem: a fact about this word that is not this
   word. The sheet's own ruling on where a reason may live is two thousand
   lines up (`grid-auto-rows` / `align-items: start`, "it costs its OWN chip's
   height and nothing else's") and this is that ruling in a flex row —
   `.nu-wchips` already wraps, so a taller chip pushes nothing sideways and
   leaves its neighbours at 44px.
   THE CAP IS SO A SENTENCE WRAPS INSTEAD OF STRETCHING THE CHIP. A flex item
   is as wide as its content, and "the row caps its holds at 6 steps, and that
   cap wins (capOf)" on one line is a chip wider than a phone; 18ch is the
   width `.nu-circ .nu-opt .nu-why`'s own 9em cap was written for, doubled for
   a strip that has the whole row rather than a wedge of a circle. */
.nu-wchip > .nu-why{ flex: 0 0 100%; max-inline-size: 18ch;
  text-align: center; white-space: normal; font-weight: 400; }
/* THE CORNER IS A CONTROL NOW — the whole record, where every table has a
   blank square. It wears the row head's own plate because it IS a head. */
.nu-corner{ font: 700 .62rem/1.3 var(--mono, monospace); text-transform: uppercase;
  letter-spacing: .04em; }
/* THE FOOTER: the record's own two rows, held apart from the sections by a
   rule rather than by a gap, because they are part of the same table and a gap
   would read as a second table. */
.nu-wordgrid tfoot .nu-footrow > th,
.nu-wordgrid tfoot .nu-footrow > td{ border-block-start: var(--bw-hard) solid var(--rule); }
/* ...AND ITS WORDS WRAP RATHER THAN WIDENING THE TABLE (2026-09-04). A footer
   row asks the RECORD its questions and the record has no voices, so its seven
   or three words are NOT columns: they sit in one spanning cell and lay out as
   a wrapping row of the same 44px word plates the body draws (ui/wordgrid.js,
   THE ROW'S OWN WORDS GO IN ONE SPANNING CELL). `9ch` is the basis and the
   grow/shrink do the rest: MEASURED, the seven master words are 111x44 two to
   a line at 320 and 390, and 139x44 on one line at 1280. `.nu-trimbtn`
   already carries the 100% inline-size and the 44px floor. */
/* ===== THE MIX ROW (TABLE.md §10a, 2026-09-07) =========================
   §10a: *"MIX is ALIGNED — one channel strip per voice column and the master
   in the corner."* It is the one special row that is NOT merged, and every
   rule below follows from that one word: a cell per column, so it needs no
   `--panew` line and no sticky button, and the row head is a LABEL rather than
   a control — the column this table freezes at the pane's left edge is 36px
   wide and has no room for a face.

   THE MASTER IS A MERGED ROW UNDER THE STRIPS, wearing the same
   `.nu-spheadcell` / `.nu-sphead` furniture TIME and RULES wear, for the same
   reason they wear it: it is a fact about the RECORD, so it has no column, and
   its line is pinned to the pane's left edge at `--panew` so it reads whole at
   320 while the strips scroll under it. Two other homes were built and
   measured first — in the mix row's HEAD (36px, the face rendered at 17px) and
   in its ADDER cell (547px and legible, at an x seven columns right of a 255px
   pane) — and both are written up at `grid.ts mixRow`. It does NOT freeze
   vertically: `stick()` walks `thead > tr` and this is in the `<tfoot>`, which
   is where a master strip belongs on a desk. */
/* (IT READ `border-block-end: 0` UNTIL 2026-09-06. The master was the middle
   of three merged rows in the `<tfoot>` and gave up its hairline to the
   PRODUCE row under it; it is a section of the RECORD panel now, one of seven
   lines that each end in the same hairline, so it keeps its own.) */
/* A SEAT'S CELL IS A BODY CELL WITH A LAMP UNDER IT. The lamp is the column
   head's own `.nu-scollamp`, built a second time (one node has one parent), so
   a player that is sounding lights at BOTH ends of its column — which is what
   an aligned row is for. */
.nu-mixcell{ position: relative; }
/* ...AND SINCE 2026-09-05 THE LAMP RIDES THE CELL'S EDGE (§13a.2: every
   special row is ONE LINE, `var(--tap)` tall). It was a 4px block under the
   fader's word with 2px of margin, and MEASURED at 390 that made the MIX row
   51px where every other row of this table is 44 plus its hairline. Laid on
   the cell's own bottom edge it says the same thing and takes no line — the
   same repair the column head's lamp took, for the same reason. */
.nu-mixcell > .nu-scollamp{ position: absolute; inset-block-end: 0;
  inset-inline: 0; inline-size: 100%; }
/* ...AND THE ROW'S OWN WORD IS A LINE OF THUMB, on a record with no players
   to draw cells for: measured 25.8px on the Silence record, which is the one
   row of this table a hand could not aim at. */
.nu-mixrow > th.nu-srowh{ min-block-size: var(--tap); }
.nu-mixrow > th.nu-srowh > .nu-srowname{ display: flex; align-items: center;
  min-block-size: var(--tap); padding-inline: var(--s3); }

/* ===== THE TWO ADDERS: THE TABLE'S OWN EDGES (2026-09-05) ==============
   Paul, looking at the table on staging: *"Move all the nav into the table, I
   should be able to add players without using the nav and sections too. I
   click band and all further operations are buttons around the table."*
   `+ line · + bass · + drums` stand in a head cell AFTER the last player and
   `+ section` in a row head UNDER the last section — the two places a
   spreadsheet has always put "one more". They wear the op button's own plate
   (44px block, half a character of side padding, the word and a hair) because
   they ARE ops; what makes them read as the edge rather than as a column is
   the dashed rule they hang off and the fact that their cell holds nothing
   else. MEASURED before this rule: adding a player cost 2 taps and both of
   them were on the nav stripe. */
/* ...AND SINCE 2026-09-05 THERE IS ONE OF THEM AT EACH EDGE (TABLE.md §13a.5).
   Paul, on the Silence record at 390: *"three adders (`+ line · + bass · +
   drums`) took more width than the three players they were offering to join,
   so the first player's head was cut to '…he ntl'."* MEASURED on v287 at 390:
   the adder column is 224px (`--addw: 22ch`) of a 364.4px pane and its head
   cell is 57.3px tall — a permanent shop window over a table you are trying to
   read. The offers are ONE CELL AT EACH EDGE now: a `+` at the end of the head
   row and a `+` row at the grid's foot, each `--tap`, and the three column
   widths go back to the players. They opened an ADD sheet for one afternoon;
   since §13e (Paul: *"Don't pop up an interface when I add a section or a
   voice. Just add it."*) each `+` IS its offer — `src/table/grid.ts plusBtn`
   carries the op's own `data-k` and fires it on the tap.
   WHAT STOOD HERE: `.nu-addbar` (the wrapping flex row), `.nu-addbtn` (the
   dashed 44px word plate and its `[disabled]`), the two `.nu-addhead` padding
   rules and `.nu-addrow`'s dashed top rule. */
/* THE HEADS ALIGN TO THE TOP, ALL OF THEM — kept, because a `+` cell and a
   two-line player head are still two different heights and a `middle`-aligned
   head would float down the middle of the taller one. */
.nu-wordgrid thead th{ vertical-align: top; }
.nu-wordgrid thead th.nu-plushead{ padding: 0; }
.nu-wordgrid tbody th.nu-plusrowh{ padding-inline: 0; }
/* THE `+` IS A MARK AND NOT A WORD PLATE, which is what makes it `--tap` wide
   instead of 22ch: DESIGN.md §2.15, every icon from ui/glyph.js with its
   `.nu-vh` word and its `data-say`. It is dashed because it is an EDGE — the
   one thing on this surface that is an invitation rather than a value. */
.nu-plusbtn{ display: flex; align-items: center; justify-content: center;
  inline-size: var(--tap); min-inline-size: var(--tap);
  min-block-size: var(--tap);
  border: var(--bw) dashed var(--rule); border-radius: var(--r1);
  background: transparent; box-shadow: none;
  font: var(--fw-body) 1.15rem/1 var(--sans, inherit); }
.nu-plusbtn:hover{ background: var(--hand-tint); }
.nu-plusbtn[aria-expanded="true"]{ background: var(--hand-tint); }
.nu-addcell{ padding: 0; }
/* (`.nu-footcells` / `.nu-footcell` STOOD HERE, to 2026-09-05. They were the
   wrapping row of 9ch word plates a `<tfoot>` row put in its spanning cell —
   the shape PERFORM wore, measured 92px and three lines deep at 390 on the
   Silence record. §13a.2 makes every special row ONE LINE, so PERFORM is a
   merged row with the same `.nu-spline` furniture MASTER and PRODUCE wear and
   its three words are its FACE; `grid.ts footRow`/`footCell` went with them.) */
.nu-perfrow > th.nu-spheadcell{ border-block-start: var(--bw) solid var(--rule); }

/* ===== THE SONIC SPREADSHEET (2026-09-05, TABLE.md §9a) ================
   Paul: *"I want the table to just re-use spreadsheet dynamics since users
   know them. Think of song composition as 'sonic spreadsheet'."*

   THE FORMULA BAR IS TWO PARTS AND ONE CONTROL. Its BODY is the vector, in the
   accordion, under the row the cell is in — because a cell here is eighteen
   fields and this page's two standing laws (menus never scroll inside
   themselves; cell menus insert BELOW the row) decide where that many words
   may go. Its HEAD is this strip: the ADDRESS of the selection, undo/redo and
   the two clipboard verbs. Nothing is drawn twice.
   ON A PHONE THE HEAD IS THE BOTTOM SHEET (§9a's own words). It sticks to the
   bottom of the viewport at ≤ 480px, which is where the thumb already is;
   above that it sits over the grid, which is where a formula bar has always
   been. Sticky and not fixed: it stays inside the pane's own stacking and
   cannot cover a control it was opened from (the flyout law). */
/* ===== THE PANE IS THE SCROLLPORT, ON BOTH AXES (2026-09-05) ===========
   Paul, using the table: *"I really like the way this table is working — we
   should have sticky headers for instruments and sections."*

   WHAT WAS TRUE BEFORE THIS RULE, MEASURED (TABLE.md 11b, T9s2 at 320/390/
   1280): the section heads held over 220px of horizontal pane scroll — they
   moved 1px, and the 1 is `border-spacing` — and the instrument heads
   declared `position: sticky` against a pane WITH ZERO VERTICAL SCROLL TO
   GIVE. `.nu-pane` sized to its content and THE PAGE was the vertical
   scrollport, so a hand scrolling down took the instruments with it. A
   declaration that cannot move is the failure §9d caught twice.

   THE DECISION, AND WHICH OF TWO LAWS GAVE. Making the column heads stick
   means making the pane the vertical scrollport, which puts an open SHEET
   inside a box that scrolls — and this page's standing law is *"menus never
   scroll inside themselves … vertical space is cheap and abundant"* (2026-08-
   16). The two are not the same sentence. That law is about a MENU with its
   own little scrollbar, opened over a page that could have shown it whole; a
   sheet here is a `<tr>` IN FLOW inside the table, and it scrolls with the
   grid it belongs to exactly as a spreadsheet's expanded row does. What a
   hand must never do is scroll a box inside a box; here there is one box, and
   it is the whole instrument. DESIGN.md 2 component 2 (*"sticky on its axis at
   every width, the corner pinned both ways"*) and 3 (*"the pane is the
   scrollport; heads stick"*) are the spec this satisfies.

   THE CAP IS THE WRAP'S, NOT THE PANE'S, and that is arithmetic and not
   taste. The brief says "100dvh minus --top-h minus --bar-h"; the pane is not
   the only thing between them. Measured on the rendered page at 320: the top
   strip ends at 55.2 (`body { padding-block-start: var(--top-h) }`), `.nu-pan`
   pays `var(--s4)` of padding above the wrap, `body` reserves
   `calc(var(--bar-h) + var(--s3))` below everything for the foot bar, and the
   FORMULA BAR is 106px tall inside the wrap at phone width. Cap the pane
   alone and the formula bar pushes 106px of page scroll straight back. So the
   WRAP takes the height between the two bands, the formula bar keeps its
   natural size inside it, and the pane is the flex child that takes what is
   left and scrolls. Measured after: documentElement.scrollHeight === its
   clientHeight at 320/390/1280 — the page does not scroll on the Band sheet,
   which is the other half of the shell's laws (no sideways page scroll, the
   bar fixed).

   `min-block-size: 0` ON THE PANE IS NOT DECORATION. A flex item's default
   `min-height: auto` is its content, so a table 3,000px tall would refuse to
   shrink and the cap would do nothing at all — the one line that turns a
   height cap into a scroller. */
/* ...AND SINCE 2026-09-05 THE WRAP HOLDS ONE CHILD (TABLE.md §13a.6). The
   arithmetic above was `100dvh - --top-h - --bar-h - --s3 - --s4`, and three
   of those four are gone: there is no top strip, the band's own panel pays no
   top padding, and the formula bar that made the wrap necessary is deleted.
   What is left is the screen minus the bar minus one step of air.
   MEASURED at 390 x 844 on Kingston 1969: the pane was 611px and is 788,
   against §13b's floor of `844 - 50.4 - 0 - 8` = 785.6. */
/* ...AND THE TOP STRIP IS BACK IN THE ARITHMETIC, 2026-09-06 (docs/NAV.md).
   The screen minus BOTH bands minus one step of air. Measured at 390 x 844 on
   Kingston 1969: the pane was 788px and is 744, against a floor of
   `844 - 44 - 50.4 - 8` = 741.6. */
.nu-sheetwrap{ display: flex; flex-direction: column; min-inline-size: 0;
  block-size: calc(100dvh - var(--top-h) - var(--bar-h) - var(--s2)); }
.nu-sheetwrap > .nu-pane[data-pane="table"]{ flex: 1 1 auto; min-block-size: 0;
  overflow: auto; overscroll-behavior: none; }
/* THE CELL SHEET'S FIRST LINE, WHICH IS WHAT `.nu-formula` BECAME. The address
   of the selection at the start, undo · redo · copy · paste at the end, drawn
   once, inside the sheet the selection opened — so the four verbs stand where
   the change was made and nothing holds the foot of the screen when there is
   no cell to spend them on. (`.nu-formula` was: a wrapping flex strip, sticky
   at the top of the wrap, `order: 2` and `inset-block-end: 0` at ≤480px — the
   "bottom sheet". Its two children, `.nu-fadr` and `.nu-fops`, are unchanged
   and are read by the same gates.) */
.nu-cellhead{ display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); }
/* ...AND IT IS THE CARD'S TITLE BAR FROM 2026-09-08 (TABLE.md §22). Three
   declarations came OFF this rule — `padding-block: var(--s2)`,
   `border-block-end` and `margin-block-end` — because all three drew the seam
   between this line and the sheet under it, and `.nu-modalhead` draws that
   seam now, once, for whichever of the six sheets is open. A head that kept
   its own rule under a header that has one is two lines where the eye needs
   none.
   IT IS THE HEADER'S `1 1 auto`, so the address takes the band and the × sits
   at the end of it — the same two ends `.nu-modalname` has on the other five
   sheets. `min-inline-size: 0` is what lets the address ellipsise instead of
   pushing the × off a phone. */
.nu-modalhead > .nu-cellhead{ flex: 1 1 auto; min-inline-size: 0; }
/* THE ADDER COLUMN IS AN EDGE, NOT A PLAYER. Under `table-layout: fixed` a
   `<col>` with no width takes an equal share, so the empty cell after the last
   player was as wide as a player — measured 1,280: a 100px column of nothing.
   It holds ONE mark since 2026-09-05 (§13a.5), so it is one thumb wide. */
.nu-sheetgrid col.nu-addcol{ inline-size: var(--tap); }
/* THE ADDRESS IS A 44px LINE like everything else in this pane, and that is
   not decoration: T7 measures every control the inventory files by its rendered
   height at 320, and an address a thumb cannot land on is an address a thumb
   cannot read either. */
.nu-fadr{ flex: 1 1 12ch; min-inline-size: 0; min-block-size: var(--tap);
  display: flex; align-items: center;
  font: var(--fw-label) 1rem/1.25 var(--sans, inherit); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-fops{ display: flex; flex-wrap: wrap; gap: 3px; flex: 0 1 auto; }
@media (max-width: 480px){
  /* (THE BOTTOM SHEET STOOD HERE. `.nu-formula` took `order: 2` and
     `inset-block-end: 0` at ≤480px and rode the foot of the pane, which is
     §9a's "the formula bar is the bottom sheet" — 105.8px of a 844px phone,
     measured, standing at rest whether or not a cell was selected. §13a.6
     deletes the bar; the address goes inside the cell sheet, where the phone
     pays for it only while a cell is open.) */
  .nu-fadr{ flex: 1 1 100%; } }

/* FROZEN HEADERS (§9a). The head row and the head column stay while the rest
   scrolls — a spreadsheet's oldest affordance and the one that makes a wide
   band readable at 320. `.nu-pane` is the scroller and it always has been; the
   sticky heads ride it. The `<th>`s carry the paper behind them, or the cells
   would show through as they pass under.

   `.nu-sheetgrid` AND NOT `.nu-wordgrid`, which is the class four surfaces
   share: the board's trim grid is drawn by the same component and freezing its
   heads is a decision about the board, taken on the board. The sonic
   spreadsheet says which table it is.

   THE CORNER IS FROZEN IN BOTH DIRECTIONS, and that is not symmetry for its own
   sake: test/shell.js A8 takes the pane's FIRST `<th>` — which is the corner —
   asks whether it declares itself sticky, and then scrolls the pane 200px and
   measures how far it moved. A corner pinned only to the top declares
   stickiness and then slides the full scroll under it, which is exactly what
   A8 exists to catch, and did, within the minute. A spreadsheet's corner cell
   has never scrolled in either direction. */
.nu-sheetgrid thead th{ position: sticky; inset-block-start: 0; z-index: 2;
  background: var(--paper); }
/* 3px AND NOT 0, AND THE 3px IS `border-spacing`. `.nu-trims` is
   `border-collapse: separate; border-spacing: 3px`, so the head column's
   natural left edge is 3px inside the pane's content box — and a cell pinned
   at 0 SNAPS those 3px the moment it sticks. Measured by test/shell.js A8,
   which scrolls the pane 200px and allows 2: "moved 4px over a 200px scroll",
   three times, at three widths. Pinning it where it already stands makes the
   freeze invisible, which is the only way a freeze is right. */
.nu-sheetgrid tbody th.nu-srowh, .nu-sheetgrid tfoot th.nu-srowh,
.nu-sheetgrid tbody th.nu-plusrowh{ position: sticky; inset-inline-start: 3px;
  z-index: 1; background: var(--paper); }
.nu-sheetgrid thead th:first-child{ inset-inline-start: 3px; z-index: 4; }

/* ===== THE SPECIAL ROWS: TIME, RULES (TABLE.md §10a, 2026-09-06) =========
   Paul: *"we could integrate rules into a special row, time + key into a
   special row … a real mobile app now with everything in the table and the nav
   space reclaimed."*

   A ROW OF THE SAME SHEET, MERGED ACROSS THE COLUMNS. One `<th colspan>` per
   row, so there is nothing to align to a voice — the tempo is not the bass's
   tempo — and the whole line is ONE button, which is what makes the row a
   44px target across the full width of a 320px screen rather than a word with
   a chevron beside it.

   ITS OFFSET IS MEASURED, NOT DECLARED. `thead th` above pins every head at
   `inset-block-start: 0`, which is right for a head of one row and wrong for a
   head of three: `src/table/grid.ts stick()` walks `thead > tr` after every
   render and writes each row's own offset as an inline style, so TIME sits at
   0, RULES under it and the column heads under both — a stack, frozen
   together, at whatever height the faces actually take at this width.

   THE FACE ELLIPSISES AND NEVER WRAPS. "83 a minute · four · G natural minor"
   at 320 is longer than the screen on some records, and a header row whose
   height depends on the genre's key name is a header row that jumps as you
   move around the catalogue. The whole line is in the button's accessible
   name, so nothing is lost to a reader; the hold explainer is the row itself. */
.nu-sheetgrid th.nu-spheadcell{ padding: 0; text-align: start;
  z-index: 5; border-block-end: var(--bw) solid var(--rule); }
/* AND THE LINE ITSELF IS PINNED TO THE PANE'S LEFT EDGE. The `<th>` cannot be:
   it spans every column, so its width IS its containing block's and a sticky
   box has nowhere to travel — measured by test/shell.js A8, which scrolled the
   Band pane 200px and watched the merged row's cell slide 88 of them. The
   BUTTON is `--panew` wide inside a cell that is the whole table, so its sticky
   range is exactly the pane's horizontal scroll, and the face stays where a
   hand can read it while the grid moves under it. */
/* ...AND IT IS THE LINE THAT IS PINNED, NOT THE BUTTON (2026-09-05, §13a.2).
   The `<th>` cannot be pinned sideways — it spans every column, so its width
   IS its containing block's and a sticky box has nowhere to travel (measured
   by test/shell.js A8, which scrolled the Band pane 200px and watched the
   merged cell slide 88 of them). The BUTTON carried the pin until this round;
   it carries a LAMP beside it on the PHRASES row, and a lamp under the button
   made that row two lines tall — 61.1px against TIME's 45, measured at 390 on
   v287, which is exactly the *"jammed up"* Paul photographed. So the pin and
   the `--panew` width belong to the LINE, the button takes the give inside it,
   and the lamp stands at its end on the same 44px. */
.nu-spline{ display: flex; flex-direction: row; align-items: center;
  inline-size: var(--panew, 100%); max-inline-size: 100%;
  position: sticky; inset-inline-start: 0;
  min-block-size: var(--tap); background: var(--paper); }
/* ...AND THE SECTIONS DISCLOSURE WEARS THE SAME BOX AND NOT THE SAME CLASS
   (2026-09-05, TABLE.md §13f). It is a button the width of a special row's
   line, so it takes `.nu-sphead`'s box; it is NOT a sheet's head, so it must
   not answer `[aria-expanded="true"].nu-sphead` — the selector three gates and
   the page's own "shut whatever is open" gesture use. MEASURED the hour it
   landed: sharing the class made every `shutAll()` FOLD THE GRID, and eleven
   checks about the mix row, the section column and the frozen stack went red
   on a table that was not on the glass. One box, two classes. */
.nu-sphead, .nu-labelbtn{ display: flex; flex: 1 1 auto; min-inline-size: 0;
  align-items: center;
  gap: var(--s3); min-block-size: var(--tap);
  padding: var(--s2) var(--s3); text-align: start;
  background: transparent; color: var(--ink);
  border: 0; border-radius: var(--r0); box-shadow: none; }
/* NO PLATE, NO TINT, AT REST OR OPEN (§13a.2: *"a hairline under, no plate
   tint"*). The open state is the row's `aria-expanded` and the sheet standing
   under it, which is a fact a hand can see without a wash of colour behind
   forty characters of type. */
.nu-spword{ flex: 0 0 auto;
  font: var(--fw-block) var(--t3)/1.2 var(--sans, inherit);
  text-transform: uppercase; letter-spacing: .06em; }
/* THE WORD LEFT, THE SENTENCE RIGHT (§13a.2), truncated with an ellipsis and
   never wrapped — a header row whose height depends on the genre's key name is
   a header row that jumps as you move around the catalogue. */
/* THE × AT THE LINE'S RIGHT END, WHILE THE ROW IS OPEN (§13a.3: *"the header
   carries the × at its right end"*). It is a target of its own and not the
   head wearing a second meaning — the same argument `.nu-top` made for its own
   pair, kept when that strip was deleted and the × went to the header of the
   thing it closes. */
.nu-spclose{ flex: 0 0 auto; min-inline-size: var(--tap);
  min-block-size: var(--tap); display: flex; align-items: center;
  justify-content: center; padding: 0;
  background: transparent; border-color: transparent; box-shadow: none; }
.nu-spclose:hover{ background: var(--flag-tint); }
.nu-spface{ flex: 1 1 auto; min-inline-size: 0; text-align: end;
  font: var(--num-fw) var(--t3)/1.2 var(--sans, inherit); color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ===== THE RECORD ROW AND ITS PANEL (2026-09-06, TABLE.md §14) =========
   *"The page is sorted by age, not by scope."* The seven record-scope
   surfaces — RULES · TIME · CHORDS · MOTIFS · MASTER · PRODUCE · PERFORMANCE
   — were four rows above the grid and three below it; they are ONE LINE at
   the top now, `THE RECORD`, whose face is the time row's own tempo · meter ·
   key, and whose tap discloses the seven as its sections.

   IT WEARS `.nu-labelbtn`, WHICH IS THE SECTIONS DISCLOSURE'S OWN BOX AND NOT
   `.nu-sphead`'s CLASS, for the reason stated where that class is defined: a
   disclosure must not answer `[aria-expanded="true"].nu-sphead`, the selector
   three gates and this page's own "shut whatever is open" gesture use. Sharing
   the class would make every `shutAll()` fold the record away.

   THE SEVEN ROWS INSIDE IT ARE THE ROWS THEY ALWAYS WERE — `.nu-sprow`, the
   merged `<th colspan>`, the `.nu-spline` pinned to the pane's left edge, the
   `.nu-sphead` with its word and its face, the × while it is open — plus one
   class saying which panel they belong to and one hairline down their inside
   edge saying it on the glass. `hidden` is what closes them, which is the
   attribute the platform already means by "not here". */
/* ...AND THE INDENT IS A HAIRLINE AND NOT A PADDING, which is a measurement
   and not a taste. The first drawing indented the line by `--s2`; a `.nu-spline`
   is `--panew` wide with `box-sizing: border-box`, so the padding does not
   move the LINE — it takes 3.47px off the BUTTON inside it, and the button is
   what T10m measures against the pane ("the row's line is as wide as what a
   hand can see"). MEASURED at 1280: the line 1242 either way, the button
   1242.1 without the padding and 1238.6 with it, against a check that allows
   12px of a 1254px pane. A decoration that costs a row 3.5px of its own
   readable width when a hairline says the same thing for nothing is the wrong
   half of the trade. */
.nu-sheetgrid tr.nu-scoperow > th.nu-spheadcell > .nu-spline{
  box-shadow: inset var(--bw-hard) 0 0 var(--rule); }
/* THE RECORD'S OWN LINE IS THE PANEL'S HEADER AND READS AS ONE: the word at
   full strength, the face beside it, and — while the panel is open — the same
   hairline the sections under it hang off. */
.nu-sheetgrid tr.nu-recrow > th.nu-spheadcell{
  border-block-end: var(--bw) solid var(--rule); }
/* ...AND SINCE 2026-09-06 THE FACE IS THE LINE (§15a). `THE RECORD` was a
   label over a line that already read `84 BPM · 4/4 · G♯ natural minor`, and
   Paul deleted it. With the word gone the face has no word to sit opposite, so
   it reads from the START at full ink: it is not a subordinate value beside a
   heading any more, it is what the row says. `.nu-spface`'s ellipsis and its
   one-line rule stay — a header whose height depends on the genre's key name
   is a header that jumps as you move around the catalogue. */
.nu-sheetgrid tr.nu-recrow .nu-spface{ text-align: start; color: var(--ink); }
/* THE TABLE'S OWN OPTIONS AT THE LINE'S END (§15a): the `--tap` square
   `.nu-spclose` already is at the other end of an open row, wearing the gear
   `ui/glyph.js` spells settings with. It is a SIBLING of the head button, for
   the reason the × is one — a button inside a button is not a control — and it
   hovers in `--hand-tint` rather than the ×'s `--flag-tint`, because it opens
   something and does not close it. */
.nu-spopts:hover{ background: var(--hand-tint); }

/* ===== THE GRID'S OWN HEADER IS ITS OWN HEAD (2026-09-06, §15a) =========
   A ROW STOOD HERE (`.nu-gridlabel`, §13e/§13f): the word SECTIONS left, the
   count right, one `--tap` line above the column heads, a disclosure since
   *"Sections should collapse when I touch it."* Paul deleted it — *"Get rid of
   the words 'the record' and the Section header entirely — we can make
   room."* — and its two jobs went one row down, into the head of the column it
   was labelling. The rules that made it a label (`position: static` so it
   never took the pane's one pin, and an escape from `.nu-trims thead th`'s
   uppercase for its count) are deleted with it: a stylesheet keeping a
   selector for a row nobody draws is the CSS half of an orphan key.

   ===== THE FROZEN COLUMN'S HEAD, WHICH NOW SAYS HOW MANY ================
   The corner is `--headw`'s floor on a phone — 8ch, MEASURED at 81.4px, 73.4
   inside the cell's padding and 66.7 inside the button's — and it may not
   grow: at 390 the players are 70px apart and 364.4 − 81.4 is exactly four of
   them. So the count goes UNDER the word rather than beside it, and it WRAPS
   rather than ellipsising, because `13 sections · 76 bars` is 121.3px on one
   line and 63.5 + 40.4 on two. Three lines of 12.48px is 37.4px, inside the
   button's own 44px floor: it costs the head band nothing, measured. */
#pan-band .nu-wordgrid thead th.nu-cornerh > .nu-corner{
  display: flex; flex-direction: column; justify-content: center;
  align-items: stretch; gap: 0; text-align: start; overflow: hidden; }
#pan-band .nu-wordgrid .nu-cornerword{
  font: var(--fw-block) var(--t0)/1.3 var(--mono, monospace);
  text-transform: uppercase; letter-spacing: .04em; }
/* THE COUNT IS THE QUIET HALF — the register the label's own face wore
   (`.nu-spface`: `--dim`, no uppercase, no tracking) — so the head reads as a
   name with a number under it and not as two headings. */
#pan-band .nu-wordgrid .nu-seccount{
  font: var(--num-fw) var(--t0)/1.3 var(--sans, inherit); color: var(--dim);
  text-transform: none; letter-spacing: 0; white-space: normal;
  overflow-wrap: anywhere; }
/* ...AND A FOLDED TABLE IS THE PANE'S OWN WIDTH. With the grid folded the
   player heads are not rendered and the body and the mix row are `hidden`, so
   the table has ONE column — but its `inline-size` is still worked out from
   `--cols`, which would leave 625px of scrollable width on a 364px pane with
   nothing in it to scroll to. That slack is what a hand drags into and feels
   as the wobble (§15a); `--panew` is what `stick()` measured the pane to be. */
#pan-band .nu-wordgrid.is-folded{ inline-size: var(--panew, 100%); }
#pan-band .nu-wordgrid.is-folded > colgroup > col:first-child{
  inline-size: auto; }

/* ===== THE PLAYING SECTION, LIT WHOLE (2026-09-05, TABLE.md §13f) ======
   Paul: *"Really light up the sections as you move through them — not just a
   tiny halo around the number."* The row head and every cell of the sounding
   section take a ground in `--clock` — DESIGN.md §1's token for *"scheduled"*,
   which is what a playhead is — strong enough to find from arm's length on a
   phone, with the ink left alone so the words on it stay the words. The mix is
   ONE custom property so the three selectors that spend it cannot drift and so
   dark mode gets it for free: `--panel` is the ground either way.
   THE `<mark>` ROUND THE NUMBER STAYS — it is the same fact at the reading
   distance of a finger, and it is what `markForm` has always written.
   (The colours are id-scoped, down beside the ground they have to beat: see
   `#pan-band .nu-wordgrid tbody tr.is-playing` below.) */
/* `--lit` IS 65% AND WAS 85%, AND FOR AN HOUR IT WAS 22% (2026-09-08, §24).
   Paul: *"The orange is a strange highlight color."*

   22% WAS WRONG AND THE GATE SAID SO. `--clock-tint`'s own strength is the
   number this page uses where the clock must be visible UNDER type, and
   reaching for it here looked right — but DESIGN.md component 11 puts a FLOOR
   on this particular ground: the playing section is lit *"≥ 3:1 against a
   resting row"*, which is Paul's other sentence about it (*"Really light up
   the sections as you move through them"*) written as a number.
   COMPUTED AGAINST `--ground` (22,25,26), which is what a resting row is:
     22% -> 1.55:1     50% -> 2.50     65% -> 3.25 ✔
     40% -> 2.10       60% -> 2.98     85% -> 4.53 (what it was)
   So 22% was a third of the light the law requires. 65% is the calmest value
   that clears it, and it is 20 points quieter than the 85% Paul was looking
   at when he called it strange.
   AND THE STRENGTH WAS NEVER THE WHOLE COMPLAINT, which is why this can come
   back up without undoing the round: what made the old row shout was that it
   was SEGMENTED (five separate rectangles, `border-spacing`) and that every
   section number on the page stood in the same red at rest. Both are fixed
   above and in the `[data-live]` rule; this one number is the part that had a
   law under it. */
.nu-sheetgrid tbody tr.is-playing{ --lit: color-mix(in srgb, var(--clock) 65%, var(--panel)); }
.nu-sheetgrid tbody tr.is-playing > th .nu-srowname{ font-weight: var(--fw-block); }

/* ===== A FOLDED GRID (2026-09-05, TABLE.md §13f) =======================
   Paul: *"Sections should collapse when I touch it."* The rows the disclosure
   folds carry `hidden`, which is the attribute the platform already means by
   "not here" — off the glass AND out of the accessibility tree. The UA's own
   `[hidden]{display:none}` is a `display` declaration and every table part
   here declares its own display through its element type, so this is stated
   rather than assumed: a `<tbody hidden>` that a later rule revives is a grid
   that folds on one browser and not another. */
.nu-sheetgrid tbody[hidden], .nu-sheetgrid tr[hidden]{ display: none; }

/* ===== THE MOTIFS ROW AND ITS BANK (2026-09-08, TABLE.md §10b step 4) ====
   §10a: *"MOTIFS is the bank across the top with previews and provenance, and
   tapping a motif points the SELECTED cell at it."* The ROW is a merged row
   like TIME and RULES and wears their furniture unchanged; what is new is the
   LAMP under its face and the BANK inside its sheet.

   THE LAMP IS STICKY FOR THE FACE'S OWN REASON. `.nu-sphead` is `--panew` wide
   and pinned to the pane's left edge, because a `<th colspan>` spanning nine
   players is nine players wide and its last word would be off the side of a
   phone. A lamp that was NOT pinned the same way would be the one thing in the
   row you had to scroll sideways to see — so it is a sticky block of the same
   width, directly under the button, carrying the sounding motif's NAME (the
   head says WHICH, because with the row shut a bare dot would say only THAT).
   It is a SIBLING of the button and never a child: the clock writes inside
   `[data-live]` and a control inside one is what test/motif-frozen A1 forbids. */
/* ...AND ITS LINE IS RESERVED WHETHER OR NOT THE CLOCK HAS WRITTEN IN IT, and
   that is a LAW and not a preference. It read `:empty { display: none }` for
   one afternoon and test/bench.test.js B6 caught it inside the hour: this row
   is in a `<thead>` whose offsets `src/table/grid.ts stick()` MEASURES after
   every render, so a lamp that appeared when the record started playing made
   the MOTIFS row 16px taller and moved `inset-block-start` on the column heads
   from 138px to 154.109px — an inline style, outside `[data-live]`, written
   because the clock wrote. "Playback mutates nothing outside [data-live]" is
   the page's oldest law and a lamp that changes the height of the row it is in
   breaks it two elements away. So the line is always there and always one line
   tall (`nowrap` and an ellipsis, so a record with four motifs sounding cannot
   grow it either), and the room is reserved before the thing arrives — the
   same discipline `staffRoom` uses for a staff that engraves on a promise.
   AND IT IS `block-size`, NOT `min-block-size`: measured, a `min` of 1.2em on
   an EMPTY span and one line of text in it are not the same height (151.812px
   against 154.109px at the column heads — an empty inline box has no strut of
   its own), so the reservation has to be exact. `content-box` keeps the
   padding out of the arithmetic. */
/* ...AND SINCE 2026-09-05 IT IS INSIDE THE ROW'S OWN LINE (§13a.2: *"no lamp
   on a second line — the MOTIF lamp draws inside the PHRASES row's own line or
   not at all"*). It was a sticky BLOCK under the button, `--panew` wide, with
   its own reserved 1.2em: correct about the sideways pin, and it made the one
   row carrying a lamp 16.1px taller than its two siblings. `.nu-spline` owns
   the pin now, so the lamp is a flex item at the end of the line and its
   reservation costs nothing — the line is 44px of thumb whether the clock has
   written in it or not, which is a stronger form of the law the block above
   was protecting (test/bench.test.js B6: a lamp that appears may not move a
   frozen head). It is still a SIBLING of the button and never a child. */
.nu-motlamp{ display: block; flex: 0 1 auto; min-inline-size: 0;
  max-inline-size: 45%;
  block-size: 1.2em; box-sizing: content-box;
  padding-inline: var(--s3);
  font: var(--num-fw) var(--t1)/1.2 var(--sans, inherit);
  color: var(--clock); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }

/* THE SHEET, AND THE ONE THING IT ASKS OF ITS CONTENTS: no minimum width.
   Everything in it — a bank row, a step grid, a staff — is inside the table's
   own horizontal scroll, and a child that refuses to shrink would make the
   grid itself wider than the record. */
.nu-motifsheet{ inline-size: 100%; min-inline-size: 0; }

/* A BANK ROW IS A PICTURE, A NAME, A PROVENANCE WORD, A LAMP, AN `open`, AND
   WHO READS IT — in that order, down the row, because that is the order a hand
   asks them in. The PICTURE is full width and 28px tall: it is the same
   ui/preview.js drawing the cell sheet's motif options wear, and at this width
   a sixteen-step tune is legible as a shape without being a second staff. */
.nu-bank{ display: flex; flex-direction: column; gap: var(--s1); }
.nu-bankrow{ padding-block: var(--s2);
  border-block-start: var(--bw) solid var(--rule); }
.nu-bankrow:first-child{ border-block-start: 0; }
.nu-bankpic{ display: block; inline-size: 100%; }
.nu-bankpic svg{ display: block; inline-size: 100%; block-size: 28px; }
.nu-bankhead{ display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s2); margin-block: var(--s1); }
/* THE NAME IS THE WRITE, so it is the row's biggest target and it takes the
   line: tapping it points the selected cell at this motif, and with no cell
   selected it ARMS the next tap and says so with `aria-pressed`. */
.nu-bankname{ flex: 1 1 auto; min-inline-size: 0; min-block-size: var(--tap);
  display: inline-flex; align-items: baseline; gap: var(--s2);
  text-align: start; }
.nu-bankname[aria-pressed="true"]{ background: var(--hand-tint); }
.nu-bankword{ font: var(--fw-block) var(--t3)/1.2 var(--sans, inherit); }
.nu-bankprov{ color: var(--dim); font-size: var(--t1); }
.nu-bankopen{ flex: 0 0 auto; min-block-size: var(--tap); }
/* THE ROW'S LAMP IS THE `<i>` THAT COMES AND GOES — the same shape a player's
   lamp in a column head is, and for the same reason: the name is already the
   button beside it, so the lamp only has to say YES. */
.nu-banklamp{ flex: 0 0 auto; display: block;
  inline-size: 10px; block-size: 10px; }
.nu-banklamp > i{ display: block; inline-size: 10px; block-size: 10px;
  border-radius: 50%; background: var(--clock); }
.nu-bankadd, .nu-bankback{ margin-block: var(--s3); }
.nu-bankadd button, .nu-bankback button{ min-block-size: var(--tap); }
/* THE FOURTEEN TRANSFORMS, back under the tune they rewrite: `.nu-tf-row`'s
   own rules already give each mark its 44px floor and the row its wrap, so
   what is left is the face over the word, which is how the stripe drew them
   and how the two bands drew them before that. */
.nu-tf-row .nu-tfface{ font-style: normal; }

/* THE PRODUCE ROW (§10b step 5) is the master's own shape one line further
   down — a merged `<th colspan>` in the `<tfoot>` wearing `.nu-sphead`, its
   line pinned to the pane's left edge so the producer's last sentence reads
   whole at 320 while the strips scroll under it. It takes the rule back that
   `.nu-masterrow` gives up, because it is the last row before performance. */
.nu-prodrow > th.nu-spheadcell{ border-block-start: var(--bw) solid var(--rule); }
/* THE BIG TEMPO AND THE ROW OF NINE MARKS ARE SHEET ROWS NOW, not a panel's
   first two lines, so each takes the width of the row it is in and the marks
   wrap at 320 instead of scrolling the pane sideways. */
.nu-timebpm{ display: flex; flex-direction: column; inline-size: 100%;
  min-inline-size: 0; }
.nu-timebpm .nu-field{ margin: 0; }
.nu-timekey, .nu-changes, .nu-rulesheet, .nu-timemeter{ inline-size: 100%;
  min-inline-size: 0; }
/* THE SIGNATURE'S TWO NUMBERS (2026-09-05, the any-meter round). One
   component per number — the typed field over its slider, DESIGN.md 8 — and
   the two stacked rather than side by side, because at 320px two sliders on
   one line are 90px each and a slider narrower than a thumb is a control you
   cannot land on. The numbers are `--t4` and not `--t5`: the TEMPO is the
   number this row is about, and two displays of equal weight would be two
   headlines. */
.nu-timesigpart{ display: flex; flex-direction: column; min-inline-size: 0; }
/* the question over the pair, the slider and its typed number side by side —
   the slider takes the room and the number takes what a two-digit figure
   needs plus the .5ch either side a control gets over its word. */
.nu-timesigpart label{ display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); }
.nu-timesigpart .nu-w{ flex: 1 0 100%; }
.nu-timesigpart input[type=range]{ flex: 1 1 8em; min-inline-size: 0; }
.nu-timemeter{ display: flex; flex-direction: column; gap: var(--s3); }
.nu-timemeter .nu-field{ margin: 0; }
.nu-timemeter .nu-hint{ margin: 0; font-variant-numeric: tabular-nums; }
.nu-signum{ inline-size: 4ch; min-inline-size: 4ch; box-sizing: border-box;
  text-align: center;
  min-block-size: var(--tap); padding: 0 var(--s1);
  font-size: var(--t4); font-weight: var(--fw-block); line-height: 1;
  font-variant-numeric: tabular-nums; font-family: inherit;
  color: var(--ink); background: transparent;
  border: var(--bw) solid transparent; border-radius: var(--r1);
  -moz-appearance: textfield; appearance: textfield; }
.nu-signum::-webkit-outer-spin-button,
.nu-signum::-webkit-inner-spin-button{ -webkit-appearance: none; margin: 0; }
.nu-signum:focus-visible{ border-color: var(--hand); outline: none; }
.nu-vsheet .nu-taprow{ display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); margin: 0; }
/* THE RULES ROW'S SHEET IS `ui/rules.js`'s OWN PANEL, seated. It kept its
   `.nu-ax` chassis — one owner for what a block of questions looks like — and
   what it loses inside a table cell is the panel's outer air, which the sheet
   row already pays. */
.nu-rulesheet > .nu-ax{ padding: 0; margin: 0; }
/* THE NAME PLATE IS THE SHEET'S FIRST LINE, so it is a line: `test/table-
   inventory.json` files `rules.name` as a control the row must offer at 320,
   and T7 measures every filed reach at 44px. It was 40 inside the row — the
   panel's own `<h3>` rhythm, which was right under a panel heading and is one
   line short under a row's word. */
.nu-rulesheet .nu-ruleplate{ min-block-size: var(--tap);
  display: flex; flex-direction: column; justify-content: center; }
/* ...AND A SEATED MENU FILLS ITS SHEET ROW. `menuWide` hands the widget over
   without its `<p class="nu-sel">`, so the row's own label is the question and
   this is the answer, at the row's full width. */
.nu-seatmenu{ display: flex; flex-direction: column; gap: var(--s2);
  inline-size: 100%; min-inline-size: 0; }

/* THE SELECTION, AND THE RANGE. One cell is selected; a shift-arrow or a
   shift-tap makes a rectangle of them. The selected cell wears the hand's own
   cobalt ring — the paint this page has used for "where your hand is" since
   2026-08-27 — and the rest of the rectangle wears its tint, so the anchor is
   never lost inside its own range. */
.nu-wcell.is-sel{ outline: var(--bw-hard) solid var(--hand);
  outline-offset: -2px; }
.nu-wordgrid td.is-inrange{ background: var(--hand-tint); border-radius: 3px; }

/* ---- THE TABLE IS A SET OF MARKS (TABLE.md §11, 2026-09-05) -----------
   Paul: *"When you redesign use more icons. Ideally the table is a large set
   of icons."* Every cell, every head and every seat draws ui/glyph.js's own
   three-part face — the glyph, an optional number, and the WORD, which is
   `.nu-vh` (hidden, in the accessible name) where the mark carries it and
   `.nu-w` (visible) where the value is a proper noun no picture can say.

   THE MARK IS THE SIZE OF THE ROW, NOT OF THE TYPE. 1.15rem against the
   cell's .72rem word: a picture has to be READ at arm's length across eighty
   cells, and a glyph set at the word's own size is a smudge. It stays inside
   the 44px the cell already spends.

   BOLD AND QUIET REACH THE MARK TOO, which is §11's own law and the reason
   this is a rule and not a default: written is the ink at full strength,
   inherited is the same .78 the word wears — so a column of dealt cells
   recedes as a picture exactly as it did as a word. */
.nu-cellword .nu-ic{ inline-size: 100%; min-inline-size: 0; gap: var(--s1);
  justify-content: center; }
.nu-cellword .nu-g{ font-size: 1.15rem; flex: 0 0 auto; }
.nu-cellword .nu-w{ overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; min-inline-size: 0; }
.nu-cellword.is-derived .nu-g{ opacity: .78; }
/* ...AND ON THE HEADS, WHERE THE NAME IS THE WORD. A player is called `stab`
   and a section is called `verse`; the mark says WHAT KIND of thing that name
   is, and the name stays visible beside it. Smaller here than in a cell — a
   head already carries two lines of type and the mark is the second voice in
   it, not the first. */
.nu-colbtn .nu-g, .nu-rowjump .nu-g{ font-size: 1rem; margin-inline-end: .18em;
  opacity: .8; }
.nu-rowjump .nu-g{ display: inline; }

/* ---- A NUMBER IS A SLIDER AND A BOX (TABLE.md §11, 2026-09-05) --------
   Paul: *"When you redesign think sliders and other UI for data entry."* One
   address, two doors: the RANGE for a thumb and the BOX for a keyboard or an
   exact value, and the unit printed after them. Both are 44px, which is the
   floor this page does not spend.
   THE BOX IS DIM WHILE NOTHING IS WRITTEN — the same dim-is-derived reading
   the cells make: what the slider shows before you touch it is what the record
   deals, not what you said. */
.nu-numrow{ display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s3); }
/* THE TROUGH, THE LINE AND THE CAP ARE THE PAGE'S OWN (`input[type=range]`
   near the top of this file: `--sl-trough`, `--sl-grab`, `--sl-cap`, the
   hand's fill, the ink's grip). Nothing is restated here — this row only says
   how much of the line the slider takes and that a thumb owns the gesture.
   (`accent-color` stood here for one edit and is gone: it repaints the native
   widget the page has already drawn by hand, which is two answers to one
   question.) */
.nu-numslide{ flex: 1 1 8rem; min-inline-size: 6rem; block-size: var(--tap);
  touch-action: none; }
.nu-numbox{ flex: 0 0 auto; inline-size: 5.5ch; min-block-size: var(--tap);
  font: var(--fw-label) var(--t3)/1.2 var(--mono, monospace); text-align: end;
  border: var(--bw) solid var(--rule); border-radius: var(--r2);
  background: var(--panel); color: var(--ink); padding-inline: var(--s1); }
/* DEFAULT IS QUIET (DESIGN.md §2's `derived` state, and its §4 word). */
.nu-numbox.is-derived{ font-weight: var(--fw-body); color: var(--dim); }
.nu-numunit{ flex: 0 0 auto; color: var(--dim); }

/* ================== THE ENVELOPE EDITOR (TABLE.md §11) =================
   Paul, after the AUX spike (2026-09-05): *"Don't do aux. keep our stuff but
   make it less chunky and more stylish. Make an Adsr and envelope editor
   though and use that for samples etc."*

   `nukernel/src/envelope/` -> `nukernel/ui/envelope.js` draws it; this is
   everything it looks like. It is the FIRST of the plate-handles-curve family
   (§11: the EQ curve, the XY pad, the tempo map, the automation lanes, the
   stereo field), so every rule below is written to be the family's and not one
   widget's — the plate, the curve, the handle and the printed value.

   THREE THINGS THE SPIKE MEASURED THAT ARE LOAD-BEARING HERE:
     · `touch-action: none` on the plate AND on the handle. Without it the
       browser claims the drag for a scroll and hands the widget a
       non-cancelable touchstart; measured on AUX, the page scrolled 400 -> 246
       under a fader drag that should have moved a fader.
     · A HANDLE IS 44px AND ITS CENTRE IS CLAMPED INSIDE THE PLATE. A 44px
       handle sitting at the plate's edge overhangs by 22 and the page scrolls
       sideways — measured. The component's own arithmetic keeps the centre in
       [22, w-22]; this stylesheet must not add a margin that undoes it.
     · THE MARK IS NOT THE TARGET. A 44px transparent button with an 11px dot
       drawn in the middle of it: `--tap` for the thumb, a dot for the eye. */
.nu-env{ display: block; inline-size: 100%; min-inline-size: 0; }
.nu-envplate{ position: relative; display: block; inline-size: 100%;
  block-size: var(--env-h); touch-action: none; overflow: hidden;
  border-radius: var(--r0); background: var(--well); }
.nu-envsvg{ display: block; inline-size: 100%; block-size: var(--env-h); }
/* THE ZERO LINE, so a release that lands short of silence is visible as
   landing short of it. */
.nu-envbase{ stroke: var(--rule); stroke-width: var(--bw); opacity: .45; }
/* THE CURVE IS THE CONTROL. Two pixels of the hand's own cobalt — the paint
   this page has meant "where your hand is" by since 2026-08-27 — because what
   is drawn here IS the thing being set, unlike every other line on the page. */
.nu-envcurve{ fill: none; stroke: var(--hand); stroke-width: var(--bw-hard);
  stroke-linejoin: round; stroke-linecap: round; }
/* ...AND THE GHOST IS WHAT IT INHERITS. §2's dim-is-derived law, in a curve:
   drawn only where a hand has written something, so "what I changed it from"
   is on the plate and not in a memory. */
.nu-envghost{ fill: none; stroke: var(--dim); stroke-width: var(--bw);
  stroke-dasharray: 3 3; opacity: .6; }
.nu-envh{ position: absolute; inline-size: var(--tap); block-size: var(--tap);
  padding: 0; border: 0; border-radius: 50%; background: transparent;
  touch-action: none; cursor: grab; display: flex;
  align-items: center; justify-content: center; }
.nu-envh:active{ cursor: grabbing; }
.nu-envh:hover{ background: transparent; }
.nu-envdot{ display: block; inline-size: var(--env-dot);
  block-size: var(--env-dot); border-radius: 50%; background: var(--hand);
  box-shadow: 0 0 0 var(--bw-hard) var(--panel); pointer-events: none; }
.nu-envh:hover .nu-envdot, .nu-envh:focus-visible .nu-envdot{
  inline-size: calc(var(--env-dot) + var(--s2));
  block-size: calc(var(--env-dot) + var(--s2)); }
/* THE FOCUS RING IS ON THE DOT'S SIZE, NOT ON A 44px CIRCLE OF AIR — a ring
   drawn round the whole tap target reads as a hole in the plate. */
.nu-envh:focus-visible{ outline: none; }
.nu-envh:focus-visible .nu-envdot{
  box-shadow: 0 0 0 var(--bw-hard) var(--panel),
              0 0 0 calc(2 * var(--bw-hard)) var(--hand); }
/* A REFUSED HANDLE IS DIM AND CARRIES ITS SENTENCE (`data-why`) — the
   no-silent-grey law, which is not only about the engine. */
.nu-envh.is-refused{ cursor: not-allowed; }
.nu-envh.is-refused .nu-envdot{ background: var(--dim); opacity: .55; }
/* THE NUMBERS, BESIDE THE HANDLES AND IN THE FIELD'S OWN UNITS. §11: *"prints
   the numbers beside the curve"* — this is the half of the ruling that makes
   removing the knob rows a move and not a loss. Bold is a value a hand set
   (the cells' own law, §11 again), quiet is what the chair inherits. */
.nu-envsays{ display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
  padding-block-start: var(--s2); }
.nu-envsay{ display: inline-flex; align-items: center; gap: var(--s1);
  font: var(--fw-body) var(--t3)/1.3 var(--mono, monospace); color: var(--dim); }
.nu-envsay b{ font-weight: var(--fw-body); color: var(--ink); }
.nu-envsay.is-said{ color: var(--ink); }
.nu-envsay.is-said > span{ font-weight: var(--fw-label); }

/* THE COLUMN GRIP (§9a: resizable columns). A DRAG-ONLY CONTROL IS A REFUSED
   CONTROL (§6 ¶A), so it is a button: it drags with a pointer and steps 12px
   with the arrow keys. It is the head's full height, which is what keeps it
   over the 44px floor test/shell.js and T5g measure on every visible button in
   this pane, and it is the width of a rule, which is what keeps it out of the
   way of the word beside it. */
/* (`.nu-sheetgrid .nu-colhead{ position: relative }` stood here, to anchor the
   grip. It came AFTER the freeze rule and quietly beat it: T9s read the head's
   computed position back as `relative` and the column heads stopped sticking
   the same minute they were told to. A `sticky` box is a containing block for
   an absolutely positioned child exactly as a `relative` one is, so the grip
   needs no rule of its own and the freeze keeps the position it declares.) */
.nu-colgrip{ position: absolute; inset-block: 0; inset-inline-end: -2px;
  inline-size: 8px; min-block-size: var(--tap); padding: 0; cursor: col-resize;
  background: transparent; border: 0;
  border-inline-end: var(--bw) solid var(--rule); border-radius: 0; }
.nu-colgrip:hover, .nu-colgrip:focus-visible{
  border-inline-end-color: var(--hand); border-inline-end-width: var(--bw-hard); }

/* THE NATIVE PICKER, for a vocabulary over eight words on a coarse pointer
   (§9b: "the native picker on touch, the typed combo on desktop with a
   keyboard, chips for a vocabulary of ≤ 8 words"). It wears the cell plate so a
   sheet reads as one list whichever widget a row holds, and it keeps the
   FIELD's own `data-k`, because an address does not move when a widget does. */
.nu-nativepick{ inline-size: auto; max-inline-size: 100%; padding-inline: .5ch;
  font: var(--btn-fw) 1rem/1.2 var(--sans, inherit); }

/* ---- THE BOARD'S LEGEND RESERVES ITS TALLEST STATE ---------------------
   nukernel/desk-gate.js, on the run of 2026-09-02: *"at 390: tapping each of
   the 5 children of the Mix icon … moves neither scrollY nor the board's panel
   itself"* — FAILED, `{"tab":"boardtab|bus|main","dY":0,"dRow":19}`.

   MEASURED, ON THE RENDERED PAGE, at 390x844 with each of the five plates
   opened in turn: `[data-k="board.legend"]` is 19px tall over the three bus
   plates ("dim is derived · bright is set") and 38px over `main` and `auto`,
   which add "· green is measured" — the only two plates with a well in them.
   The legend stands ABOVE `#boardpanel`, so those 19px push the panel down and
   the grid moves under the thumb that opened it. Nothing was wrong with the
   sentence: it was right on all five plates, and a sentence that is true on
   three of them in one line and on two of them in two is still one sentence.

   SO THE BOX IS THE SIZE OF ITS LONGEST STATE, ALWAYS. This is the page's own
   law, said about a line of type instead of about a button: GEOMETRY IS
   RESERVED AT CREATION (`.nu-trayfoot` — "a control that materialises when the
   first line lands would move the marks above it under a thumb that is
   reaching for one"), and the countdown and the log badge already obey it.
   `2lh` is two of this element's own lines; the `em` before it is the same
   number for an engine without `lh`, measured off the 38px this legend
   actually draws at `--t2`.
   WHAT IT COSTS: one line of white above the rack on a plate whose legend fits
   on one line, at a width where it does. That is the price of the panel never
   moving, and the panel never moving is the thing a hand can feel. */
[data-k="board.legend"]{ min-block-size: 2.9em; min-block-size: 2lh; }
/* ========================================================================
   WAVE 4D — THE GENRE EDITOR, TIGHTER (2026-09-02)
   ========================================================================
   Paul, after using the deployed page: *"The genre editor is great. It can be
   a lot tighter though — it has text all over the place. Look at it from the
   point of view of a user just seeing it for the first time."*

   MEASURED BEFORE, on the rendered panel at 390 and 1280: 27 rows, a 4117px
   deck, and row heights running 33 · 46 · 76 · 103 · 228 · 322 · 478 — because
   every editable rule was three stacked things (a bold question line, a
   control line under it, a tier line under that) and a positional list was one
   full field per chair. Paul's "text all over the place" is exactly what a
   column of that reads like.

   THE BLOCK ABOVE STANDS AND IS NOT EDITED — the six rules it states are still
   the panel's, and what follows overrides only the geometry those rules asked
   for when the sentence and its answer were on two lines. ui/rules.js now
   walks the rule's own sentence PARTS and builds the widget where the value
   is, so a rule row is one line of words with the control standing in it.
   Everything here serves that one sentence: it may wrap, it may not stack. */

/* THE PLATE IS ONE LINE, AND THE LINEAGE IS THE LINE UNDER IT. `.nu-namebar`
   is a baseline flex row and `.nu-ruleplate` let it wrap, so at 390 the name,
   the place-year and the kin came out as three ragged lines of ink. Two block
   lines instead, each ellipsised: a plate that grows with the length of a
   genre's name is a plate that is a different height on every record. */
.nu-ruleplate{ display: block; }
.nu-ruleline{ display: block; min-inline-size: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nu-rulekin{ display: block; margin-block-start: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* the axis word, one step quieter and one step closer to its block: it is a
   divider you read once. (Small caps by the rule above; this only tightens
   the air around it, which is what makes eight blocks fit on a phone.) */
.nu-axword{ margin: var(--s3) 0 0; }
.nu-rulax{ margin-block-end: var(--s3); }

/* ONE ROW IS ONE LINE. A flex row so the reset mark sits at the end of the
   sentence rather than under it, and the sentence itself takes the rest. */
.nu-rule{ display: flex; align-items: center; gap: var(--s2);
  padding-block-end: 0; }
/* THE SENTENCE. It is a `<label>` — the text diet skips `label`, which is what
   lets thirty-eight sentences cost the page nothing, and a reader hears the
   whole sentence as the first control's name. `line-height: normal` cancels
   the global `label{ line-height: var(--tap) }`, which would put 44px of
   leading between two wrapped lines of one sentence. */
/* ---- AND IT IS TWO LINES, 2026-09-03 -----------------------------------
   WHAT STOOD IN THE DECLARATION BELOW: `display: flex; flex-wrap: wrap;
   align-items: center; gap: 0 var(--s2)` — one wrapping line of words with
   the widgets flowing among them, which is the drawing the block above
   describes and defends.
   Paul, after using the deployed composer: *"Arrange things so the slider and
   function descriptions are on a line with the slider after that line, not
   bunched together."*
   MEASURED at 390 before this change: ten of the twenty-five rows wrapped
   INSIDE the sentence, so `the tempo is [====slider====] 76` put `beats a
   minute` on a line below the control and the question could not be read in
   one pass. It is a COLUMN now — the sentence (with its value printed in it)
   on line one, the control alone on line two — and every claim the block
   above makes about the `<label>`, the diet and the accessible name is
   untouched, because the two lines are inside it. */
.nu-rule > .nu-said{ flex: 1 1 auto; min-inline-size: 0;
  display: flex; flex-direction: column; align-items: stretch;
  gap: var(--s1);
  min-block-size: var(--tap); line-height: normal;
  padding-block: var(--s1); }
/* LINE ONE — the sentence, and it is ordinary inline text. The words carry
   their own spaces (`nukernel/rules.js` says "the tempo is " with the space
   in it), so there is no gap to set and nothing to align: a `<b class="nu-v">`
   or an `<output class="nu-v">` stands where the answer stands and the line
   wraps like prose when it is long. */
.nu-wline{ display: block; min-inline-size: 0; }
/* LINE TWO — the control, and nothing else on it. A flex row because a
   sentence with several answers (a positional `instr`, a numeric `orn`) has
   several controls and they wrap; a single-answer row has one and it takes
   the width. */
.nu-ctl{ display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s1) var(--s2); min-inline-size: 0; }
/* A REFUSAL THAT `menu()` LIFTED OUT OF ui/selects.js's field goes UNDER the
   control it refuses, never beside it — the same `flex: 1 0 100%` the field's
   own rule (and `.nu-opt .nu-why`) uses, said again for the control line
   because the line it used to sit on is not this one. */
.nu-ctl > .nu-why{ flex: 1 0 100%; min-inline-size: 0; }
/* THE ANSWER, INSIDE THE SENTENCE. It is the one word on the line that a hand
   can move, so it is the one word that is not the caption weight — and a
   number is tabular, so a live drag does not shuffle the words after it. */
.nu-rv{ font-weight: var(--fw-label); font-variant-numeric: tabular-nums; }
output.nu-rv{ display: inline; }
/* A WORD IN THE SENTENCE IS A WORD, NOT A LINE. `.nu-sel > label > .nu-w` (the
   even-field recipe) gives it `flex: 1 0 100%` and a 44px line box, which is
   right for a question ABOVE its control and is the whole of what this round
   is undoing. Here it flows.
   ...AND IT IS NOT SHOUTED. A question standing over its control earns 700
   weight at body size — it is the only thing on its line. A sentence with the
   answer inside it does not: at `--t3`/700 the tempo rule wrapped onto two
   lines at 390 with a slider in the middle of it, which is the "text all over
   the place" again in a smaller font. `--t2` at 500 is the page's caption
   voice, the same one `.nu-namebar small` and every value caption uses, and it
   puts the emphasis where the emphasis belongs: on the CONTROL, which is the
   only part of the sentence a hand can move. */
.nu-rule .nu-w{ flex: 0 1 auto; inline-size: auto; block-size: auto;
  line-height: normal; font-size: var(--t2); font-weight: 500;
  white-space: normal; overflow: visible; }
/* the controls, sized to sit in a line of type rather than to own one */
/* THE 22ch CAP IS A MAXIMUM AND THE BOX STILL SHRINKS TO ITS WORDS — which is
   how this block was measured, and which stopped being true for one day when
   4B and 4D merged. A `<select>`'s intrinsic width is its longest option; an
   `<input>`'s is its `size` attribute, twenty characters whatever is in it. So
   every menu in a sentence drew 224px — the cap, not the content — and the
   control wrapped onto its own line under its own words. MEASURED on the
   rendered Rules panel at 390: `rate` / `mode` / `scale` / `harmony` 66px each
   and `plan` 96px, which is what turned R9's "inside the two tap rows every
   other question gets" red. The repair is in ui/selects.js (`buildCombo` sizes
   the field to its longest word, the way the element it replaced did) and not
   here, because the number in this rule was never wrong: nothing in a sentence
   may be wider than 22ch, and most menus are far narrower than that. */
/* (THE 22ch CAP IS NOW A FLOOR-AND-CAP ON ITS OWN LINE, 2026-09-03. The
   paragraph above stands as the reason the cap exists; what changed is that
   the menu no longer shares a line with the words, so the number that matters
   is the one every other menu on this page uses when it stands under its own
   question — `.nu-sel > label > .nu-combo`'s 30ch.) */
.nu-rule .nu-combo{ flex: 1 1 auto; min-inline-size: 0;
  max-inline-size: min(100%, 30ch); }
/* (`.nu-rule .nu-combo > select{ block-size: var(--tap) }` stood here and
   matches nothing since 2026-09-02: the field is the box's only child now and
   `.nu-combofield` carries `block-size: var(--tap)` itself.) */
/* THE SLIDER TAKES THE LINE (2026-09-03). It read `flex: 0 1 130px` — a
   130px stub sized to sit inside a line of type, which is what the sentence
   drawing needed and what Paul's *"the slider after that line"* replaces. On
   its own line it is the whole width, which is also the widest a thumb can be
   given for free. */
.nu-rule input[type=range]{ flex: 1 1 100%; inline-size: 100%;
  min-inline-size: 0; block-size: var(--tap); }
/* ...AND THE 28ch CAP DOES NOT APPLY TO IT. That cap (`.nu-field > label >
   input[type=range]` and the three row classes beside it, ~500 lines up) says
   in its own words what it is for: "the three surfaces that put a slider on a
   line with words". A Rules slider is not on a line with words any more — it
   IS the line — so the cap would leave it at 240px in a 900px column at 1280,
   which is the one measurable cost of the old drawing carried into the new
   one. Only the sliders that OWN the line are freed: a `map`'s per-key slider
   lives in a `.nu-cell` and still shares its line, so it keeps the cap. */
.nu-rule > .nu-said > .nu-ctl > input[type=range]{ max-inline-size: 100%; }
.nu-rule output{ flex: 0 0 auto; font-variant-numeric: tabular-nums; }
/* a map's per-key answer: the key as a small word, then its control, the two
   kept together on one wrap */
.nu-cell{ display: inline-flex; align-items: center; gap: var(--s1);
  min-inline-size: 0; }
.nu-cell > .nu-k{ font-style: normal; font-size: var(--t1);
  font-weight: var(--fw-label); color: var(--rule); }
/* A SENTENCE WITH SEVERAL ANSWERS IN IT GIVES EACH ONE LESS ROOM, and the
   shape is read off `data-shape` (which is `edit.kind`, the same fact
   ui/rules.js reads to pick the widget) rather than off a field's name. A
   positional `instr` list is one menu per chair and a numeric `orn` map is one
   slider per decoration: at the single-answer cap of 22ch, three instrument
   menus were 205px of column at 390. Measured after: 100px. Nothing is
   hidden — a `<select>` ellipsises its CLOSED word and opens at full width,
   which is the same trade `.nu-sel > label > .nu-combo`'s own 30ch cap makes
   and states. */
.nu-rule[data-shape="list"] .nu-combo,
.nu-rule[data-shape="map"] .nu-combo{ flex: 0 1 auto;
  max-inline-size: min(100%, 13ch); }
/* A MULTI-ANSWER ROW SHARES ITS CONTROL LINE, which is the same arithmetic
   this block has always made and it is now made on line two instead of inside
   the sentence: `orn` is one slider per decoration and `touch` is two, so a
   full-width slider each would be five and two stacked 44px rows. They take
   half the line and wrap. */
.nu-rule[data-shape="map"] input[type=range],
.nu-rule[data-shape="pair"] input[type=range]{ flex: 1 1 40%;
  inline-size: auto; min-inline-size: 56px; }
.nu-rule[data-shape="map"] .nu-cell,
.nu-rule[data-shape="pair"] .nu-cell{ flex: 1 1 min(100%, 6em); }
/* an `fx` chip inside the sentence — the same `.nu-opt` chip, in line. Its
   row is a `<fieldset>` with the sentence as its `<legend>`, because a chip
   IS a `<label>` (that is what makes the 24px target test/shell.js A3
   measures) and a label inside a label is not a shape a browser is asked to
   make sense of. The fieldset carries no frame of its own: the chips are the
   drawing and the legend is the sentence, in the same line box as every other
   rule row. */
.nu-rule > fieldset.nu-said{ border: 0; margin: 0; padding: 0;
  min-inline-size: 0; }
/* (IT WAS `float: left` UNTIL 2026-09-03 — the legend beside the chips, which
   is the bunching Paul's two-line row takes out. The legend is line one and
   the `.nu-ctl` under it is line two, the same shape every other rule row
   has; a `<legend>` is not a flex item of its own fieldset, so the column
   above cannot lay it out and `display: block` is what puts it on its line.) */
.nu-rule > fieldset.nu-said > legend{ padding: 0; float: none;
  display: block; margin-inline-end: 0; }
.nu-rule .nu-opt{ display: inline-flex; flex-wrap: nowrap;
  margin-block: var(--s1); margin-inline-end: var(--s2);
  min-block-size: var(--tap); }

/* A SAID-ONLY ROW IS ONE LINE TOO, and a REFUSED one is that line greyed with
   its reason in `data-why` (where the diet's T3 and the no-silent-grey law
   both look) and in `data-say` (where a hold looks). It used to print the
   sentence, an em dash and the reason; the reason is the answer to a question
   a first-time reader has not asked yet. */
.nu-rule > .nu-why{ flex: 1 1 auto; min-inline-size: 0; display: flex;
  align-items: center; min-block-size: var(--tap); margin: 0; }
.nu-rule[data-why]{ opacity: .55; }
.nu-rule[data-why] .nu-said{ pointer-events: none; }

/* the way back, at the end of the sentence and not under it */
.nu-rule > button[data-k^="rule-reset"]{ flex: 0 0 auto; margin-block-start: 0; }

/* THE PALETTE IS A `+`, NOT A FIELD. One compact menu at the block's foot: it
   was a 44px "add a rule" label over a 44px menu whose first option said the
   same words again, under every one of the eight blocks. The visible label is
   gone and the accessible one is not. */
.nu-pal{ margin: var(--s1) 0 0; }
.nu-pal .nu-combo{ max-inline-size: min(100%, 15ch); }
/* (`.nu-pal .nu-combo > select` went the same way as `.nu-rule`'s — no
   `<select>` inside the box since 2026-09-02.) */
/* ======================================================================
   WAVE 4B — THE COMBOBOX (2026-09-02)
   ======================================================================
   Paul, after using the composer on staging: *"The combo boxes just don't work
   and are confusing. I was expecting more of onfocus show custom dropdown then
   filter based on input — one line instead of two."*

   THE BLOCK THIS AMENDS IS `.nu-combo`, ABOVE, AND IT IS LEFT WHERE IT STANDS.
   Its argument — the page's own arrow instead of six platform arrows, the
   detent colours, a filter over a long list — is unchanged and is exactly what
   is drawn here. What its declarations assumed is gone: there is no `<select>`
   inside `.nu-combo` any more and no `<input type=search>` above one, so every
   rule up there that names `> select` or `.nu-combo-filter` now matches
   nothing. They are not deleted (this wave appends; the parent merges) — they
   are SUPERSEDED here, name for name, and the two-line block they dressed is
   one line now:

       .nu-combo  >  input.nu-combofield[role=combobox]
                     ul.nu-combolist[role=listbox][hidden]

   THE LIST IS IN THE FLOW. No `position: absolute`, no `max-block-size`, no
   `overflow` anywhere in this block, and that is the load-bearing absence: a
   menu on this page may not scroll inside itself, so an open list GROWS THE
   BOX and the page grows with it. 108 instruments make a long page, which is
   the one thing this surface is allowed to be.

   WHAT SIZES THE BOX. The field is the box: `.nu-combo` is capped at the same
   `min(100%, 30ch)` the `<select>` was (its own paragraph above carries the
   measurement — "a menu stretched across a 1400px desktop is a 1345px control
   holding one word"), and the field fills it. The cap is stated as
   `inline-size` and not only as a maximum so the width does not JUMP when the
   list opens: a shut list is `display:none` and contributes nothing to the
   grid's track, so a wrapper sized to its content would be one width closed and
   another open, under the finger that just opened it. */

/* ---- the field ------------------------------------------------------- */
/* THE PLATE IS THE `<select>`'s PLATE, declaration for declaration (nu.css's
   `select{…}` rule near the top of this file), because it is the same control
   wearing a different element and it may not read as a different species. What
   it does NOT take from `input[type=text]` is the "a cut, not a plate" face:
   you do not write into this one, you choose in it. */
.nu-combofield{
  inline-size: 100%; min-inline-size: 0;
  font-size: var(--t3);              /* 16px: the iOS floor, never a --t under it */
  font-weight: var(--btn-fw);
  background: var(--panel);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  min-block-size: var(--tap); block-size: var(--tap);
  /* room for the triangle on the end side, the same reserve the select had */
  padding-inline: var(--s3) calc(var(--s3) + 1.1em);
  text-overflow: ellipsis; cursor: pointer; }
/* READONLY IS THE SHUT STATE AND IT LOOKS LIKE ONE. ui/selects.js clears
   `readonly` at the moment the list opens, which is the moment typing starts
   meaning something, so the caret appears exactly when it can be used. */
.nu-combofield:not(:read-only){ cursor: text; }
.nu-combofield:hover{ background: var(--flag-tint); }
.nu-combofield:disabled{ color: var(--faint); background: var(--well);
  border-color: var(--faint); cursor: default; }

/* ---- the detent, on the field --------------------------------------- */
/* `.nu-combo.is-seated > select` / `.nu-combo.is-said > select` are superseded:
   the classes are on the FIELD now (ui/selects.js `paintDetent`), because the
   field is the element that carries the word. Same two colours, same argument:
   dim for the default the record dealt, the hand for a value you said. */
.nu-combofield.is-seated{ color: var(--dim); }
.nu-combofield.is-said{ color: var(--hand); font-weight: var(--fw-label); }
.nu-combo:has(> .nu-combofield.is-said)::after{ color: var(--hand); }
.nu-combo:has(> .nu-combofield:disabled)::after{ color: var(--faint); }

/* ---- the arrow rides the FIELD's row, not the box's foot ------------- */
/* `.nu-combo::after` above is `inset-block-end: 0`, which was the bottom of a
   box that held only the menu. The box grows downward now, so the same
   declaration would walk the triangle to the bottom of a 108-row list. It is
   re-anchored to the top — the field is always the first row — and it turns
   over when the list is open, which is the one thing on the page that says
   "this is open" without a word. */
.nu-combo::after{ inset-block-start: 0; inset-block-end: auto; }
.nu-combo.is-open::after{ transform: scaleY(-1); }

/* ---- the box -------------------------------------------------------- */
.nu-combo{ gap: 0; }
.nu-sel > label > .nu-combo{ flex: 0 1 auto;
  inline-size: min(100%, 30ch); max-inline-size: min(100%, 30ch); }

/* ---- the list ------------------------------------------------------- */
/* One plate under the field, square-shouldered at the top so the two read as
   one control rather than as two stacked plates. No scroller: see the header. */
.nu-combolist{ list-style: none; margin: 0; padding: 0;
  background: var(--panel);
  border: var(--bw) solid var(--ink);
  border-start-start-radius: 0; border-start-end-radius: 0;
  border-end-start-radius: var(--r1); border-end-end-radius: var(--r1);
  border-block-start: 0; }
.nu-comboopt{ display: flex; align-items: center;
  min-block-size: var(--tap); padding: var(--s2) var(--s3);
  line-height: 1.25; cursor: pointer; }
.nu-comboopt + .nu-comboopt{ border-block-start: var(--bw) solid var(--well); }
.nu-comboopt:hover{ background: var(--flag-tint); }
/* THE WORD YOU ARE STANDING ON WEARS THE HAND CAP, which is what `.is-on`
   means everywhere else on this page (`.nu-opt`, an open tab, a pressed mark);
   the keyboard's own position is the flag, which is what hover is, so the two
   never claim the same thing. */
.nu-comboopt.is-on{ background: var(--hand); color: var(--panel); }
.nu-comboopt.is-active{ background: var(--flag-tint); }
.nu-comboopt.is-on.is-active{ background: var(--hand); color: var(--panel);
  outline: var(--bw-hard) solid var(--flag); outline-offset: calc(-1 * var(--bw-hard)); }
/* GREYED, NOT HIDDEN — the global `.is-off{ opacity: .45 }` and `.is-quiet`
   dress these for free, exactly as they dressed `option:disabled`. All this
   adds is that a refused word does not pretend to be tappable. */
.nu-comboopt.is-off{ cursor: default; }
.nu-comboopt.is-off:hover, .nu-comboopt.is-quiet:hover{ background: transparent; }
/* THE GROUP HEADING — what `<optgroup>` drew, in the page's own machine label
   (`ideal/design-system.html:60-63`, ".lab — 11px mono 700 caps, dim"). */
.nu-combogrp{ padding: var(--s3) var(--s3) var(--s1);
  font: 700 var(--t1)/1.2 var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--dim); }
.nu-combonone{ padding: var(--s2) var(--s3); }

/* ---- the compact form, which is the same widget in a cell ------------ */
/* `selectEl` returns `.nu-combo.is-compact`, so the four places a BARE menu
   used to sit keep their measurements — the cell's 9em cap and its whole
   argument are in `td > select`'s own block above, and are restated here on
   the element that now stands there. */
td > .nu-combo{ inline-size: 100%; min-inline-size: 0; max-inline-size: 9em; }
.nu-slotrow .nu-combo{ flex: 1 1 auto; min-inline-size: 0; max-inline-size: 100%; }
.nu-vow .nu-combo{ min-inline-size: 4.5em; }
.nu-sgrid td > .nu-combo{ min-inline-size: 7em; }
/* dim is derived, bright is set — `gridCell` puts `.is-derived` on the widget
   it is handed, which is the wrapper now and was the `<select>` before */
.nu-sgrid td > .nu-combo.is-derived > .nu-combofield{
  font-weight: var(--fw-body); opacity: .78; }
.nu-sgrid tbody tr:has(mark) > td > .nu-combo > .nu-combofield{
  border-color: var(--clock); }

/* ======================================================================
   THE SAMPLE CRATE (2026-09-03)

   Paul, 2026-09-01: *"I can't really access or organize samples used in, say,
   San Francisco 1996. They aren't accessible to the app in any way."*

   ONE GROUP PER CHAIR, ONE ROW PER FILE, and nothing here is a new chassis:
   the name plate is `.nu-namebar`, the chair's colour is `[data-vi]` +
   `.nu-vpaint.is-edge` (the quiet form — a 3px edge bar in the player's hue),
   the swap is the page's own combobox inside a `.nu-sel`, the source's address
   is `.nu-routelink` (the 44px door every other pointer wears) and the
   audition is the base button face. What this block adds is a LIST: how a file
   row lays out, and what stops it laying out sideways.

   WHY A `<ul>` AND NOT A TABLE. A record can reach thirty files and a table
   would have to scroll inside itself at 320 (the `.nu-pane` chassis) — which
   is right for a GRID, where the columns line up and reading down one of them
   is the point, and wrong here: these rows are a LIST of things, each of which
   answers a different number of questions (a zone knows its key range, a break
   knows its tempo, a kit lane knows neither). So the row WRAPS instead, and
   the page never scrolls sideways because nothing in it is wider than its
   column: `min-inline-size: 0` on the flex children and `overflow-wrap:
   anywhere` on the two that carry arbitrary text off disk (a file name, a URL).
   Measured at 320 / 390 / 1280 by test/samples.browser.js S6.
   ====================================================================== */
.nu-crate{ margin-block: var(--s4); }
/* THE GROUP CARRIES `.nu-vpaint.is-edge` TOO (see ui/samples.js) — the 3px
   inset bar in the player's hue, which is the roster's own quiet form and the
   only thing on this page allowed to paint with a category token. All this
   rule adds is the air that keeps the rows off that bar. */
.nu-crategrp{ margin-block-start: var(--s4); padding-inline-start: var(--s3); }
.nu-cratehead{ display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--s2); margin: 0 0 var(--s2); }
/* the player's name, in the player's hue; `.is-edge` supplies the quiet form
   and this only sets the type */
.nu-cratewho{ font-weight: var(--fw-block); }
/* ...and what that chair is ON, in the registry's own words: the second voice,
   which is what `--dim` is for everywhere else on this page */
.nu-crateis{ color: var(--dim); font-size: var(--t2); }

.nu-cratelist{ list-style: none; margin: var(--s2) 0 0; padding: 0; }
.nu-crow{ display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); min-inline-size: 0;
  padding-block: var(--s2);
  border-block-start: var(--bw) solid var(--well); }
.nu-crow > *{ min-inline-size: 0; }
/* THE KIND CHIP — the machine label, which is `.nu-combogrp`'s own face said
   on one word: 11px mono, caps, tracked. It is a fixed column so the eye can
   run down it, and it is the one thing here that does NOT wrap. */
.nu-ckind{ flex: 0 0 auto; min-inline-size: 5.5em;
  font: 700 var(--t0)/1.2 var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--dim); }
/* the file's own name, off disk — arbitrary text, so it breaks anywhere rather
   than pushing the column out */
.nu-cname{ flex: 1 1 8em; font-weight: var(--fw-label);
  overflow-wrap: anywhere; }
/* what the owning table knows about it: a duration, a tempo, a key range. The
   machine voice, like every other value this page prints. */
.nu-cdata{ flex: 1 1 auto; color: var(--dim);
  font: var(--num-fw) var(--t1)/1.3 var(--mono); }
/* the audition — a thumb, at the end of the row, wearing the page's own button
   face (the base rule) and nothing else. `aria-pressed` paints it the way
   every other two-state mark on this page is painted. */
.nu-cplay{ flex: 0 0 auto; min-block-size: var(--tap); }
.nu-cplay[aria-pressed="true"]{ background: var(--clock); color: var(--panel);
  border-color: var(--clock); }
/* WHERE IT COMES FROM — the source's own label and its address, on their own
   line under the row, because a licence line is the one piece of prose here
   that is longer than the row it belongs to. It takes the whole width
   (`flex-basis: 100%`) so the label never squeezes the file's name. */
/* on a ROW it takes the whole width (`flex-basis: 100%`) so the label never
   squeezes the file's name; standing on its own under a group head it is just
   a line. One rule, because it is one thing said in two places. */
.nu-csrc{ flex: 1 0 100%; display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2); margin: var(--s1) 0 0; }
.nu-crategrp > .nu-csrc{ margin-block-end: var(--s2); }
.nu-clabel{ color: var(--dim); font-size: var(--t1); }
/* THE REFUSAL A PRESS WRITES (ui/samples.js `refuse`) — the page's own
   `.nu-why` face, on its own line under the row it belongs to. */
.nu-crefuse{ flex: 1 0 100%; margin: var(--s1) 0 0; }
.nu-curl{ overflow-wrap: anywhere; font-size: var(--t1); }

/* ======================================================================
   THE MENU IS THREE WIDGETS AND ONE CHASSIS (2026-09-06)

   Paul, 2026-09-05: *"In general dropdowns barely work."* `nukernel/src/menus/`
   is the one owner of every menu on the page now, and `src/menus/pick.ts` gives
   a vocabulary one of three: CHIPS up to eight words, the NATIVE `<select>`
   above eight on a coarse pointer, the TYPED COMBO above eight with a keyboard.
   All three sit inside the same `<span class="nu-combo">`, because the chassis
   above — the plate, the ▾, the 30ch cap, the `is-seated` / `is-said` detent —
   is what makes a menu read as a menu, and a control may not read as a
   different species for being drawn a different way.

   THREE DECLARATIONS ARE ALL THE PAINT THIS ROUND NEEDS, and each is a
   correction to a chassis rule that assumed there was one widget in the box:

     · NO ▾ OVER A STRIP OF CHIPS. `.nu-combo::after` is the arrow that says
       "there is a list under this"; a strip has no list, and an arrow over its
       last chip is a lie with a 44px target under it.
     · A STRIP IS AS WIDE AS ITS WORDS, NOT 30ch. `.nu-sel > label > .nu-combo`
       caps the box at `min(100%, 30ch)`, which is right for one word in a field
       and wrong for eight chips: capped, the strip wrapped to four rows under a
       label that had 60% of the line to itself. It takes the whole line and
       wraps inside itself, which is what `.nu-wchips` does anyway.
     · AND THE NATIVE PICKER IS 44px AND 16px LIKE EVERYTHING ELSE A THUMB
       TOUCHES: it wears `.nu-combofield`, which is the plate, the tap height
       and the iOS type floor said once for both elements. What it needs on top
       is the `appearance: none` the `.nu-combo > select` rule above already
       gives it — that rule was written for the 2026-09-02 wrapper, matched
       nothing for four days, and matches again now. Nothing to add.
   ====================================================================== */
.nu-combo[data-widget="chips"]::after{ content: none; }
.nu-sel > label > .nu-combo[data-widget="chips"],
.nu-combo[data-widget="chips"]{
  inline-size: 100%; max-inline-size: 100%; flex: 1 1 100%; }
/* the strip fills the box it is in, and never scrolls inside itself */
.nu-combo > .nu-wchips{ inline-size: 100%; min-inline-size: 0; }

/* ===== THE BAR, THE HAMBURGER, AND A PANEL AS A SHEET ====================
   (2026-09-09, TABLE.md §10b steps 6 and 7.) Paul: *"…then have a hamburger
   menu for score, video, screensaver, and have genre, dice, playstop along the
   bottom — a real mobile app now with everything in the table and the nav
   space reclaimed."*

   THREE FIXED BOXES AND ONE ATTRIBUTE, and that is the whole of the chrome:
   `.nu-top` (the close and the ≡, at the top corner), `#nu-menu` (the four
   viewers and the log, a plate under the ≡), `.nu-bar` (the foot), and
   `[data-sheet]` on whichever panel is open. `#nu-chrome` itself is NOT laid
   out — it is a `<nav>` whose three children are each `position: fixed`, so it
   is the reading-order anchor and nothing else. With the stylesheet off it is
   an ordinary block of buttons before the panels, which is the row's own
   argument kept (see the tombstone in nukernel/index.html).

   WHAT WENT WITH THE GUTTER: `.nu-tray`, `.nu-traylist`, `.nu-trayfoot`,
   `.nu-traycut`, `.nu-trayopts`, `.nu-trayvol`, the four `[data-depth]` rules
   and their `--nu-indent`, the two `#nu-tray .nu-ic` grids, the `@media
   (min-width: 900px)` type step, and `.nu-tray .is-sounding`. Forty-one rules.
   `.nu-count`, `.nu-seedrow`, `.nu-strip-out`, `.nu-log`, `.nu-say` and every
   `.nu-ic` / `.nu-g` / `.nu-n` / `.nu-vh` rule are UNTOUCHED — they were never
   the stripe's, they were the marks', and the marks moved with their rules. */
#nu-chrome{ display: contents; }

/* ---- THE TOP STRIP IS DELETED (2026-09-05, TABLE.md §13a.1) -----------
   Paul, with the Silence record on his iPhone: *"Please look at this mess and
   I can't really get to anything — things don't scroll out of the way for me
   to focus it's all jammed up."* MEASURED at 390 x 844 on v287: two fixed
   boxes, `.nu-top` 55.8 x 55.2 at the top corner and `.nu-bar` 390 x 50.4 at
   the foot, plus a 105.8px formula bar that never scrolled either.
   §13a.1: *"Nothing is fixed but the bottom bar. A thing pins only while you
   are inside it."* So `.nu-top` goes and its two marks go to the two places
   they belong: the ≡ is the LAST button of `.nu-bar` (it is the one control on
   the screen in every state of this page, so it may not shift under a reach —
   the end of the bar is where it does not), and the × is the open sheet's own
   header, which is what closes a sheet everywhere else software is written.
   WHAT STOOD HERE: `.nu-top` (fixed, z 60, at the inline end, a `--s2` pad and
   the notch), `.nu-top > button` (the tap floor and this page's plate — panel
   ground, ink border, hard shadow), its hover, its `[hidden]`, and
   `.nu-top .nu-ic`. Five rules. The plate they wore is `.nu-sheethead`'s now,
   below, at the one place a × still needs one. */

/* ---- THE TOP STRIP: WHERE YOU ARE, AND THE DOOR (2026-09-06) ---------
   docs/NAV.md, Paul: *"move the view selector into a hamburger on the top
   right. Consolidate that with the existing bottom right hamburger (which goes
   away)… So now bottom row is pure play controls and top right is compose
   arrange controls."*

   IT IS THE SECOND OF EXACTLY TWO FIXED BANDS, and the paragraph above — "the
   top strip is deleted" — is the state this replaces. What is different from
   the strip §13a.1 deleted, and why this one is allowed where that one was
   not: THAT strip was a floating PLATE at one corner, over a page it did not
   reserve room in, holding a × for a sheet that could close itself; THIS one
   is a full-width band that <body> takes out of the page (`padding-block-start:
   var(--top-h)`), holding the two controls that must be on the screen in every
   state of the box — the record's name and the one door to everywhere else.
   §13a's law is amended to say two, and TABLE.md §16 is where it is amended.

   IT IS `--tap` TALL AND SPENDS NOTHING ELSE. Two 44px targets at the two ends
   of one row, the page's own gutters either side, an opaque ground and a rule
   under it for the same reason the bar has one over it: the page scrolls under
   it and see-through chrome over moving type is what this page's flat-paint
   law has never allowed. */
.nu-topstrip{ position: fixed; z-index: 58;
  inset-inline: 0; inset-block-start: 0;
  display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;
  gap: var(--s2);
  block-size: var(--top-h);
  padding-block-start: env(safe-area-inset-top, 0px);
  padding-inline: var(--gl) var(--gr);
  background: var(--panel); color: var(--ink);
  border-block-end: var(--bw) solid var(--ink); }
/* THE RECORD'S NAME IS AT THE END AND IS AS WIDE AS ITS OWN WORD — which is
   a correction to the arrangement it had in the bar, made on the rendered page
   at 1280. There it was `flex: 1 1 auto` (the bar's own rule, moved with the
   node) and drew a 1,200px plate with "Kingston 1969" floating in the middle
   of it, and a name centred in a plate the width of the screen is not at
   either end of anything. So the plate takes its CONTENT's width (`0 1 auto`);
   `min-inline-size: 0` on the face is what lets a long genre name ellipsise
   rather than push the ≡ off a phone, and the 44px floor below stops that
   shrink going under a thumb. */
/* (IT WAS AT THE *START* UNTIL 2026-09-07, TABLE.md §20. Paul: *"Sorry
   hamburger should be on left."* The ≡ took the start of the band and the
   name took the other end, which is the trade that sentence buys; the swap is
   in the MARKUP, at `stripEl.append` in ui/eight.js, and nothing in this block
   places either node — the tape between them is `1 1 auto` and does all the
   placing there is. docs/NAV.md still says *"THE RECORD'S NAME GOES TOP
   LEFT"*: that line is now wrong and is the doc's to correct, not this file's.
   The name keeps `text-align: start` INSIDE its own plate, because a plate at
   the right end of a band is still a left-to-right label. */
/* (`margin-inline-end: auto` CAME OFF THIS RULE ON 2026-09-06, TABLE.md §18.
   It held the ≡ at the far end across a dead gap — 191px at 390, 121px at 320
   — and the gap is the TAPE's now, which is a `1 1 auto` child and does the
   same job by being there. An auto margin beside a flexible child eats the
   free space the flexible child was asked to take, and the tape would have
   been squeezed to its own min-content.) */
/* ---- AND THE NAME IS THE HEADER (2026-09-08, TABLE.md §22) -----------
   Paul: *"Move the playback bar to the bottom bar and expand the genre as
   header but don't put a border around it."*

   TWO DECLARATIONS AND A SUBTRACTION. `1 1 auto` where the paragraph above
   argued `0 1 auto`: that argument was made when the TAPE was the strip's
   flexible child and the name was one of the two things it pushed to the
   ends, and a name sized to its own word was right beside a readout that
   wanted the rest. The tape is the bar's from today, so there is no second
   claimant and no dead gap to leave — the name takes the band.
   AND `0 1 auto`'s OTHER HALF IS UNCHANGED: `min-inline-size: 0` on the face
   (below) is what still ellipsises `Gregorian chant` rather than pushing the
   ≡ off a phone, and the 44px floor at `.nu-topstrip button` still stops the
   shrink going under a thumb.
   THE PLATE COMES OFF, WHICH IS NOT A RESTYLE BUT A CHANGE OF KIND. A border,
   a ground and a hard shadow are what this page draws around a TARGET; a
   header is a thing you READ. The button is still a button — it still opens
   Where, still wears `aria-pressed`, still takes a tab stop and a thumb — and
   what it stops claiming is that it is a plate sitting ON the band. The
   band's own ground shows through, so the strip reads as one surface with a
   name on it instead of a chip floating at one end of a bar.
   `background: none` AND NOT `transparent`: the base `button` rule sets
   `background`, and the shorthand is what has to be answered. */
/* ---- AND IT LOOKS LIKE A TAB (2026-09-08) -----------------------------
   Paul: *"Make the name of the genre in the top bar more obviously a tab and
   then put the edit button as a proper button immediately to its right not as
   a second Hamburger."*

   IT WAS A WORD WITH AN ACCENT ON IT. §22c took the plate off this button for
   a good reason — a header is a thing you READ, not a target sitting on the
   band — and §25 then made it half of a two-tab switch without giving it back
   anything that says "tab". Ink alone does not: a coloured word beside another
   word is two words, and the one binary this page has was carried entirely by
   a hue.
   SO IT GETS THE ONE MARK A TAB HAS ALWAYS HAD — the rule UNDER it, in the
   `--hand` the pressed word already uses, plus that hue's own tint as a
   ground. Not a border: a frame all the way round is a plate, which is what
   §22c refused, and a tab is precisely a plate with its bottom edge open onto
   the thing it selects. The unpressed tab keeps a transparent rule of the
   same weight, so nothing moves by two pixels when the mode changes.
   `0 1 auto` AND NOT `1 1 auto`, which is the other half of the ask: the name
   took the whole band, so `Edit` was pushed to the far end and read as a
   second, unrelated control. The name is now as wide as it needs to be (and no
   wider — `min-inline-size: 0` on the face still ellipsises `Gregorian chant`
   rather than pushing anything off a phone), and the button sits against it. */
.nu-topstrip > button[data-k="toptab-Where"]{ flex: 0 1 auto;
  min-inline-size: var(--tap);
  background: none; border: 0; border-radius: var(--r2) var(--r2) 0 0;
  box-shadow: none;
  border-block-end: 2px solid transparent;
  padding-inline: var(--s2); }
/* ...AND WHEN IT IS THE OPEN VIEW IT SAYS SO IN INK, NOT IN A BOX. `aria-
   pressed="true"` painted this button with the page's fill because it was a
   plate; a header with no plate has nowhere to put a fill that would not
   repaint the whole band, so the pressed state is the ACCENT on the word —
   the same channel every other name on this page uses to say "you are here".
   The `.nu-sub2` rule below (`--on-fill`) was written for the filled plate
   and is answered here for the same reason. */
/* `--hand` AND NOT `var(--accent, …)`, WHICH WAS A FALLBACK TO NOTHING
   (2026-09-08). Both pressed rules were written `var(--accent, var(--ink))`
   and there IS no `--accent` in tokens.css — so every one of them resolved to
   plain ink and the pressed state was invisible. On a two-tab band that is not
   a cosmetic miss: it is a mode switch that never says which mode you are in,
   which is the "declared but never arriving" shape this box keeps a memory
   note about, shipped in the very round that adds the tabs.
   `--hand` is the page's own answer and its comment says so — *"you set it, it
   is selected, it is focused"* — which is exactly what a tab you are on is. */
/* ...AND THE TAB YOU ARE ON IS UNDERLINED, NOT FILLED (2026-09-08). Paul, of
   the other one: *"Don't put a box around Edit on the top."* A tint IS a box
   once it has a corner on it, and both tabs had the same one, so both lose it:
   the word takes the accent and the rule under it takes the accent, which is
   how a tab has said "you are here" since tabs. The pair is still drawn by one
   pair of rules — they differ in nothing but their data-k. */
.nu-topstrip > button[data-k="toptab-Where"][aria-pressed="true"]{
  background: none; color: var(--hand);
  border-block-end-color: var(--hand); }
/* ===== AND THE OTHER MODE IS A TAB BESIDE IT (2026-09-08, TABLE.md §25) =
   Paul: *"I don't think people will know there's a song editor so put two tabs
   at the top: Explore and Edit."*

   THE WORD HAS TO BE ON THE GLASS, WHICH IS THE WHOLE ASK. `paintIcon` puts
   every mark's word in a `.nu-vh` — clipped, for a screen reader and a page
   with no stylesheet — and the first build of this tab therefore drew a bare
   `☰` 44px wide. A picture nobody can name is exactly the door Paul is saying
   people do not find, so `Edit` is revealed the same way the record's name is
   revealed on the plate beside it: one rule, un-clipping the span this page
   already puts there.
   IT IS `0 0 auto` AND EXPLORE IS `1 1 auto`, so the two do not compete: the
   mode you are LEAVING is a word and a mark, and the mode you are IN carries
   the record. Measured at 320 with the name at its longest: Explore 230px,
   Edit 74, the ≡ 44 — nothing wraps and nothing is under a thumb.
   NO PLATE, for §22c's reason: a border is what this page draws around a
   TARGET and the strip is a band of marks. The pressed state is the accent on
   the word, which is the same channel the plate beside it uses. */
/* ---- AND EDIT IS A BUTTON, BESIDE IT (2026-09-08) ---------------------
   Paul: *"put the edit button as a proper button immediately to its right not
   as a second Hamburger."* Two faults, and the mark is the smaller one
   (ui/glyph.js gives this tab the pencil now; `☰` stays on the plate's
   `Session` row where a list belongs). The other is that it was drawn like the
   header beside it — no plate, no ground, no edge — at the FAR END of the band
   behind a flexible gap, which is how a control comes to read as chrome.
   IT IS THE PAGE'S OWN BUTTON AGAIN: the `--rule-strong` edge, the panel
   ground and the `--r2` corner every other target on this page wears, standing
   immediately after the name. It keeps its `0 0 auto` — a button is its own
   width — and the free space now falls after it, which is why the two read as
   a pair with the room to their right rather than as two ends of a bar. */
/* ...AND IT IS NOT A BOX (2026-09-08). Paul: *"Don't put a box around Edit on
   the top."* The round before this gave it the page's own button — an edge, a
   ground and a corner — because it had been drawn like the header beside it and
   read as chrome. The edge was the wrong half of that fix: what it needed was
   to stand next to the name rather than at the far end of the band, and it
   does. So it wears what the tab beside it wears — the word, the accent when it
   is the open view, and the rule under it — and the two read as a pair because
   they are drawn as a pair. */
.nu-topstrip > button[data-k="toptab-Band"]{ flex: 0 0 auto;
  margin-inline-start: var(--s2);
  background: none; color: var(--ink);
  border: 0; border-radius: 0; box-shadow: none;
  border-block-end: 2px solid transparent;
  padding-inline: var(--s3); }
.nu-topstrip > button[data-k="toptab-Band"] .nu-ic{
  display: flex; flex-direction: row; align-items: center; gap: var(--s1); }
.nu-topstrip > button[data-k="toptab-Band"] .nu-vh{
  position: static; inline-size: auto; block-size: auto;
  clip: auto; clip-path: none; margin: 0; padding: 0; white-space: nowrap;
  font-size: var(--t2, .82rem); font-weight: var(--fw-label); }
.nu-topstrip > button[data-k="toptab-Band"][aria-pressed="true"]{
  background: none; color: var(--hand);
  border-block-end-color: var(--hand); }
@media (hover: hover){
  .nu-topstrip > button[data-k="toptab-Band"]:hover{ background: var(--flag-tint); }
}
.nu-topstrip > button[data-k="toptab-Where"] .nu-ic{ min-inline-size: 0;
  justify-content: flex-start; text-align: start; }
/* ---- THE NAME IS A NAME AGAIN (2026-09-06, TABLE.md §18) --------------
   THE FOUR RULES BELOW WERE `.nu-bar > button[data-k="toptab-Where"]` AND THE
   BUTTON LEFT THE BAR ON 2026-09-06 (v298). Nothing re-scoped them, so in the
   strip the plate fell back to `.nu-vh`'s clip — the record's NAME was one
   clipped pixel and the only word on the glass was `.nu-sub2`, the place and
   the year. On a record whose sub line is dropped (the plate drops it when it
   would say the word twice) the top left corner was a bare ⊕: Paul's
   screenshot, at 390, with no name in it. This is the same four rules at the
   band the button actually stands in, and the plate reads
   `Reggae / Kingston 1969` again. */
.nu-topstrip > button[data-k="toptab-Where"] .nu-ic{
  display: grid; grid-template-columns: 1.4em minmax(0, 1fr);
  align-items: center; justify-items: start; column-gap: var(--s1);
  inline-size: 100%; }
.nu-topstrip > button[data-k="toptab-Where"] .nu-g{ grid-column: 1;
  grid-row: 1 / 3; justify-self: center; }
.nu-topstrip > button[data-k="toptab-Where"] .nu-vh,
.nu-topstrip > button[data-k="toptab-Where"] .nu-sub2{
  grid-column: 2; min-inline-size: 0; inline-size: 100%;
  text-align: start; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.nu-topstrip > button[data-k="toptab-Where"] .nu-vh{
  position: static; inline-size: 100%; block-size: auto;
  clip: auto; clip-path: none; margin: 0; padding: 0;
  font-size: var(--t2, .82rem); font-weight: var(--fw-label); }
.nu-topstrip > button[data-k="toptab-Where"] .nu-sub2{ display: block;
  color: var(--dim); font-size: var(--t1, .72rem); line-height: 1.15;
  font-weight: 500; }
.nu-topstrip > button[data-k="toptab-Where"][aria-pressed="true"] .nu-sub2{
  color: var(--dim); opacity: 1; }
/* ---- AND THE TAPE TAKES THE GAP (2026-09-06, TABLE.md §18) -----------
   Paul: *"You could put the playback bar to the right of the genre on top top
   if you want."* The tape is a READOUT and not a control, so it belongs with
   identity and navigation rather than with the transport: the strip is
   identity, status and navigation; the bar is controls and only controls. It
   is the same node with the same two lines — the words over a 4px track — and
   it takes the give the auto margin used to hold empty.
   AND IT IS THE ONE CHILD THAT PLACES THE OTHER TWO (2026-09-07, §20). The
   band has no `justify-content`, no `order` and no template: it is three
   children in markup order and one of them is flexible, so whichever two
   nodes sit either side of the tape are AT THE TWO ENDS, at every width, for
   free. That is why the ≡ moving to the left cost this block nothing but the
   direction of its comments — the order it draws is the order ui/eight.js
   appends, and there is nowhere else that fact is written down. */
/* (`.nu-topstrip > .nu-tape` STOOD HERE, 2026-09-06 to 2026-09-08. The tape
   went back down to the bar — Paul: *"Move the playback bar to the bottom
   bar"* — and the box rule went with it, one band down, unchanged in every
   declaration. The two paragraphs above are kept because they are the
   argument that put it here and the measurement that paid for it; what
   retired them is written at `.nu-bar > .nu-tape` and at `stripEl.append` in
   ui/eight.js. The clause about the flexible child doing all the placing is
   the one that OUTLIVED the move: the strip is two children now and the NAME
   is the `1 1 auto`, so the ≡ is still at the start edge for free.) */
.nu-topstrip > #burger{ flex: 0 0 auto; }
/* ---- WHILE THE PLATE IS OPEN THE STRIP IS ITS ≡ AND NOTHING ELSE ------
   (2026-09-07, TABLE.md §20.) Paul: *"The hamburger menu should open higher
   and the menu icon should be a dismiss button."*

   MEASURED, AND IT IS WHY THIS RULE EXISTS RATHER THAN A `z-index` ON THE ≡
   ALONE: the first build lifted `#burger` to `z-index: 60` and playwright
   could not press it — *"`<b id="nu-menuname">Stellate</b> … intercepts
   pointer events"*, 60 times, at both widths. A child's `z-index` competes
   only INSIDE its parent's stacking context, and the strip is one
   (`z-index: 58`), so the ≡ was 60 of 58 and the whole strip was still under
   the plate's 59. The band has to rise, not the button.
   AND WHEN THE BAND RISES, EVERYTHING ELSE ON IT HAS TO GET OUT OF THE WAY,
   or the record's name and the tape paint over the plate they are supposed to
   be behind. So while the plate is open the strip keeps its box and gives up
   everything else: no ground, no rule, its other two children `visibility:
   hidden`, and `pointer-events: none` on the band so a tap on its dead air
   falls through to the plate — or past it, to the one listener that closes on
   an outside tap. THE COST IS THE TAPE, said out loud: with the plate open you
   cannot read the playhead. A plate is a thing you are inside of for one tap.
   `data-menu` IS WRITTEN BY `setMenu` AND BY NOTHING ELSE — the same one
   writer that moves `aria-expanded` on the button — so it is a rendering of
   the state and not a second copy of it. */
.nu-topstrip[data-menu="open"]{ z-index: 60; pointer-events: none;
  background: transparent; border-block-end-color: transparent; }
.nu-topstrip[data-menu="open"] > *{ visibility: hidden; }
.nu-topstrip[data-menu="open"] > #burger{ visibility: visible;
  pointer-events: auto; }
.nu-topstrip button{ min-inline-size: var(--tap); min-block-size: var(--tap); }
/* ---- AND THE ≡ IS A MARK, NOT A PLATE (2026-09-08, TABLE.md §22c) -----
   Paul: *"Get rid of the border around the hamburger and the [x]."*

   IT IS THE SAME SUBTRACTION THE RECORD'S NAME TOOK ONE ROUND AGO, and the
   same sentence justifies it: a border, a ground and a hard shadow are what
   this page draws around a TARGET, and the two ends of the top strip are a
   MARK and a NAME. With the name already plateless the ≡ was the last plate on
   the band, which read as one button parked on a header rather than as the
   band's own two ends. The strip is one surface now.
   NOTHING ABOUT THE TARGET MOVES. `.nu-topstrip button` one line up is still
   the 44px floor in both axes, the `data-say` and the `.nu-vh` word are
   untouched, and `[aria-expanded]` still says what the next press does. A
   control losing its border is not a control losing its size — which is the
   clause `.nu-bar` wrote when its own marks lost theirs (*"Get rid of icon
   borders and shadows just put a light line between"*).
   WHAT SAYS IT IS PRESSABLE is what says it on every other borderless mark in
   this chrome: the ink, and `--flag-tint` under a hand. And while the plate is
   open the ≡ wears × and says `close` (component 13), which is a state the
   band already draws without a box around it. */
.nu-topstrip > #burger{ background: none; border: 0; border-radius: 0;
  box-shadow: none; }
/* ---- AND ON A PHONE IT KEEPS NO GROUND (2026-09-08) ------------------
   Paul: *"When the hamburger is expanded, the X has a background color. It
   doesn't need one; just a plain X is fine."*

   IT IS STICKY HOVER, NOT A STATE. Nothing paints the × — the ground is this
   line, the ordinary pointer-over tint, and a touch screen has no pointer to
   move away: the tap that opens the plate leaves the button hovered until
   something else is touched, so the ≡ becomes an × ALREADY LIT, and the light
   reads as "this thing is on" when the only thing that is on is the plate
   filling the screen behind it. Measured: after a real `tap()` on a touch
   viewport the burger computes `background: rgb(48,68,56)` and holds it.
   `@media (hover: hover)` is the whole cure — a mouse still gets the tint it
   is owed, a finger gets a plain mark. The two tabs beside it take the same
   guard for the same reason, and every :hover in this strip should join them
   as it is touched. */
@media (hover: hover){
  .nu-topstrip > #burger:hover{ background: var(--flag-tint); }
}
/* ---- ...AND SO IS EVERY × (2026-09-08, §22c) -------------------------
   The second half of Paul's sentence. `.nu-sheethead > button` had already
   taken the ground and the shadow off the sheet's close, and left a 1px
   TRANSPARENT border and a `--r1` corner behind — which draws nothing today
   and is a plate the first time anyone gives buttons a border colour. A mark
   that is not a plate should not be carrying a plate's box.
   THE THREE ×'s ON THIS PAGE NOW SAY THE SAME THING: the sheet's
   (`#sheetclose`), the special row's (`.nu-spclose`, which has been
   transparent since it landed and is made explicit here), and the card's
   (`.nu-modalx`, born borderless in §22). One mark, one construction, three
   places — and `--flag-tint` under a hand on all three. */
.nu-sheethead > button, .nu-spclose{
  background: none; border: 0; border-radius: 0; box-shadow: none; }
.nu-sheethead > button:hover, .nu-spclose:hover{ background: var(--flag-tint); }

/* ---- A SHEET'S OWN HEADER, AND THE × IS ITS (§13a.1) ------------------
   One line at the top of whichever panel is open: the sheet's NAME at the
   start, its close at the end. It is IN FLOW and not sticky, because §13a's
   whole sentence is that one band pins at a time and a sheet that pinned its
   own header would be a second fixed box on a page that just deleted one. */
.nu-sheethead{ display: flex; flex-direction: row; align-items: center;
  justify-content: space-between; gap: var(--s3);
  min-block-size: var(--tap); margin-block-end: var(--s3);
  border-block-end: var(--bw) solid var(--rule); }
.nu-sheethead > b{ font: var(--fw-block) var(--t3)/1.2 var(--sans, inherit);
  text-transform: uppercase; letter-spacing: .06em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-sheethead > button{ flex: 0 0 auto;
  min-inline-size: var(--tap); min-block-size: var(--tap);
  background: transparent; border-color: transparent; box-shadow: none; }
.nu-sheethead > button:hover{ background: var(--flag-tint); }
.nu-sheethead > button[hidden]{ display: none; }
.nu-sheethead > b[hidden]{ display: none; }
/* ---- …AND THE PICKER'S HEADER IS THE × AND NOTHING ELSE (2026-09-06) ---
   Paul: *"Get rid of 'where' and the line above and the output that goes
   '33000 BC · 1 record within ten years · Hohle Fels'; leave the close icon.
   Use the new space to move the globe up."* The NAME is hidden by ui/eight.js
   (`sheetName.hidden` — the panel keeps index.html's own visually-hidden <h2>,
   so the sheet is still named for a screen reader), and THE LINE is this rule:
   the header's own bottom border, plus the gap under it, so the globe starts
   directly beneath the ×. It is scoped to `#atlas` because the four viewers
   behind the ≡ are titled sheets and keep both.

   AND THE × KEEPS THE FAR CORNER. `.nu-sheethead` is `space-between`, which
   with the name hidden leaves ONE child and puts it at the START — measured,
   the close landed at x=13 under the reader's left thumb and where the name
   had been. `flex-end` is what "the name at the start, its close at the end"
   means when there is no name. */
#atlas > .nu-sheethead{ border-block-end: 0; margin-block-end: 0;
  justify-content: flex-end; }
.nu-sheethead .nu-ic{ display: flex; flex-direction: row; align-items: center;
  justify-content: center; gap: var(--s1); }

/* ---- THE MENU: FOUR VIEWERS AND THE LOG ------------------------------
   IT IS `.nu-strip-out`'S PLATE ANCHORED AT THE OTHER CORNER, and it is
   written out rather than reusing that class for one reason: the chassis fixes
   `inset-inline-start`, and this strip hangs from the ≡, which is at the
   inline END. Everything else about it is the chassis's own six declarations.
   MENUS NEVER SCROLL INSIDE THEMSELVES is the law and this is the standing
   exception a strip is allowed, argued here as it must be at every strip: five
   rows at 44px is 220px, so the `max-block-size` is never reached on any glass
   this page runs on — it is a ceiling against a future sixth row, not a
   scroller. */
/* IT IS ADDRESSED BY ID AND NOT BY A CLASS, and that is a measurement rather
   than a preference: `.nu-menu` is ALREADY ui/menus.js's chip strip
   (`nu-wchips nu-menu is-chips`), so the first draft of this block turned every
   chip strip on the page into a fixed plate at the top corner —
   test/table.browser.js had it inside the hour, as `SHORT prod.name 265x28`.
   One surface, one name; this one has an id already, so it uses it. */
/* ...AND IT HANGS OFF THE BAR SINCE 2026-09-05 (TABLE.md §13a.1). It hung
   from `--top-h`, which was the top strip's height; the ≡ is the last button
   of `.nu-bar` now, so the plate stands directly above the mark that opens it
   and at the same edge. A menu that opened at the far corner from its own
   button is the one arrangement a thumb cannot follow. */
/* ...AND IT HANGS FROM THE TOP SINCE 2026-09-06 (docs/NAV.md). The ≡ is the
   FIRST button of `.nu-topstrip` since 2026-09-07, so the plate stands
   directly UNDER the mark that opens it and at the same edge — the same
   sentence as before, turned over. A menu that opened at the far corner from
   its own button is the one arrangement a thumb cannot follow. */
/* ...AND THAT EDGE IS THE START SINCE 2026-09-07 (TABLE.md §20). Paul:
   *"Sorry hamburger should be on left."* ONE DECLARATION IS THE WHOLE OF IT —
   `inset-inline-end` became `inset-inline-start` — because "which corner the
   plate hangs at" was never spelt anywhere else: the ≡ is placed by the
   markup (ui/eight.js `stripEl.append`) and the plate by this line, and the
   two agree because both now say START. The inset is the same `--s2` the
   other edge had, so the plate's corner stands the same 5.6px inside the
   glass it stood inside on the right, and the ≡ — which sits at `--gl`, and
   `--gl` is `0px` — overhangs it by exactly that hair at its own TOP-LEFT
   corner. THAT IS THE ASK'S SECOND HALF BUILT: the opener and the × are at
   the plate's own corner rather than floating at the far end of the band.
   AND IT MAKES `.nu-log` CONSISTENT AT LAST. The log panel has been anchored
   to the START since it was written while the row that opens it hung off a
   plate at the END — a door on the right that opened a drawer on the left.
   Both are at the start now and nothing had to move to make that true. */
/* ...AND IT OPENS AT THE TOP OF THE SCREEN SINCE 2026-09-07 (TABLE.md §20).
   Paul: *"The hamburger menu should open higher and the menu icon should be a
   dismiss button."* MEASURED before: the plate's top edge was 47.2px at 390
   and at 320 — the strip's 44px plus a hair — and 47.2px is exactly the room
   there was above it. It takes that room: the plate now begins one hair under
   the safe area and the STRIP IS BEHIND IT, which costs nothing, because
   everything the strip says while the plate is open is said by the plate (the
   record's eight rows are the record's name said in full) except the two
   controls a hand needs, and one of those is the ≡ itself.
   THE ≡ STAYS ON THE GLASS, ABOVE THE PLATE, AND IT IS THE DISMISS. It is
   lifted out of the strip's stacking context (`#burger`, below) so it stands
   over the plate's own top-LEFT corner and reads as the plate's close, which
   is Paul's second sentence built rather than drawn. `.nu-menuname` reserves a
   thumb's width at its START so the app's name never runs under it.
   THE TAPE AND THE RECORD'S NAME GO BEHIND. That is the trade, said out loud:
   with the plate open you cannot read the position. A plate is a thing you are
   inside of for one tap. */
#nu-menu{ position: fixed; z-index: 59;
  inset-block-start: calc(env(safe-area-inset-top, 0px) + var(--s1));
  inset-inline-start: calc(var(--s2) + env(safe-area-inset-left, 0px));
  display: flex; flex-direction: column; align-items: stretch;
  inline-size: min(30ch, calc(100vw - 2 * var(--s2)));
  /* MENUS NEVER SCROLL INSIDE THEMSELVES, and the ceiling is the SCREEN's
     since 2026-09-06. It was `min(70vh, 560px)` and the plate held five rows;
     it holds nine now — six views, the seed's heading and its row, the log —
     which measures 443.3px, and MEASURED at 320 x 568 (test/gutter.js T10's
     own second phone) 70vh is 397.6 and the plate scrolled inside itself by
     45px. The ceiling is what is left of the glass under the strip that opens
     it, so the plate scrolls only when a screen genuinely cannot hold it:
     511px at 568, 787 at 844. */
  /* ...AND IT STOPS AT THE TOP OF THE BAR, which is the other half of opening
     higher (2026-09-07, §20). The plate ran to 834 of an 844px phone before
     this round and covered 40px of the transport with it; a menu row under the
     thumb that is reaching for ▶ is the *"easy to get into a state like that"*
     §18 was called to fix, at the other end of the screen. And it is what buys
     back the FIRST of §18c's three ways out: with the plate ending here there
     is a full-width 50.4px band of glass that is not the plate, at every
     width, so a tap outside has somewhere to land. MEASURED at 390 and 320:
     the plate is 3.2 to 790.4 and the bar's top edge is 793.6. */
  max-block-size: calc(100dvh - env(safe-area-inset-top, 0px)
                       - 2 * var(--s1) - var(--bar-h)); overflow-y: auto;
  overscroll-behavior: contain;
  /* THE BLOCK PADDING IS A HAIR AND THE INLINE PADDING IS TIGHT (2026-09-06,
     §18): measured at 390 x 844 the plate's seventeen rows came to 790px
     against 787.2 of glass and scrolled by three pixels — a scrollbar for
     nothing. The air a plate owes is between its GROUPS, which is what the
     headings and their own padding give it. */
  margin: 0; padding: var(--s1) var(--s2);
  background: var(--panel); color: var(--ink);
  border: var(--bw) solid var(--ink); border-radius: var(--r1);
  box-shadow: var(--shadow); }
#nu-menu[hidden]{ display: none; }
/* THE ROWS ARE IN GROUPS SINCE 2026-09-06 (§18), so these read `#nu-menu
   button` and not `#nu-menu > button`: a view row is a child of
   `.nu-menugroup` now and a selector that still demanded a direct child would
   have taken the ground out from under all six on the day they were grouped. */
/* ---- A ROW IS A LINE, NOT A PLATE (2026-09-08) ------------------------
   Paul: *"The menu should have a very light line between items instead of
   heavy curved lines."*

   THE PLATES WERE THE BASE BUTTON'S, NOT THIS BLOCK'S, which is why they
   survived a rule that already said `border-color: transparent`. `button` in
   this stylesheet carries a 1px edge, `--r1` of corner and the page's control
   SHADOW; the line above cleared the edge's COLOUR and left the corner and the
   shadow, so every row drew as a rounded box with a hard 2px offset under it —
   eighteen of them stacked, which is the "heavy curved lines" exactly.
   SO THE ROW GIVES UP ALL THREE and takes a hairline BETWEEN rows instead: one
   `--rule` line on every row after the first in its group, which is the
   lightest mark this page has and the one `.nu-bar` already uses between its
   own marks. A list of destinations is a list, and what separates its items is
   a line, not a frame each.
   THE GROUPS NEED NO LINE OF THEIR OWN — a sticky heading in small caps stands
   between them and is a stronger break than any rule. */
#nu-menu button{ min-block-size: var(--tap); inline-size: 100%;
  justify-content: flex-start; text-align: start;
  background: transparent; border: 0; border-radius: 0; box-shadow: none; }
/* ...AND EVERY ROW IN THE PLATE STEPS THE SAME (2026-09-08). Paul: *"the
   spacing between some menu items is off."* Measured at 390x844, top to
   bottom: 44 · 44 · 44 · 44 · 44 · 44 · 44 · 44 — then 47 · 53 · 52. The last
   three rows are the odd ones and for two reasons, both of them structural
   rather than typed: `Daylight` and `Set seed` each sit ALONE inside a
   `.nu-menuseed` wrapper, so `button + button` never matched and they took no
   hairline, and the wrapper carried a `--s2` skirt of its own; `logger` is a
   direct child of the plate and was in no group at all. A hand reads that as
   three rows drifting apart at the bottom of a list whose top is perfectly
   even, which is exactly what it is.
   SO THE RULE ASKS WHERE A ROW STANDS, NOT WHOSE CHILD IT IS: a row that
   follows another row takes the line, whether the two are siblings in a group,
   one-per-wrapper, or the plate's own children. Same hairline, same 44, one
   list. */
#nu-menu .nu-menugroup > button + button,
#nu-menu .nu-menuseed > button,
#nu-menu > button:not(:first-child){
  border-block-start: var(--bw) solid var(--rule); }
#nu-menu .nu-menuseed > button{ display: block; }
/* ---- THE ONE ROW OF THIS PLATE THAT IS A LINK (2026-09-08) ------------
   The archive row leaves the app, so it is an `<a>` (a middle click and a
   "copy link address" have to work) — and a plate whose rows all look alike
   must not have one row that looks like a link. It takes the same box the
   buttons take, in the same order, and gives up the two things an anchor
   brings with it: the underline and the visited colour. */
#nu-menu > a.nu-menuold{ display: block; inline-size: 100%;
  min-block-size: var(--tap); box-sizing: border-box;
  color: inherit; text-decoration: none;
  border-block-start: var(--bw) solid var(--rule); }
#nu-menu > a.nu-menuold:visited{ color: inherit; }
@media (hover: hover){ #nu-menu > a.nu-menuold:hover{ background: var(--flag-tint); } }
/* ...AND THE PLATE ITSELF ADDS NOTHING BETWEEN THEM. `<nu-plate>` brings a
   `row-gap` of `--s1` (3.2px measured), which falls between the plate's DIRECT
   children and not between siblings inside a group — so eight grouped rows
   stepped 44 while the three that stand on their own stepped 47. The air a
   plate owes is between its GROUPS, and the headings and their own padding
   already give it; this line only removes the air it was adding between rows
   that a hand reads as one list. Measured after: 44 the whole way down. */
#nu-menu{ row-gap: 0; }
#nu-menu button:hover{ background: var(--flag-tint); }
/* THE VIEW YOU ARE ON IS `aria-current` AND NOT `aria-pressed` (2026-09-06,
   docs/NAV.md: *"The current view is marked in the list"*). With six views one
   of them is ALWAYS the one you are standing on, including the table — and
   `Where` would otherwise have been "pressed" here and "pressed" on the record's
   name at the same moment, which is one state spelled twice. `aria-pressed`
   stays where it means something: the genre plate's own toggle. */
#nu-menu button[aria-current]{ background: var(--hand);
  color: var(--on-fill); border-color: var(--hand); }
#nu-menu button[aria-current]:hover{ background: var(--lamp-hot); }
/* ---- EVERY MARK IN THIS PLATE OCCUPIES THE SAME ADVANCE (2026-09-07) --
   Paul, TABLE.md §20: *"Icons in hamburger should all have same width not
   float left it looks uneven."*

   THIS IS A NEW DECISION AND NOT AN EXISTING LAW APPLIED. Nothing in this
   tree required equal-width marks before today. THE FACT THAT FORCES IT,
   measured on the real app under iPhone 14 emulation before the change was
   made (scratchpad/design/system-2/measure-before.json): `.nu-g` is
   `font-size: 1.2rem; font-family: var(--sym)` with NO WIDTH, and `--sym` is
   a PROPORTIONAL stack — so across the plate's eighteen rows the marks
   advanced anywhere over a 21.3px range and the eighteen words started at
   ELEVEN different x. The eye reads a list off the start edge of its words;
   an edge that moves eleven times is noise the reader subtracts on every row,
   which is what Paul saw before anyone measured it.
   THE FIX IS NOT TO CHOOSE NARROWER MARKS. That would be a promise about a
   font nobody controls, in a stack that falls back differently on every
   platform, for a set of marks that grows every round. The fix is to stop
   asking a mark how wide it is: give it a COLUMN, centre it there, and start
   every word at the column's end. A mark is then a mark and not a
   measurement, and a nineteenth row can carry any glyph at all without moving
   the eighteen above it.
   THE SHAPE IS THE RECORD'S NAME PLATE'S, COPIED ON PURPOSE (this file,
   ~7460: `grid-template-columns: 1.4em minmax(0, 1fr)` with the mark on
   `grid-column: 1; justify-self: center`). That rule was written to stop a
   NAME being clipped and the equal advance was a side effect nobody wrote
   down. It is written down now, and `<nu-menu-row>` in the design system —
   `.nu-elmenurow`, ~9640 — declares the same three tracks with the same
   `1.4em`, so the plate and the element draw one row and not two. `1.4em`
   and not `rem` because the column then scales with the ROW's type, which is
   the same reasoning the spacing ramp is under.
   THE THIRD TRACK IS THE COUNT'S, and it is what a grid costs that the flex
   row did not: `paintIcon` builds one face for every mark on this page in one
   order — glyph, number, word — so the number is the SECOND child and the
   word the third, and auto-placement would have put the log's count where the
   word goes. All three are placed by hand below. An empty third track is
   `max-content` of nothing, i.e. zero, so a row with no count spends only the
   gap; the words still start on the one line, which is the whole ask.
   (`.nu-sub2` — `paintIcon`'s optional second line — is NOT placed here,
   because no row of this plate has one and a rule with no user is the thing
   this file spends every round deleting. If one ever arrives it lands in row
   2 and test/shell.js A6p2 goes red first: *"a Unicode mark and a title-case
   name and NOTHING ELSE"*.) */
/* ---- AND THE GAP IS `--s4` BECAUSE ONE MARK IS A COMPOUND ------------
   MEASURED on the rendered plate at 390 the hour the column was written, mark
   ink against a 19.6px track: `§` 9.2, `♩` 8.7, `⌗` 10.4, `♬` 16.8, `▣` 17.8 —
   every one of the seventeen inside the column — and `▶∞` THIRTY. The play
   mode's face is a transport triangle plus its modifier (`▶∞` loop, `▶|` once,
   `▶⚄` album, fields.js `PLAYMODES`), which is TWO glyphs in a slot the rest
   of the plate spends one on. Centred in a one-mark column it hangs 5.2px
   past each end (see `.nu-g` below for why it is each END and not only the
   word's), and against the `--s2` gap this rule first carried that came out at
   MINUS 5.5px of clearance: `▶∞Loop`, touching, in the only row it could
   happen to. The other seventeen had 5.8 to 10.4px.
   SO THE GAP CLEARS THE WORST OVERFLOW AND THE COLUMN DOES NOT GROW. `--s4`
   is 11.2px here, which leaves the compound mark 6.0px of daylight — MEASURED
   after, beside 12.1px for the next-widest mark (`▣`) and 16.4px for the
   narrowest (`§`) — where `--s2` leaves it minus 0.3 and `--s3` two and a
   half. Nothing got tighter than it was and one thing stopped colliding.
   The alternative was a
   2.2em column, and it was refused: it would have paid for one outlier by
   putting 7.5px of dead air beside all seventeen single marks and by walking
   away from the `1.4em` `<nu-menu-row>` declares. A GAP IS THE RIGHT PLACE TO
   ABSORB AN OUTLIER because it moves every word by the same amount, and the
   words starting on ONE x is the whole ask.
   THE REAL FIX IS UPSTREAM AND IS NOT THIS FILE'S: the plate's own law is
   *"one format for each menu entry: Unicode icon (not emoji) plus title case
   name"* — ICON, singular — and `▶∞` is two. When `PLAYMODES` gives the mode
   one mark, this gap can go back to `--s2` and this paragraph can go. */
#nu-menu .nu-ic{ display: grid;
  grid-template-columns: 1.4em minmax(0, 1fr) max-content;
  align-items: center; column-gap: var(--s4);
  /* AND IT FILLS THE ROW, which is what puts the count at the far END. It
     was reaching that end with `margin-inline-start: auto` on the number — a
     FLEX idiom, deleted below — and an auto margin only ever worked because
     the box it was in happened to be as wide as the button. It is said now. */
  inline-size: 100%; text-align: start; }
/* AND THE MARK'S OWN BOX IS THE TRACK, which is `justify-self: center` said
   so that a tape measure can read it. The two draw the SAME PIXELS — a glyph
   centred in 1.4em either way — but `justify-self` leaves the span sized to
   its own ink, so `test/_system2-measure.cjs` (which measures `.nu-g`'s rect,
   because that is what "icons should all have same width" says) reported the
   21.3px spread of the FONT after the words had already been brought onto one
   line. Paul's sentence is about the icons and not only about the words, so
   the box is made to be what the eye is being told it is. `.nu-elmark` in THE
   ELEMENTS still uses `justify-self` and should take these two declarations
   the next time that block is open; until then the difference is a
   measurement's and not a rendering's, and it is written here so nobody
   "fixes" this back. */
/* ---- AND ALL THREE NAME THEIR ROW, WHICH IS NOT BELT AND BRACES -------
   MEASURED, by test/gutter.js T10 going red the hour this grid was written:
   `["logger","log",213.1,274.8,23.2,44]` — the log's word hanging below the
   bottom of its own button. THE CAUSE IS THE FACE'S BUILD ORDER. `paintIcon`
   appends glyph, then NUMBER, then word; the number is placed in column 3 and
   the word in column 2, so grid's own (sparse) auto-placement put the glyph in
   row 1, the number in row 1 column 3 — and then found its cursor PAST column
   2, which the algorithm may never walk backwards from, and dropped the word
   into ROW 2. One row of the plate was two lines tall and the only row it
   happened to was the only row with a count.
   The old flex row could not have this bug: `order: 1` on the number was
   enough there because a flex line has no second line to fall to. A grid does,
   so a column placement that disagrees with the DOM order must say its row as
   well. `<nu-menu-row>` in THE ELEMENTS needs none of this — its template
   renders mark, word, count in that order — and the difference between the
   two is exactly the difference between styling markup you own and styling
   markup a shared painter builds. */
/* AND IT CENTRES BY FLEX AND NOT BY `text-align`, WHICH IS THE DIFFERENCE
   BETWEEN OVERFLOWING ONE WAY AND OVERFLOWING BOTH. MEASURED at 390 with the
   compound mark: under `text-align: center` `▶∞`'s ink ran 20.9 → 50.9 in a
   box of 20.9 → 40.5, i.e. its left edge sat exactly ON the box's left edge
   and every one of the 10.4 overflowing pixels went toward the word. That is
   what `text-align` does when a line is wider than its box — it clamps at the
   start edge and spills to the end — so the gap would have had to be wider
   than the WHOLE overflow to clear it. A flex centre is UNSAFE alignment: the
   item is centred whatever its size and spills equally at both ends, so the
   same mark now hangs 5.2px each way, the leftward half into the button's own
   7.7px of padding where there is nothing to hit, and the gap has half as
   much to cover. The box itself is still exactly the track (`inline-size:
   100%`), so every mark still measures one width. */
#nu-menu .nu-ic > .nu-g{ grid-row: 1; grid-column: 1;
  inline-size: 100%;
  display: flex; align-items: center; justify-content: center; }
#nu-menu .nu-ic > .nu-vh{ grid-row: 1; grid-column: 2; }
/* THE WORDS ARE VISIBLE HERE, which is the one thing the gutter could never
   afford. `#nu-tray .nu-vh` had to un-hide the word in a 87px column and
   ellipsise it; a 30ch plate holds "Screensaver" whole at body size, so this
   is the same four declarations with nothing to clip. */
#nu-menu .nu-vh{ position: static; inline-size: auto; block-size: auto;
  clip: auto; clip-path: none; overflow: visible; white-space: nowrap;
  margin: 0; padding: 0;
  font-size: var(--t3, 1rem); font-weight: var(--fw-label);
  /* ...AND IT IS TITLE CASE, HERE AND ONLY HERE (2026-09-07, TABLE.md §20).
     Paul: *"Come up with one format for each menu entry: Unicode icon (not
     emoji) plus title case name."* IT IS A PRESENTATION RULE AND NOT A SECOND
     COPY OF THE WORDS: `special.rules.word` is the string "rules" in one
     catalogue, drawn `RULES` by the table's own row label (`text-transform:
     uppercase`) and `Rules` here. A capitalised copy of eighteen words in
     src/copy would be the second owner every round of this branch has been
     spent deleting. `capitalize` and not `capitalize` on a `first-letter`
     pseudo, because `this song` is two words and both of them are the name. */
  text-transform: capitalize; }
/* THE COUNT SITS AT THE FAR END OF ITS OWN ROW. `paintIcon` builds the face in
   one order for every mark on this page — glyph, number, word — which reads as
   "♪ 2 cantor" in a 44px column and as "¶ 3 log" in a 30ch plate, with the
   badge between the picture and the name. This puts it at the end without
   giving this file a second copy of the face's markup.
   IT IS A GRID PLACEMENT SINCE 2026-09-07 (§20) AND WAS `order: 1;
   margin-inline-start: auto`. Both were FLEX idioms and the row is a grid now
   (see `#nu-menu .nu-ic`, above): `order` in a grid only shuffles
   AUTO-placement, which this row no longer uses, and an auto inline margin
   inside a `max-content` track has nothing to push against. One declaration
   says the same thing in the row's own terms, and it says WHICH end rather
   than "as far as it will go". */
#nu-menu .nu-n{ grid-row: 1; grid-column: 3;
  font-variant-numeric: tabular-nums; }
/* ---- THE PLATE'S THREE BLOCKS (2026-09-06, docs/NAV.md) --------------
   The app's name over the views, and `Set seed` over the die and its number.
   Both are HEADINGS and neither is a target: they carry no `--tap` floor and
   no ground, and the two controls under the second one are the ones that were
   in the bar this morning. */
.nu-menuname{ display: block;
  /* AND ITS START CLEARS THE ≡ (2026-09-07, §20): the plate opens under the
     lifted hamburger now, and a name that ran beneath it would be a word with
     a button on top of it. `--tap` exactly, which is the width of the thing
     it is clearing.
     THE RESERVATION MOVED WITH THE ≡ (same day, same section). It was on the
     END while the plate hung at the end; the plate hangs at the START now and
     a reservation left where it was would have been 44px of nothing on the
     right and the app's name under the × on the left. AND IT IS SPELT
     LOGICALLY — the four-value `padding` shorthand it replaces was PHYSICAL
     (`top right bottom left`) and would have reserved the wrong side outright
     in a right-to-left document, while every inset around it is already
     `inline-start`/`inline-end`. */
  padding-block: var(--s2) var(--s3);
  padding-inline: calc(var(--s3) + var(--tap)) var(--s3);
  font: var(--fw-display) var(--t3)/1.1 var(--sans, inherit);
  text-transform: uppercase; letter-spacing: .08em; }
.nu-menusub{ display: block; padding: 0 var(--s3) var(--s1);
  font: var(--fw-label) var(--t1)/1.2 var(--sans, inherit);
  text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }
/* (ITS `padding-block-end: var(--s2)` CAME OFF 2026-09-08 — see "EVERY ROW IN
   THE PLATE STEPS THE SAME". The skirt was the seed row's when the row was a
   field with a heading over it; it is a plain row now and a row's spacing is
   the row's.) */
.nu-menuseed{ display: block; }
/* ---- FOUR GROUPS, AND THE HEADINGS STICK (2026-09-06, TABLE.md §18) ---
   Paul: *"I expected that to live in the hamburger and for the hamburger to be
   nicely organized."* The plate holds seventeen rows now — six views, the
   record's eight, the seed's door and the log — which measures taller than a
   phone, so it scrolls INSIDE ITSELF and each group's heading stays on the
   glass while its own rows are under the thumb.
   THE INNER SCROLL IS A NAMED EXEMPTION AND NOT A DRIFT (DESIGN §3): the law
   it looks like it breaks — *"a field too long for the screen FOLDS … and
   never goes behind a scroll box"* — is about A FIELD'S OPTIONS, where a hand
   is choosing one VALUE out of many and folding by cluster is how it keeps its
   place. This is a NAVIGATION list of seventeen destinations, each of which is
   its own subject; folding it would put every destination two taps away
   instead of one, which is the cost the round was called to remove.
   `position: sticky` needs a scrollport with a real block size, which
   `#nu-menu`'s `max-block-size` + `overflow-y: auto` already is. */
.nu-menugroup{ display: flex; flex-direction: column; align-items: stretch; }
#nu-menu .nu-menusub{ position: sticky; inset-block-start: 0; z-index: 1;
  background: var(--panel); padding-block: var(--s1); }
/* (`.nu-menurow`, `.nu-menuword` AND `.nu-menuface` STOOD HERE, 2026-09-06 to
   2026-09-07 — the record row's two-part line, the word left and its sentence
   right, ellipsised. Paul: *"Come up with one format for each menu entry:
   Unicode icon (not emoji) plus title case name."* There is ONE row shape in
   this plate now and it is the one `icon()` builds for every other row —
   `.nu-ic > .nu-g + .nu-vh` — so three classes and their eight declarations
   are deleted rather than left for a selector nobody can find a printer for.
   The sentences are the rows' accessible names and their `data-say`; the
   record's own faces are one tap away, in the sheets that own them.) */
/* THE SEED ROW IN THE PLATE. It is the same `.nu-seedrow` the bar held — same
   node, same children, same listeners — and what the plate gives it that the
   bar could not is ROOM: 30ch, so the die, the number and the field stand on
   one line at every width and the seed's own wait drops under them on its own
   (the row is `flex-wrap: wrap` and always was). */
/* (`#nu-menu .nu-seedrow`'s TWO RULES STOOD HERE, 2026-09-06 to 2026-09-06.
   The row is back in the bar — Paul: *"Move the dice back into the bottom"* —
   and what stands in the plate is `#seedmenu`, a `.nu-menurow` DOOR that
   presses it. The rule below is kept because the plate would still hold the
   row honestly if anything ever put it back.) */
/* THE 44px FLOOR IN BOTH AXES, AND IT HAD TO BE RE-DECLARED WHERE THE ROW
   LANDED. `.nu-bar button` carried it for the die and the number while they
   stood in the bar; `#nu-menu > button` carries it for the plate's own rows
   (`inline-size: 100%`), and the seed row's two targets are NEITHER — they are
   nested one level in. MEASURED the hour the row moved, by test/gutter.js T10:
   `["rewrite", 44, 33.7]` — a thumb tall and two thirds of a thumb wide, which
   is the same finding that block already records about this glyph ("a glyph
   brings 10-30px"). */
#nu-menu .nu-seedrow button{ min-inline-size: var(--tap);
  min-block-size: var(--tap); }
.nu-viewcut{ flex: 0 0 auto; inline-size: 100%;
  border: 0; border-block-start: var(--bw) solid var(--rule);
  margin: var(--s2) 0; }

/* ---- THE BAR ----------------------------------------------------------
   THE GENRE TOOK THE GIVE UNTIL 2026-09-06 AND THE TAPE TAKES IT NOW
   (docs/NAV.md, Paul: *"So now bottom row is pure play controls"*). The
   paragraph this replaces is kept because it is a measurement of the row it
   describes: "the name plate is the one thing in this row whose content has no
   bound ('Gregorian chant' over 'Rome 600'), so it is the `1 1 auto` with
   `min-inline-size: 0` and an ellipsis; the seed row and the transport are
   `0 0 auto`… Measured at 320: the plate gets 116px, the seed row 88, the
   transport 132 — nothing under the 44px floor and no wrap."
   THE ROW IS THREE MARKS AND A READOUT NOW. The transport is `0 0 auto` at the
   START (the options' door, the voicing, ▶ — three facts about the next press
   of ▶, in the order v263 put them and under a thumb), and the TAPE is the
   `1 1 auto` at the end, because a position indicator is the one thing here
   that can use whatever width is left and is not a target at all.
   IT IS AN OPAQUE PLATE WITH A RULE ON TOP, not a translucent overlay: the
   page scrolls under it and a see-through bar over moving type is the one
   thing this page's flat-paint law has never allowed. */
.nu-bar{ position: fixed; z-index: 58;
  inset-inline: 0; inset-block-end: 0;
  display: flex; flex-direction: row; align-items: center; flex-wrap: nowrap;
  gap: var(--s2);
  /* THE HEIGHT IS THE TOKEN AND NOT WHATEVER THE CONTENT COMES TO, which is
     the whole of what `--bar-h` promises the page: `body`'s
     `padding-block-end` is arithmetic on it, so a bar that came out a pixel
     taller than its own token would be a pixel of the page under the bar and
     nothing would say so. `block-size` and not `min-block-size` — measured,
     the row's tallest control renders 45.18px against the 44px floor and the
     bar came to 51.38 against a token of 50.39; the box is the token, the
     control keeps its 45.18, and the difference is spent inside the padding
     where a transparent button has nothing to clip. */
  block-size: var(--bar-h);
  /* …AND THE END OF THE ROW GETS AIR (2026-09-08). Paul: *"Give me some
     padding on the bottom right of the bottom bar."* `--gl` is the gutter and
     it is `0px` — *"the views run to the glass"* — which is right for a table
     that fills the width and wrong for the last control in a band: the volume
     ended flush against the edge of the screen, with the thumb's own travel
     running off the glass. `--s4` is the air this page pays between a control
     and an edge everywhere else. The START of the row keeps the gutter, because
     the ≡ is a corner target and Fitts is the argument for leaving it in the
     corner — the same argument that just moved play to the other one. */
  padding: var(--s1) var(--s4)
           calc(var(--s1) + env(safe-area-inset-bottom, 0px)) var(--gl);
  background: var(--panel); color: var(--ink);
  border-block-start: var(--bw) solid var(--ink); }
/* (THE GENRE PLATE'S TWO RULES AND THE SEED ROW'S FOUR STOOD HERE, 2026-09-05
   to 2026-09-06. The plate is `.nu-topstrip`'s `1 1 auto` now, with the same
   two declarations at the same two selectors one band up; the seed row is a
   row of `#nu-menu` and gets its width back, so the `flex-wrap: nowrap` this
   file gave it for a bar — "a bar has width and no height to spend" — is
   exactly the declaration it no longer needs. `.nu-count` moved with neither:
   it is inside the tape, below.) */
.nu-bar > .nu-bartp{ flex: 0 0 auto; display: flex; flex-direction: row;
  align-items: center; gap: var(--s1); }
/* PLAY IS TWICE THE TARGET (2026-09-08). Paul: *"Put the play button on the
   bottom left and make it 2x wide for fitt's law, then the other buttons."*
   ui/eight.js puts it first in the row and this gives it the width: `--tap`
   is the floor for every isolated control and this is the one control that
   earns two of them. It is a WIDTH and not a height — the band is `--bar-h`
   and nothing in it may grow taller — so what doubles is the axis a thumb
   travelling along the foot of the screen actually crosses.
   AT 320 IT IS STILL AFFORDABLE: the row's budget there is the ≡ (44), this
   (88), the voicing (44), the die (44), undo and redo (88) and the room —
   which is why the tape's floor and the 1px seams below 380 were measured in
   the first place. */
.nu-bar > .nu-bartp > #play{ min-inline-size: calc(var(--tap) * 2); }
/* ---- THE DIE AND THE ROOM, IN THE BAR (2026-09-06, TABLE.md §18) ------
   Paul: *"Move the dice back into the bottom"* and *"…integrate them into the
   bar with only a pop-up."* The arithmetic, measured on the rendered page at
   320: the bar's content box is 312px; `.nu-bartp` is three 44px marks and
   two 1px seams = 134; the seed row is the die (44) and its number (44) = 88;
   four gaps at 8 (1 below 380) = 4; which leaves 86 for the room. A track of
   86px reads a percent and takes a thumb, so nothing here needs opening to be
   set — and the ONE pop-up left in the bar is `#playops`, holding the mode
   and the take.
   THE SEED ROW DOES NOT WRAP HERE. In the plate it had 30ch and could spend a
   line; a bar has width and no height at all, so it is `nowrap` and its two
   targets keep their 44px floor. */
.nu-bar > .nu-seedrow{ flex: 0 0 auto; flex-wrap: nowrap; gap: var(--s1); }
/* ---- UNDO AND REDO, IN THE FOOT (2026-09-08, TABLE.md §24) ------------
   Paul: *"We can move undo, redo to the bottom nav and make them global."*

   THE BUDGET AT 320, WHICH IS WHERE IT IS TIGHTEST and where every number in
   this block has always come from: the bar's content box is 312px; `.nu-bartp`
   is two 44px marks and a 1px seam (89); the seed row is the die alone (44);
   THIS pair is another two marks and a seam (89); four seams at 1px (4); the
   room is not drawn under a thumb. That leaves **86px of tape** — against the
   70px its own note names as the floor (*"a track under 70px cannot show a bar
   of an 88-bar record moving"*), so the readout survives the two new marks
   with 16px to spare. At 390 the gaps are 8 and the tape gets 130.
   THEY STAND AFTER THE DIE AND BEFORE THE TAPE, which is the row's own
   argument about groups: the transport is what you press to HEAR, the die and
   these two are what you press to CHANGE, and the tape is the readout that
   ends the row. Three groups of controls in the order a hand uses them, then
   what they did. */
.nu-bar > .nu-baredit{ flex: 0 0 auto; display: flex; flex-direction: row;
  align-items: center; gap: var(--s1); }
/* (`.nu-bar > .nu-seedrow > #seedval` STOOD HERE. The number is deleted from
   the page on 2026-09-07 — Paul: *"Get rid of seed number too"* — and the row
   is the die and the hidden field it becomes.) */
.nu-bar > .nu-seedrow > #seedin{ flex: 0 0 auto; inline-size: 6ch; }
/* ---- AND THE ROOM IS THE SAME SIZE AT EVERY WIDTH (2026-09-07, §20) ----
   Paul: *"Make volume less wide."* MEASURED before, on the rendered bar: the
   fader was the row's `1 1 auto` and took whatever the marks left — 89.9px at
   320, 149.3 at 390 and **1,036.3 at 1280**, which is a metre of slider for a
   number between 0 and 100.
   THE BUDGET AT 320, WHICH IS WHERE IT IS TIGHTEST AND WHERE THE NUMBER COMES
   FROM: the bar's content box is 312px; `.nu-bartp` is two 44px marks and a
   1px seam (89); the seed row is the die alone (44); three seams at 1px below
   380 (3). That leaves 176 — so 176 is the most the room could have, and it is
   not what it should have: a fader as wide as half the phone is a control
   claiming to be the most important thing in the bar. 112px is what it takes,
   which is a ~64px track after the chassis's own margins and the percent
   beside it — wider than the 42.5px track 320 shipped, narrower than 390's
   101.9, and the SAME at 1280 as on a phone, which is the whole point.
   THE SLACK GOES BETWEEN THE GROUPS AND NOT AFTER THEM. `margin-inline-start:
   auto` holds the room at the far end, so the transport and the die read as
   one group under a left thumb and the room as its own thing at the other end,
   and the air the two departed marks left is between them rather than trailing
   off the end of the row. */
/* (`margin-inline-start: auto` CAME OFF THIS RULE ON 2026-09-08, and the
   paragraph above keeps its arithmetic because the arithmetic is still true.
   The TAPE is the bar's `1 1 auto` from today (below), and a flexible child
   between the groups does the auto margin's whole job by being there — the
   same sentence `.nu-topstrip` wrote when the tape arrived on IT and its own
   auto margin came off. An auto margin beside a flexible child eats the free
   space that child was asked to take.) */
.nu-bar > .nu-vs-wide{ flex: 0 0 auto; inline-size: 112px; }
/* ---- AND THERE IS NO ROOM FADER UNDER A THUMB (2026-09-08, §22) -------
   Paul: *"Just get rid of the volume control on mobile if it can't work.
   Leave it on desktop."*

   IT CANNOT WORK, AND THE REASON IS IN THE ENGINE RATHER THAN IN THIS FILE.
   A phone gets the WAV-FIRST route (engine/faust/live/live.js: `isMobile ||
   isSafari` -> a real <audio> playing rendered segments), and that route has
   no live output graph to hang a gain on. It says so itself, in as many
   words: *"element.volume is kept for engines that honor it (immediate) and
   the bake covers the one that does not: iOS returns 1 from a volume write"*
   — so on the platform this rule is written for, the level rides `mvol` into
   the next bar FED to the worker and lands when the segments already in the
   queue have played out. Seconds, not frames. A fader you drag and hear
   nothing from is not a slow fader, it is a control that lies about being
   one, and the honest thing is not to draw it.
   WHAT A THUMB USES INSTEAD is the phone's own hardware volume, which is
   always there, is never seconds late, and is the control a person reaches
   for on a phone anyway.
   `(pointer: coarse)` IS THE PREDICATE AND IT IS THE PAGE'S OWN. It is what
   `src/menus/pick.ts coarse()` already means by *"is there a thumb on this
   screen"* — the one owner of that question — so this rule does not open a
   second definition of mobile beside it. It is not the same question as the
   engine's UA test, and the difference is a touch-screen laptop: it keeps the
   desktop's instant fader in the ENGINE and loses it from the BAR. That trade
   is taken on purpose, because the alternative is asking the engine a
   question it cannot answer until the first press of play.
   THE CONTROL IS STILL IN THE DOCUMENT. `display: none` takes it off the
   glass and out of the accessibility tree; it does not delete `#vol`, so the
   store, the restore, `nukernel/desk-gate.js`'s min/max read and the board's
   own `#vol2` (the same 0..100 value, ui/engineer.js `listening`) are all
   exactly as they were. One store, one fewer view of it. */
/* ---- AND THERE IS NO ROOM ON A NARROW GLASS EITHER (2026-09-08, §24) --
   MEASURED, and it is a regression this round introduced: with undo and redo
   in the bar, a 320px FINE-pointer page put the room fader at x=229 with a
   width of 112 — `r=341` against a 320px viewport, so 21px of it hung off the
   screen and the page could scroll sideways. The coarse rule below never fired
   there (a desktop at 320 is a fine pointer), and the budget the edit pair was
   argued against assumed the fader was absent.
   320 IS THE NUMBER BECAUSE THE ARITHMETIC IS: content 312, less two 44px
   transport marks and a seam (89), the die (44), the edit pair and its seam
   (89), four seams (4) — 226 spent before the tape has a pixel. 112 more does
   not fit and never did; what changed is that there are now two more marks
   ahead of it.
   IT IS THE ROOM THAT GOES AND NOT THE TAPE, for §22's own reason: the fader
   is a control with a hardware equivalent on every machine that has one, and
   the tape is the only thing on the page that says how much record is left.
   AND THE BREAKPOINT IS 460 AND NOT 380, WHICH IS ALSO MEASURED. 380 stopped
   the overflow and left the tape 27px wide at 390 on a mouse — under the 70px
   its own note names as the floor (*"a track under 70px cannot show a bar of
   an 88-bar record moving"*), which is a readout that is present and useless.
   THE SUM: the bar's fixed children are the transport (91.2), the die (45),
   the edit pair (91.2) and four 8px gaps (32) = 259.4. Add the room's 112 and
   371.4 is spent, so the tape reaches its 70px floor at 441.4 and clears it at
   460. Below that the room is not drawn and the tape takes what the fader
   would have — 130px at 390.
   A NARROW DESKTOP STILL HAS A ROOM: `.nu-listen` on the mix board is hidden
   only under `(pointer: coarse)`, so a fine pointer in a small window keeps
   the monitor one tap away. Nothing loses the control; the BAR loses it. */
@media (max-width: 459px) {
  .nu-bar > .nu-vs-wide{ display: none; }
}
@media (pointer: coarse) {
  .nu-bar > .nu-vs-wide{ display: none; }
  /* ...AND THE BOARD'S OWN MONITOR GOES WITH IT (2026-09-08). The paragraph
     above ends *"one store, one fewer view of it"*, and for a few hours that
     was literally true and quietly wrong: `#vol2` on the mix board is the
     SAME 0..100 store with the SAME engine underneath it, so a phone still had
     a fader that answered seconds late — one tap further away, where a hand
     would find it after giving up on the bar and conclude the box was broken
     rather than that this control cannot work here.
     IT IS THE MONITOR AND NOT A CHANNEL FADER, which is the whole of why this
     rule names `.nu-listen` and not `.nu-vs-tall`: the board's channel faders
     are the same chassis and are a different KIND of value. A channel fader
     writes a mix offset into the RECORD and reaches the ear through the
     recompile, exactly like every other written number on this page — it works
     on a phone, it is not late, and taking it away would delete real control
     over the mix. Only the room is refused, and only because it is the one
     value the WAV-first route cannot move in time. */
  .nu-listen{ display: none; }
}
/* ---- THE ROOM, LYING DOWN (2026-09-06, TABLE.md §18) ------------------
   The same chassis the mix board and the gutter have used since 2026-08-27
   (`ui/engineer.js vchassis`), turned ninety degrees: the <input> is still the
   keyboard and screen-reader channel at `opacity: 0`, the TRACK still owns the
   pointer, and `touch-action: none` is still on the control and only on the
   control, so a drag on the fader never becomes a scroll of the page. What
   these rules change is which axis the fill and the thumb run along — and the
   value's readout, which stands beside the track here rather than under it,
   because a bar has no second line to give it. */
.nu-vs-wide{ display: flex; flex-direction: row; align-items: center;
  gap: var(--s1); min-inline-size: 0; }
.nu-vs-wide .nu-vs-track{ inline-size: auto; flex: 1 1 auto;
  min-inline-size: 0; block-size: var(--sl-trough); margin-inline: 12px;
  margin-block: 0; }
.nu-vs-wide .nu-vs-track::before{ inset: -14px -10px; }
.nu-vs-wide .nu-vs-fill{ inset: 0 auto 0 0; block-size: auto;
  inline-size: calc(10px + (100% - 20px) * var(--v, 0)); }
.nu-vs-wide .nu-vs-thumb{ inset-block-start: 50%; inset-block-end: auto;
  left: calc((100% - var(--sl-grab)) * var(--v, 0));
  transform: translateY(-50%);
  inline-size: var(--sl-grab); block-size: var(--sl-cap); }
.nu-vs-wide .nu-vs-thumb::after{ inset: 7px 50% 7px auto;
  block-size: auto; inline-size: var(--bw-hard);
  margin-top: 0; margin-inline-end: calc(var(--bw-hard) / -2); }
.nu-vs-wide .nu-vs-in{ inset: 50% -10px auto -10px;
  transform: translateY(-50%); inline-size: calc(100% + 20px);
  block-size: 44px; writing-mode: horizontal-tb; direction: ltr; }
.nu-vs-wide .nu-vs-val{ flex: 0 0 auto; }
/* ---- THE TAPE (2026-09-06, docs/NAV.md) ------------------------------
   Paul: *"Make a play status tape position indicator that incorporates the
   beat countdown on the bottom right."*

   IT IS TWO LINES AND NOT ONE, WHICH IS A MEASUREMENT. Side by side — the
   drawing's own `▓▓▓▓▓░░░░░░  3 · bar 12/88` — the track and the words share
   161px at 320 (296 of column, less three 44px marks and three 1px seams), and
   a track under 70px cannot show a bar of an 88-bar record moving. Stacked,
   the words take the line and the TRACK TAKES THE TAPE'S WHOLE WIDTH, which is
   the one thing it is for: how much record is left, read at a glance.
   Both fit inside the bar's 44px of content — the line is `--t1` over a 4px
   track with a hair between them — so the tape costs the row no height.
   NOTHING IN IT IS A TARGET. It is a readout: no `--tap` floor, no ground, no
   border, and its accessible name is the whole sentence (`tape.aria`). */
/* ---- AND THE TAPE IS BACK IN THE BAR (2026-09-08, TABLE.md §22) -------
   Paul: *"Move the playback bar to the bottom bar."* The line this replaces
   read: "(`.nu-bar > .nu-tape` STOOD HERE — the tape is the top strip's since
   2026-09-06, §18, and its box rule went up with it.)" It is the same four
   declarations that went up, come back down, because the tape's BOX has never
   depended on which band it stands in: a column, centred, its two lines a
   `--s1` apart, and `min-inline-size: 0` so the words ellipsise rather than
   push the room off the end.
   IT IS THE ROW'S ONE `1 1 auto` and therefore the row's whole placer: the
   transport and the die are `0 0 auto` at the start, the room `0 0 auto` at
   the end (and absent under a thumb), so the tape is what holds them apart.
   MEASURED ON THE RENDERED BAR, at the three widths this page is argued at:
   **242.6px of tape at 390** and **184 at 320**, both with no room fader
   (`(pointer: coarse)`), and **1,015 at 1280** with the fader at the end. The
   390 figure is a hair under the 249.9 the tape had in the strip and stands on
   the band the ▶ that moves it is on, which is the trade said in one line.
   `.nu-bartp` renders 91.2 and the seed row 45 at every width, so the tape is
   simply what is left.
   THE FOUR RULES BELOW ARE THE TAPE'S OWN PARTS and stay unscoped on purpose:
   they describe a readout, not a place — which is the sentence that let this
   box rule travel twice without them moving once. */
/* AND IT STANDS THE FULL HEIGHT OF THE BAND (2026-09-08). The bar is
   `align-items: center`, so the tape was as tall as its own words — MEASURED
   11.5px inside a 50.4px bar, which is why the ground below could only ever be
   a stripe. `align-self: stretch` is the one declaration that makes the
   readout's box the band's box; the words stay centred in it by this rule's
   own `justify-content`. */
.nu-bar > .nu-tape{ flex: 1 1 auto; min-inline-size: 0; align-self: stretch;
  display: flex; flex-direction: column; justify-content: center;
  gap: var(--s1); }
.nu-tapeline{ display: flex; flex-direction: row; align-items: baseline;
  justify-content: flex-end; gap: var(--s2); min-inline-size: 0; }
.nu-tapesay{ font: var(--num-fw) var(--t1)/1 var(--mono, monospace);
  font-variant-numeric: tabular-nums; color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE COUNTDOWN, INSIDE THE TAPE. It keeps its own two-part face — the number
   in the machine weight, its unit under it — and lies down into one line here,
   because the tape's line is a line and not a column. Empty is not drawn,
   which is `.nu-count`'s own `:empty` rule and the reason there is one state
   to keep honest instead of two. */
.nu-tape .nu-count{ flex: 0 0 auto; flex-direction: row;
  align-items: baseline; gap: var(--s1); padding-block: 0; }
/* ---- THE TAPE IS THE HEIGHT OF THE BAR (2026-09-08) -------------------
   Paul: *"Make the play bar 100%… full height I guess I meant, it's just a
   little orange bar."*

   IT WAS A 4px TRACK UNDER THE READOUT, which is a progress bar drawn as a
   footnote to itself: at 1280 the tape is 917px of width and was spending FOUR
   PIXELS of the bar's 44 on the one thing in the band that moves. Measured on
   the rendered page — `.nu-tape` 917.3 × 44, `.nu-tapetrack` 917.3 × 4.
   SO THE TRACK IS THE TAPE'S OWN GROUND, inset behind the line rather than
   stacked under it: `position: absolute` over the tape's whole box, the words
   and the countdown drawn ON it. Where a record is is now a REGION of the bar
   filling up, which is what a hand reads at a glance from across a room, and
   the 4px sliver is gone rather than doubled.
   THE READOUT MUST SURVIVE THE FILL, so the fill is the tint and not the
   colour, with the sounding edge kept solid: `--clock-tint` behind the text
   (the same token `mark` uses to highlight a word without hiding it) and a
   2px `--clock` rule at the playhead. Nothing here is orange any more than the
   rest of the band is — `--clock` is the skin's, and `skins/paper.css` swaps
   it with everything else (§20's skin law).
   THE PARTS STAY UNSCOPED, which is this block's own sentence: they describe a
   readout, not a place, and the tape has already travelled twice without
   them. */
.nu-tape{ position: relative; }
.nu-tapetrack{ position: absolute; inset: 0; inline-size: 100%;
  min-inline-size: 0; background: var(--well); border-radius: var(--r2);
  overflow: hidden; pointer-events: none; }
.nu-tapefill{ display: block; block-size: 100%; inline-size: 0;
  background: var(--clock-tint);
  border-inline-end: 2px solid var(--clock); }
/* …AND THE WORDS RIDE OVER IT. Both children of the tape are lifted out of the
   ground's stacking order; the track is the only thing behind them. */
.nu-tapeline, .nu-tape .nu-count{ position: relative; z-index: 1; }
/* ---- AND AT 320 THE ROW SPENDS ITS AIR RATHER THAN A MARK (2026-09-05) --
   The ≡ joined the bar as its last button (TABLE.md §13a.1) and MEASURED at
   320 x 844 the row wanted 334.4px of a 320px screen: seven marks at the 44px
   floor is 308, and the six gaps between them were 26.4 of the 12 that were
   left. A bar that overflows is a mark off the side of the phone, and the ≡ is
   the one control that is on the screen in every state of this page.
   THE FLOOR IS NOT NEGOTIABLE AND THE GAP IS. Every mark keeps its 44px in
   both axes — that is the rule this page has held since the marks landed, and
   the gutter's own gate reads it — so what gives at the narrow end is the air
   between them: 1px, which is a seam and not a gap, and only below 380 where
   there is nothing else to spend. Measured after: 314px in a 320px row. */
@media (max-width: 379px){
  .nu-bar{ gap: 1px; }
  .nu-bar > .nu-bartp{ gap: 1px; }
  .nu-bar > .nu-baredit{ gap: 1px; }
  .nu-topstrip{ gap: 1px; }
  /* (`.nu-bar > .nu-seedrow { gap: 1px }` AND `.nu-bar #seedval {
     padding-inline: 0 }` STOOD HERE, 2026-09-05 to 2026-09-06, and both were
     about the same 320px arithmetic: "seven marks at the 44px floor is 308,
     and the six gaps between them were 26.4 of the 12 that were left." The bar
     holds THREE marks now — the seed row is behind the ≡ and the genre plate
     is one band up — so 320 has 296 of column against 132 of ink, and the tape
     takes the 161 that are left. The seam is kept for the three that remain
     and for the strip, which is two marks and a name with no bound.) */ }
/* THE 44px FLOOR IS BOTH AXES HERE, AND THE OTHER ONE WAS LOST IN THE MOVE.
   `#nu-tray button, #boardtabs button, .nu-decktabs > button { min-inline-size:
   var(--tap) }` was one rule for three surfaces and the stripe's selector went
   with the stripe; the block size is declared below and the INLINE size was
   not, so the four marks Paul named — *"genre, dice, playstop along the
   bottom"* — measured 29.3 to 33.7 CSS px wide at 320, 390 and 430, and
   test/gutter.js T10 had it in both axes within the hour. It is the same
   argument the deleted rule carried: "a glyph brings 10-30px, so `←` in a
   2px-padded button is a 20px target — under half the floor." The room is
   there: the six visible marks total 299.3px in a 304px row at 320. */
.nu-bar button{ min-block-size: var(--tap); min-inline-size: var(--tap);
  background: transparent; border-color: transparent; }
.nu-bar button:hover{ background: var(--flag-tint); }
/* ---- NO BORDER, NO SHADOW, A HAIRLINE BETWEEN (2026-09-07, §20) --------
   Paul: *"Get rid of icon borders and shadows just put a light line between."*

   THE BORDERS WERE ALREADY TRANSPARENT (the rule above) and the SHADOW was
   not: `button` carries the page's control shadow and the bar's marks
   inherited it, so three transparent-bordered buttons still sat in three
   little wells. Both are said explicitly here rather than left to a cascade
   nobody can read, and what replaces them is one `--rule` hairline between
   adjacent controls — the token DESIGN §1 calls "a rule is a HAIRLINE; a frame
   is a plate, and a plate is rare".
   THE FLOOR IS UNTOUCHED. A control losing its border is not a control losing
   its size: `min-block-size` and `min-inline-size` above are still `--tap`,
   and the 1px the rule adds is a border on the box, not a bite out of it.
   IT IS DRAWN ON THE SECOND CHILD ONWARDS, at both levels the bar nests: the
   three groups (`.nu-bartp`, `.nu-seedrow`, the room) and the marks inside the
   first two. A leading rule on the row's first control would be a line against
   the edge of the screen.
   (THIS IS DELIBERATELY MINIMAL. The design system Paul has already described
   — *"a mixing board/LED with musical aesthetics and green phosphor/vector
   vibes"* — restyles this band whole; what is written here is the STRUCTURE of
   the treatment, in tokens, so that pass has one thing to replace and not
   three.) */
.nu-bar button{ box-shadow: none; }
.nu-bar > * + *,
.nu-bar > .nu-bartp > button + button,
.nu-bar > .nu-seedrow > button + button{
  border-inline-start: var(--bw) solid var(--rule); }
.nu-bar button[aria-pressed="true"]{ background: var(--hand); color: var(--on-fill);
  border-color: var(--hand); }
/* ---- AND A MARK WITH NOTHING TO DO LOOKS LIKE IT (2026-09-08) ---------
   Paul: *"don't give me redo available unless I'd used undo."*

   IT WAS ALREADY REFUSED AND IT DID NOT SAY SO. `paintEdit` (ui/eight.js) has
   set `aria-disabled="true"` on `#tredo` since the day it was built and the
   stack is right — `DocUndo.canRedo` is `fwd.length > 0`, and `fwd` is filled
   by an undo and emptied by every op — so the button was announcing "refused"
   to a screen reader and NOTHING to an eye. Measured on the rendered bar, at
   390 and at 1280: `#tundo` and `#tredo` at rest read `aria-disabled true`
   with `opacity: 1` and the identical colour to the live marks.
   THE REASON IS THAT THE TREATMENT WAS CLASS-SCOPED. §15's grey is written for
   `.nu-elbtn[aria-disabled]`, `.nu-elseg`, `.nu-elcell`, `.nu-elmenurow` — and
   the bar's marks are bare `<button>`s (`mkBtn`), so not one of those rules
   could reach them. This is the same treatment, in the same tokens, said for
   the band that had been missed: dimmed and dashed, never `disabled`, so the
   tap still lands and `data-why` still answers.
   IT IS THE WHOLE BAR AND NOT THE TWO MARKS, because every mark in this band
   that can be refused deserves the same answer. */
.nu-bar button[aria-disabled="true"]{
  border-style: dashed; border-color: var(--legend-faint);
  color: var(--legend-faint); background: transparent; opacity: .55; }
.nu-bar button[aria-disabled="true"]:hover{ background: transparent; }
/* THE GENRE PLATE READS AS TWO LINES, the wiki name over the place and year,
   which is exactly what it read as in the gutter's foot — the same
   `.nu-vh` + `.nu-sub2` pair, the same one writer (`nameRecord`), turned from
   a column into a row that has width to spend. */
/* (THE GENRE PLATE'S SIX RULES STOOD HERE and are `.nu-topstrip`'s now,
   2026-09-06, §18 — the button left this band on 2026-09-06 and these did
   not follow it, which is why the record had no name in the corner. A
   selector left behind is not dead code: it is a control that stops working
   in a place the file still describes.) */
/* THE DIE STACKS TIGHT, and this is the rule `#nu-tray #rewrite` was: `#rewrite`
   is the one button in the chrome with a child OUTSIDE its `.nu-ic` — the
   literal space that keeps the accessible name from joining into "rewrite5"
   with the stylesheet off — and a flex container drops a whitespace-only child
   without removing it from the DOM. Same 44px, same reason, one selector
   shorter. */
#rewrite{ display: flex; flex-direction: row;
  align-items: center; justify-content: center; }
/* (`.nu-baropts` AND ITS FIVE RULES STOOD HERE, with `.nu-bartp { position:
   relative }` under them — the play fold and the box it anchored against. Paul,
   2026-09-07: *"Bottom bar: get rid of gear and move those functions into the
   menu."* The fold, its door and its two children's home are deleted; the mode
   and the take are rows of the hamburger's HOW IT PLAYS block. The bar has no
   pop-up at all now, which is the first time that has been true since it was
   drawn.) */

/* ---- AND THE 44px FLOOR THE GUTTER'S OWN RULE USED TO CARRY -----------
   `#nu-tray button, #boardtabs button, .nu-decktabs > button{ min-inline-size:
   var(--tap) }` was one rule for three surfaces and the first selector is
   deleted with the stripe. The other two are not: "an <svg> brought its own
   40px of width; a glyph brings 10-30px, so `←` in a 2px-padded button is a
   20px target — under half the floor." The chrome's own three boxes declare it
   themselves — `.nu-top > button`, `#nu-menu > button` and `.nu-bar button`,
   each above — and the third of those was MISSING for an afternoon, which
   test/gutter.js T10 measured at 29.3-33.7px on the four marks Paul named. */
#boardtabs button, .nu-decktabs > button{ min-inline-size: var(--tap); }

/* ---- A PANEL AS A SHEET ----------------------------------------------
   §10a: *"The hamburger (≡): Score, Video, Screensaver, Export — the viewers"*
   and *"genre (opens WHERE, the globe, as a sheet — it is a picker)"*. Five of
   the six panels are sheets and `#pan-band` never is, because the table is the
   page and not a view you opened.
   IT IS ONE ATTRIBUTE AND THE PANEL DOES NOT KNOW IT IS ONE. `showTab` writes
   `data-sheet` on the open non-Band host; nothing in ui/atlas.js, ui/video.js,
   ui/screensaver.js or `deckBlock` changed a line.
   IT IS IN FLOW AND NOT `position: fixed`, AND THAT WAS MEASURED. The first
   draft made a sheet a fixed box with its own scroll — which is what "over the
   table" reads like — and three things went with it: `window.scrollY` stopped
   moving on four of the six surfaces, so `tabScroll` (the anchor law's whole
   substance: *"Stop scrolling when I touch the page in any way!"*) remembered
   0 for every one of them and shell A6e skipped at all four widths with "fewer
   than two scrollable tabs"; `#app`'s own scroll and the `putPanes` sideways
   restore were measuring a box that no longer scrolled; and NOTHING WAS
   BEHIND IT ANYWAY, because `showTab` puts `data-off` on every panel but the
   open one, so the table is not on the page while a sheet is up. A fixed box
   over a `display: none` table is a fixed box over the ground. So the
   attribute stays — it is what the stylesheet and the gates read to know a
   panel is a SHEET rather than the page — and what it declares is the one
   thing a sheet owes: it fills the screen it opened over, with the chrome's
   two bands reserved by <body> exactly as they are for the table. */
.nu-pan[data-sheet]{ min-block-size: calc(100dvh - var(--top-h)
                                          - var(--bar-h) - var(--s2)); }

/* ======================================================================
   EXPLORE IS TWO FIXED HALVES, STACKED (2026-09-08)
   ======================================================================
   Paul: *"Make the genres plus search take up half the explore screen and the
   globe the other half. Make the globe reach 100% top to bottom. Keep those
   sizes fixed."* Then, on seeing the first cut side by side: *"BTW when I say
   split in two I mean stacked top to bottom."*

   SO IT IS TWO BANDS AND NOT TWO COLUMNS, at every width. The globe takes the
   top half and fills it top to bottom; the search and the 510-row index take
   the bottom half and scroll inside it. (The side-by-side version stood here
   for one round. It was wrong twice over: it read "half the screen" as a
   vertical cut, and on a 390px phone it made halves of 187px — an earth the
   size of a thumbnail beside names clipped to two words.)

   IT WAS A SCROLLING DOCUMENT AND IT IS A SPLIT NOW, which is the point of the
   ask. The globe was a full-width block with the index BELOW it, so the two
   things this view is for could never be on the glass at once: you spun the
   earth, scrolled past it to read a name, and the earth was gone. Measured
   before, at 1280x900: the globe 1280x603 and the index starting at y=673,
   with 73px of it visible.

   "KEEP THOSE SIZES FIXED" IS THE WHOLE SECOND SENTENCE. The panel is exactly
   the glass between the two bands — `100dvh` less the strip, the bar and the
   gap, the same arithmetic `[data-sheet]` already did as a floor, stated as a
   HEIGHT — it hides its own overflow, and the rows are `1fr 1fr` of a box that
   cannot grow. Neither half can push the other; the only scroll left on this
   screen is the index's own, inside its half.

   AND THE GLOBE FILLS ITS HALF, which is not CSS: ui/atlas.js `refit()` sized
   the earth as `min(width * 0.94, viewport * 0.67)`, a ratio written for a
   globe standing in a long scrolling page. It takes its own box's height now
   when the box has one, and keeps the ratio where it does not. See the note
   there. */
.nu-pan[data-sheet]#atlas{
  block-size: calc(100dvh - var(--top-h) - var(--bar-h) - var(--s2));
  min-block-size: 0;
  display: grid;
  grid-template-columns: 100%;
  grid-template-rows: 1fr 1fr;
  gap: var(--s2);
  overflow: hidden;
  padding-block: 0;
}
/* The globe is the top band, full width, filling its half top to bottom. Its
   own overflow is gone with the scroll it belonged to: the map is drawn to fit
   this box, so there is nothing to pan to. */
#atlas[data-sheet] > #atlasWrap{
  grid-row: 1;
  margin-inline: calc(-1 * var(--s4));
  min-block-size: 0; block-size: 100%;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
/* …and the index is the bottom band and owns the only scroll on the screen.
   The search stays stuck to the top of ITS half rather than to a page that no
   longer scrolls. */
#atlas[data-sheet] > #atlasIndex{
  grid-row: 2;
  margin-block-start: 0;
  min-block-size: 0; block-size: 100%;
  overflow-y: auto; overscroll-behavior: contain;
  /* AND A ROW SCROLLED TO LANDS BELOW THE SEARCH, NOT BEHIND IT. The index
     puts the current record on the glass by `scrollIntoView`, and the search
     is `position: sticky` at the top of this box — so without a scroll padding
     the row it just found sits UNDER the bar, half of its name showing. One
     `--tap`, which is the bar's own height. */
  scroll-padding-block-start: var(--tap);
}

/* ======================================================================
   THE DESIGN PASS (2026-09-05) — DESIGN.md §1–§2, TABLE.md §11's restyle
   ======================================================================
   Paul, after the AUX spike: *"keep our stuff but make it less chunky and
   more stylish."* Everything above this line stays; this block is the
   RESTYLE, and it is written as a block rather than as fifty edits in place
   for one reason a reader can check: the rules it overrides are the ones the
   census measured, each is named here beside its number, and pulling this
   block out returns the page to what it looked like on v281.

   WHAT WAS MEASURED, on the rendered page at 390 (scratchpad/design/
   measure.cjs + census.cjs, Kingston 1969 at reading 1), before this block:

     the sheet at rest   76 plates on screen · 9 rules over 1px · 5,780px² of
                         border across 139 controls (41.6 each)
     a cell sheet        47 plates · 259px² of border per control
     a column sheet      75 plates · 205px² per control
     a row sheet         54 plates · 196px² per control

   A PLATE, COUNTED: a box with a border on all four sides AND a ground of its
   own. DESIGN.md §2 names exactly two — the curve editor (component 9) and
   the bar (component 12) — so every other one on this surface is a frame the
   type could have carried. The census named who was drawing them: `th,td`
   (the global 1px cell rule, 28 of them at rest), `button` (the Enamel face:
   panel ground, ink border, hard shadow), `select`, `input`, `.nu-colbtn`
   (seven filled hue plates), `.nu-vsheet`'s own frame.

   WHAT THIS BLOCK MAY NOT SPEND: `--tap`. 44 stays 44 everywhere, which is
   §11b's own sentence — the round buys INK, not rows.

   IT IS SCOPED TO `#pan-band` AND THE TWO PIECES OF CHROME DESIGN.md NAMES
   (the bar, the hamburger). The atlas, the globe, the screensaver and the
   deck are not DESIGN.md's components and are not restyled by a document
   that does not describe them.
   ---------------------------------------------------------------------- */

/* 1 · THE GRID IS NOT A GRID OF BOXES ---------------------------------
   `th,td{ border: 1px solid var(--rule) }` (line ~1787) plus `.nu-trims`'s
   3px `border-spacing` is what drew 28 little frames at rest. The table's own
   `background`/`border` (line ~1782) is the 29th. The structure is the
   spacing and the type; the only rules left are the two a spreadsheet needs —
   one under the heads, one over the footer. */
/* THE BAND PANEL PAYS NO TOP PADDING (2026-09-05, §13a.6's budget). `.nu-pan`
   gives every panel `--s4` of air above its content, which is right for a
   panel of prose and is 12.8px of a phone for a panel that is one full-bleed
   instrument. The table's own first row is its heading. */
#pan-band{ padding-block-start: 0; }
#pan-band table.nu-wordgrid{ background: none; border: 0; }
#pan-band .nu-wordgrid th,
#pan-band .nu-wordgrid td{ border: 0; background: none; }
#pan-band .nu-wordgrid tbody > tr:nth-child(even) > td{ background: none; }
#pan-band .nu-wordgrid thead th.nu-colhead,
#pan-band .nu-wordgrid thead th.nu-cornerh,
#pan-band .nu-wordgrid thead th.nu-plushead{
  border-block-end: var(--bw) solid var(--rule); }
#pan-band .nu-sheetgrid th.nu-spheadcell{
  border-block-end: var(--bw) solid var(--rule); }
/* the footer's own rule was `--bw-hard solid --rule` — an emphasis edge for a
   boundary, which DESIGN.md §1 spends on a focus ring and on nothing else. */
#pan-band .nu-wordgrid tfoot .nu-footrow > th,
#pan-band .nu-wordgrid tfoot .nu-footrow > td{
  border-block-start: var(--bw) solid var(--rule); }

/* 2 · A CONTROL ON THE TABLE IS A WORD, NOT A PLATE -------------------
   The Enamel face (`button`, line ~798: panel ground, ink border, 2px hard
   shadow) is what a *page* of buttons is dressed in; a spreadsheet is a
   surface of values, and a value that wears a button is a value you cannot
   read past. Named class by class rather than as `#pan-band button`, because
   three fills on this surface are SEMANTIC and must survive: `aria-pressed`
   (the hand), the `.w-*` word paints on the mix row, and `--vpaint`. */
#pan-band .nu-wcell,
#pan-band .nu-rowjump,
#pan-band .nu-corner,
#pan-band .nu-opbtn,
#pan-band .nu-groupbtn,
#pan-band .nu-clearback,
#pan-band .nu-colbtn{
  background: transparent; box-shadow: none; border-width: 0; }
/* AN OP IS A VERB AND KEEPS ITS EDGE — a hairline, and no ground, so it is a
   control you can see and not a plate you have to read past. */
#pan-band .nu-opbtn,
#pan-band .nu-groupbtn{
  border: var(--bw) solid var(--rule); border-radius: var(--r1); }
#pan-band .nu-opbtn:hover,
#pan-band .nu-groupbtn:hover{ background: var(--hand-tint); }
/* ...AND THE THREE SEMANTIC FILLS ARE SAID AGAIN AT THIS SPECIFICITY,
   because `#pan-band .nu-opbtn` (1-1-0) outranks `button[aria-pressed]`
   (0-2-0) and a pressed chip that lost its cobalt would be the state that
   matters most, silently deleted by a restyle. */
#pan-band .nu-groupbtn[aria-pressed="true"],
#pan-band .nu-wchip[aria-pressed="true"]{
  background: var(--hand); border-color: var(--hand); color: var(--on-fill); }

/* ======================================================================
   THE SHEET, TIDIED (2026-09-08)
   ======================================================================
   Paul, with two screenshots of an open sheet: *"This is a mess do you see
   it"* — then *"Clean it all up and deploy."* The sliced card rows were the
   loudest of it and are fixed where they were caused (src/table/grid.ts: the
   cap was typed in `--tap` and a card is not a tap). These are the other four
   things in those screenshots, each of them a rule that was answering its own
   question and not the row's.

   1 · A WORD THAT CANNOT BE PUSHED STOPS BEING A BUTTON. `MAKE IT` is
   thirty-three qualities and most of them are unavailable on any given chair,
   so the wall Paul photographed was thirty-three BOXES of which eight could be
   pressed. Dimming them to .5 kept every box, which is the noise. A disabled
   verb keeps its word and gives up its edge and its ground: the boxes on the
   glass are the ones a hand can use, and the rest read as the vocabulary they
   are. Nothing moves — same order, same wrap, same 44px targets.

   2 · `Clear` BELONGS TO THE FIELD ABOVE IT, NOT TO THE MIDDLE OF THE SHEET.
   It is `flex: 0 0 auto` after a grid that takes the whole row, so it wrapped
   onto a line of its own and centred there — a lone word floating under a
   picker, attached to nothing. An auto margin at its start pushes it to the
   row's end, which is where a field's own undo goes.

   3 · THE THREE MARKS UNDER `THIS CELL` SAY WHAT THEY DO. They are
   `.nu-opbtn.is-mark` — 44px squares carrying a glyph, with the word in a
   `.nu-vh` — and three unlabelled boxes in a sheet whose every other control
   is a word is exactly the *"what does that do"* this design keeps deleting
   elsewhere. The word comes out of the visually-hidden span and stands beside
   the glyph. The span is the SAME span, so nothing is written twice and the
   accessible name does not change.

   4 · ONE SEPARATOR. A dashed edge means REFUSED on this surface (`.nu-wcell.
   is-refused`, `.nu-mkb[data-why]`) — it is the one thing dashes say — so a
   dashed line between two ordinary fields is that word used for spacing. The
   sheet's field rows take the hairline the plate's rows already take.
   ====================================================================== */
/* `aria-disabled` AND NOT `disabled`, which is this page's own law and the
   reason the first cut of this rule changed nothing: DESIGN.md component 15 —
   *"a `disabled` button takes no click, no pointerdown and no pointerover, so
   the reason underneath it reaches nobody holding a phone"*. Every verb in
   this bar is greyed the accessible way, so the selector has to ask the same
   question the page answers. Both are listed: a `disabled` chip anywhere else
   in this sheet should read the same. */
#pan-band .nu-sheetops.is-verbs .nu-opbtn[aria-disabled="true"],
#pan-band .nu-sheetops.is-verbs .nu-opbtn[disabled]{
  border-color: transparent; background: none; opacity: .75;
  color: var(--dim); }
#pan-band .nu-clearback{ margin-inline-start: auto; }
#pan-band .nu-sheetops .nu-opbtn.is-mark{
  inline-size: auto; min-inline-size: var(--tap);
  padding-inline: .6ch; gap: .5ch; }
#pan-band .nu-sheetops .nu-opbtn.is-mark > .nu-vh{
  position: static; inline-size: auto; block-size: auto;
  clip: auto; clip-path: none; white-space: nowrap;
  overflow: visible; margin: 0; }
#pan-band .nu-vsheet .nu-sheetrow{ border-block-end: 0; }
#pan-band .nu-vsheet .nu-sheetrow + .nu-sheetrow{
  border-block-start: var(--bw) solid var(--rule); border-block-end: 0; }

/* 3 · A VALUE IN A SHEET IS A CUT, NOT A BOX --------------------------
   A sheet row is `label · control · value · clear-back` and the control has to
   be findable by a thumb, so it keeps ONE hairline — under it, where a form
   has always put the line you write on. Radius `--r0`: an underline has no
   corners to ease. The GRID's own cells keep zero border (T5e: "a resting cell
   has no box"), which is why this is `.nu-vsheet`-scoped. */
#pan-band .nu-vsheet .nu-wcell,
#pan-band .nu-vsheet .nu-nativepick,
#pan-band .nu-vsheet .nu-numbox,
#pan-band .nu-vsheet .nu-textbox,
#pan-band .nu-vsheet .nu-combofield{
  background: transparent; box-shadow: none;
  border: 0; border-block-end: var(--bw) solid var(--rule);
  border-radius: var(--r0); }
#pan-band .nu-vsheet .nu-wcell:hover,
#pan-band .nu-vsheet .nu-nativepick:hover{
  background: transparent; border-block-end-color: var(--ink); }
/* WRITTEN IS BOLD AND INHERITED IS QUIET, and on a sheet that is now the whole
   of the difference — §11b measured it on the grid's cells and this is the
   same law one tier in. */
#pan-band .nu-vsheet .nu-wcell{ font-weight: var(--fw-label); }
#pan-band .nu-vsheet .nu-wcell.is-derived{ font-weight: var(--fw-body); }
/* A REFUSAL IS A DASH, NEVER A SILENCE (DESIGN.md §2.14). */
#pan-band .nu-vsheet .nu-wcell.is-refused,
#pan-band .nu-vsheet .nu-wcell[disabled]{
  border-block-end-style: dashed; }
/* THE SHEET ITSELF IS NOT A PLATE. It keeps its ground — an open row is a
   region and a region may have a ground — and gives up the frame. */
#pan-band .nu-vsheet{ border: 0; }

/* 4 · A CHIP IS A HAIRLINE AND A WORD --------------------------------- */
#pan-band .nu-wchip{
  background: transparent; box-shadow: none;
  border: var(--bw) solid var(--rule); border-radius: var(--r2); }
#pan-band .nu-wchip:hover{ background: var(--hand-tint); }
#pan-band .nu-wchip[disabled]{ background: transparent; border-style: dashed; }

/* 5 · THE COLUMN HEAD KEEPS ITS HUE AND GIVES UP ITS FILL -------------
   `.nu-vpaint` is a filled plate in the player's colour with white ink and an
   ink border (line ~1586), and seven of them across the top of a 390px table
   is seven filled plates. The HUE IS THE FACT (DESIGN.md §1: "--v0..3 = which
   player") and it is kept — as the quiet form the same rule already declares
   for a row that must keep its own ground (`.nu-vpaint.is-edge`), turned to
   the block-end so it underlines the name instead of gripping its side. */
/* ---- AND THE HEAD STOPS BEING THE HEAVIEST THING (2026-09-08, §24) ----
   Paul: *"the header row is thicker than the rest."*

   MEASURED, and the head is not taller — 53.3px against a body row's 64.5. It
   is HEAVIER: every column head wore a 3px bar of its player's hue, so a band
   of four players drew four slabs of colour across the top of a grid whose
   rows have no rules at all between them. Weight, not height, and the word
   "thicker" is the right one for what it looked like.
   `--bw-hard` IS THE PAGE'S OWN ANSWER — the same 2px it uses wherever a line
   has to be seen rather than felt — and the hue is untouched. DESIGN.md §1's
   *"--v0..3 = which player"* is a claim about the COLOUR and never about how
   many pixels of it there are; the mark below is still painted in it too, so
   the player's hue is on the glyph a thumb aims at as well as under the name.
   THE BAR STILL WINS ITS ARGUMENT with the row underneath: 2px of a saturated
   hue against 0px of anything is still a head that reads as a head. */
#pan-band .nu-wordgrid thead th.nu-colhead .nu-colbtn.nu-vpaint{
  background: transparent; color: var(--ink); border: 0;
  box-shadow: inset 0 calc(-1 * var(--bw-hard)) 0 var(--vpaint, var(--ink)); }
#pan-band .nu-wordgrid thead th.nu-colhead .nu-colbtn.nu-vpaint:hover{
  background: var(--hand-tint); }
/* ...AND THE HEAD'S OWN MARK IS PAINTED IN IT, so the hue is on the glyph a
   thumb is aiming at and not only on a bar under it. */
#pan-band .nu-wordgrid thead th.nu-colhead .nu-colbtn .nu-g{
  color: var(--vpaint, var(--ink)); }

/* 6 · HIERARCHY BY WEIGHT AND SIZE ------------------------------------
   `.nu-trims thead th` / `tbody th` are .62rem uppercase mono — the type of a
   dense readout, on the two things a hand navigates by. DESIGN.md §1: "nothing
   under 16px on a phone that a hand edits; hierarchy by weight and size, not
   by boxes." The instrument leads at --t2, the player's name follows at --t1,
   and the section name is --t2 with its bars still small beside it. */
/* MEASURED, AND THE FIRST DRAFT WAS WRONG ABOUT WHERE THE SIZE GOES. Seven
   columns at 390 are 48px each; `--t2` on the instrument truncated every one
   of them to three characters ("clea…", "perc…") — a name is not more legible
   for being larger and cut off. So the head's hierarchy is WEIGHT: the
   instrument keeps `--t1` and takes `--fw-block`, the player's name drops to
   `--t0` and dims, and the size that was spent here is spent on the ROW head,
   which has 13ch to spend it in. */
#pan-band .nu-wordgrid .nu-colinstr{
  font: var(--fw-block) var(--t1)/1.15 var(--sans, inherit); }
#pan-band .nu-wordgrid .nu-colname{
  font: var(--fw-body) var(--t0)/1.2 var(--mono, monospace); opacity: .7; }
/* ...AND A ROW HEAD CLIPS RATHER THAN OVERFLOWING — BUT ONLY ONCE THE COLUMN
   IS THE WIDTH THIS PARAGRAPH CLAIMED IT WAS (repaired 2026-09-05).
   The sentence that stood here said "`table-layout: fixed` gives the head
   column 13ch". MEASURED, it does not, and has not since §10a put the special
   rows in the `<thead>`: `.nu-wordgrid thead th:first-child` is now the TIME
   row's `<th colspan>`, so the 13ch lands on a cell that spans every column
   and sizes none of them, and no `<col>` carried a width either. Chrome shared
   the table's declared width out instead and the head column came out 35.6px
   at 320 AND at 390 — into which `--t2` plus `overflow: hidden` printed "b…",
   "d…", "in…", "v…" under a corner reading "S…". The section names are what a
   composer navigates by; a name at one letter is not a name.
   THE WIDTHS ARE DECLARED ON THE `<col>`s AND THE TABLE IS THEIR SUM, which is
   the one shape `table-layout: fixed` renders exactly — a table wider than the
   sum has its surplus shared out in proportion, and the old formula
   (`13ch + cols * 9ch`, 865px against a 364px pane) was 480px of surplus
   parked in the adder column. Declared: the section names 8ch, a player 3.5ch
   (which is what a player column already measured, so the players do not move),
   the adder 22ch — its three offers on ONE line, measured at 214.3px, because
   `.nu-addcol`'s own 9ch stacks them three deep and takes the header row from
   48px to 139, which is 91px of a phone's screen spent on a gap above the
   player heads (they are `vertical-align: bottom` and the corner is not); at
   22ch the header row measures 57.3px. The offers still wrap when a record's
   words are longer; the column no longer makes them. MEASURED at 320 AND 390: the head column 35.6 → 81.4px, a
   player 35.6 → 35.6px, the dead adder 550 → 224px, the corner "SECTION" whole,
   and "bass alone / drums & bass / intro / verse / chorus / bridge / solo /
   outro" — every section name on this record but the twelve-character one —
   printing whole where before every one of them was a letter and an ellipsis.
   EIGHT AND NOT THIRTEEN: 13ch would be a third of a 320px pane spent on the
   names; 8ch is the longest of the standard words ("chorus", "bridge") with
   room, and a longer name still ellipsises, which is what the paragraph below
   is about. Wider than the sum (1280) the proportion is what scales, which is
   the same table with room.
   THE `<col>`'s INLINE STYLE STILL WINS: `WIDTH` writes a resized player's
   width onto its own `<col>` and no rule here can outrank an attribute. */
/* ...AND THE PLAYERS SHARE THE PANE SINCE 2026-09-05 (TABLE.md §13a.7). The
   widths were `--headw: 8ch · --colw: 3.5ch · --addw: 22ch`, and MEASURED at
   390 on Kingston 1969 that is seven player columns of 35.6px each holding a
   `.nu-cellword` whose own `min-inline-size` was 56px — SIX overlapping cell
   pairs in the first section row, which is the pile in Paul's screenshot. Two
   numbers were wrong at once: the column was narrower than the thing inside
   it, and 224px of the pane was parked in an adder that is a `--tap` mark now.
   §13a.7: *"A player column is never narrower than its glyph plus `--s2` each
   side, and never wider than the pane divided among the players present."*
   Both ends are stated here and the `max()` is the whole rule: the SHARE is
   the pane (`--panew`, written by `src/table/grid.ts stick()`, because only
   the DOM knows it) less the head column and the `+`, divided by the players
   present (`--cols` is the column count plus one, so the players are
   `--cols - 1`); the FLOOR is the glyph and its two `--s2`s. Where the share
   is the bigger number the columns fill the pane and nothing scrolls sideways;
   where it is not, every column stands at the floor and the pane scrolls, and
   in neither case does a cell overlap its neighbour. */
/* ...AND SINCE 2026-09-06 THE FLOOR IS A WORD AND NOT A GLYPH (TABLE.md §14
   item 2). The Coach House walkthrough measured the old floor's cost: ten
   players at 390 drew 140 identical dots while the same table at 1280 printed
   every name. The arithmetic that decides it is in `src/table/grid.ts
   stick()`: ten players share 217px of a 364px pane, which is three
   characters each, so no arrangement of lines fits ten words across a phone.
   The column is sized to a WORD instead and the pane scrolls sideways with
   the section column frozen — the gesture every spreadsheet on a phone has.
   `--wordw` is MEASURED (9ch in the CELL's own type plus the cell's own side
   padding) and written onto the table by `stick()`, because only the DOM
   knows what a character of that font is; the glyph floor stays as the other
   half of the `max()`, for the pathological case where a word is narrower
   than the mark beside it. */
#pan-band .nu-wordgrid{ --headbase: 8ch;
  --cellmin: max(calc(1.15rem + 2 * var(--s2)), var(--wordw, 0px));
  /* THE GAPS ARE PART OF THE WIDTH AND THEY ARE NOT SMALL. `.nu-trims` is
     `border-collapse: separate; border-spacing: 3px`, so a table of nine
     columns carries TEN 3px gaps — 30px, measured, which is 8% of a 390px
     phone and exactly what pushed the `+` off the right edge on the first
     drawing of this rule. `--cols` is the column count plus one, so the
     columns are `--cols + 1` and the gaps `--cols + 2`. */
  --gaps: calc(3px * (var(--cols, 2) + 2));
  /* ...AND THE DIVISOR IS NEVER ZERO. The blank state (`silence`) is a record
     with NO players, so `--cols - 1` is 0 and `calc(x / 0)` is invalid at
     computed-value time — which takes `--colw`, `--headw` and the table's own
     `inline-size` down with it. MEASURED before this guard, on Silence at 390:
     the head column collapsed to its content and the `+` stood at x = 18.8,
     six pixels from the left edge of a 364px pane. A record you have not built
     a band into yet is exactly the record this table has to be readable on. */
  --nplay: max(1, calc(var(--cols, 2) - 1));
  --colshare: calc((var(--panew, 100%) - var(--headbase) - var(--tap) - var(--gaps))
                   / var(--nplay));
  --colw: max(var(--cellmin), var(--colshare));
  /* THE HEAD COLUMN TAKES THE SLACK, AND THAT IS WHAT KEEPS THE `+` A THUMB.
     Under `table-layout: fixed` a table WIDER than the sum of its columns hands
     the surplus out IN PROPORTION — measured on the Silence record at 390,
     where a record with no players left 320px of slack and the `+` drew 124.7px
     of it, and on Kingston at 390, where it drew 57.4 against a 44px token. So
     the table is EXACTLY the sum and there is no surplus to hand out: the head
     column is told to be everything the others are not, which is where a
     section name that ellipsised wanted the room anyway. Its 8ch is the floor,
     and `--headbase` (that same 8ch, a constant) is what the players' share is
     worked out against, so the two do not chase each other. */
  --headw: max(var(--headbase),
               calc(var(--panew, 100%) - (var(--cols, 2) - 1) * var(--colw)
                    - var(--tap) - var(--gaps)));
  inline-size: calc(var(--headw) + (var(--cols, 2) - 1) * var(--colw)
                    + var(--tap) + var(--gaps)); }
#pan-band .nu-wordgrid > colgroup > col{ inline-size: var(--colw); }
/* THE HEAD COLUMN TAKES THE SLACK, AND THAT IS WHAT KEEPS THE `+` A THUMB.
   Under `table-layout: fixed` a table wider than the sum of its columns hands
   the surplus out IN PROPORTION — measured on the Silence record at 390, where
   a record with no players left 320px of slack and the `+` column drew 124.7px
   of it. A percentage on a `<col>` resolves against the TABLE's own width, so
   the head column is told to be everything the others are not: the section
   names get the room (which is where a name that ellipsised wanted it), the
   players keep their share, and the `+` is `--tap` on every record at every
   width. Its own 8ch is the floor. */
#pan-band .nu-wordgrid > colgroup > col:first-child{ inline-size: var(--headw); }
#pan-band .nu-wordgrid > colgroup > col.nu-addcol{ inline-size: var(--tap); }
/* ...AND A CELL FILLS ITS COLUMN AND NEVER MORE. `.nu-cellword`'s 56px floor
   is right for a sheet row and wrong for a grid cell — a floor wider than the
   column it stands in is the overlap itself. In the grid the column carries
   the floor (`--cellmin` above) and the button takes exactly what it is given. */
#pan-band .nu-sheetgrid tbody > tr > td > .nu-cellword,
#pan-band .nu-sheetgrid tfoot td.nu-mixcell > .nu-cellword{ min-inline-size: 0; }
/* ---- THE WORD SHOWS WHERE THERE IS ROOM (§13a.7) --------------------
   *"Cells are glyphs first, words when there is room … the word shows when
   the column measures ≥ 9ch and hides otherwise."* The measurement is
   `stick()`'s — a 9ch probe in the head's own type against the width a player
   column actually came out — and it writes ONE class on the table, because
   every player column is the same width. `.nu-vh` and the accessible name
   carry the word when the glyph stands alone, which is the icon round's law.
   AND A HEAD IS NEVER CUT MID-WORD. Inside the wide state, `stick()` asks each
   head whether its NAME fits: one that does not falls back to its first word
   (`.is-first`), and a first word that does not fit either falls back to the
   glyph (`.is-glyphonly`). Two spans, one name, no second spelling. */
/* ...AND A HEAD FILLS ITS COLUMN AND NEVER MORE, which is `.nu-cellword`'s
   own repair said about the row above the cells: `.nu-colbtn` carries
   `min-inline-size: 56px` and a 56px button in a 33px column is the overlap
   again, one row higher. MEASURED at 390 on Kingston 1969: seven heads 33.2px
   wide holding seven 56px buttons. */
#pan-band .nu-sheetgrid thead th.nu-colhead > .nu-colbtn{ min-inline-size: 0; }
/* ---- THE PINNED BAND IS ONE ROW OF THUMB (§13b) ---------------------
   *"pinned bands inside the pane, at rest: the heads alone (≤ 1 × `--tap` +
   3px)"*, and the 3 is `border-spacing`. MEASURED before this rule: 57px —
   `--ctl` (48) inside a button with `--s1` of block padding inside a `<th>`
   with 2px of its own, which is three paddings for a row whose whole content
   is one mark. Where the words are hidden the head is a MARK and a mark is
   44px of thumb and nothing else; where they are shown (1280, and any record
   whose columns are 9ch) the head is two lines and keeps its air. */
#pan-band .nu-sheetgrid thead th.nu-cornerh,
#pan-band .nu-sheetgrid thead th.nu-plushead{ padding-block: 0; }
/* ...AND THE PLAYER'S LAMP RIDES THE HEAD'S OWN EDGE RATHER THAN ADDING A
   LINE UNDER IT. `.nu-scollamp` is a 4px block and it made the band 49px
   where the budget is 47. The `<th>` is `position: sticky`, which is a
   containing block, so the lamp is laid on the head's bottom edge — the same
   place `.is-sounding`'s own edge bar is drawn — and takes no height at all.
   It is still a SIBLING of the button and still the clock's own surface. */
/* ---- AND THE BAND IS OPAQUE, GAPS INCLUDED (2026-09-08) ---------------
   Paul: *"when a row is playing and I scroll left the HEADER ROW is
   transparent."*

   IT IS `border-spacing` AGAIN, AND IT IS THE SAME LESSON §24 LEARNED ABOUT
   THE PLAYING ROW two hundred lines up: this grid sets `border-spacing: 3px`,
   so the head's cells CANNOT TOUCH. Each `<th>` paints its own opaque ground
   and between every pair of them is a 3px column of nothing — and what is
   behind a sticky head is the body scrolling under it. Stationary that reads
   as a seam; with a lit row passing beneath, those stripes go red and the band
   reads as a transparent header.
   SO THE GROUND MOVES TO THE `<tr>`, which is the one box in a separated table
   that has no gaps in it — exactly the repair the lit row took, quoted here
   because it is the same defect one row up. The cells keep their own paint
   (they are still what a hover and a sounding column light), and the row
   underneath them fills the spacing. */
#pan-band .nu-sheetgrid thead > tr{ background: var(--deck); }
#pan-band .nu-sheetgrid thead th.nu-colhead{ position: sticky; }
#pan-band .nu-sheetgrid thead th.nu-colhead > .nu-scollamp{
  position: absolute; inset-block-end: 1px; inset-inline: 0;
  inline-size: 100%; }
#pan-band .nu-sheetgrid thead th.nu-cornerh > .nu-corner{
  padding-block: 0; min-block-size: var(--tap); }
#pan-band .nu-sheetgrid:not(.has-words) thead th.nu-colhead{ padding-block: 0; }
#pan-band .nu-sheetgrid:not(.has-words) thead th.nu-colhead > .nu-colbtn{
  padding-block: 0; gap: 0; min-block-size: var(--tap);
  align-items: center; justify-content: center; }
#pan-band .nu-sheetgrid .nu-cnfirst{ display: none; }
#pan-band .nu-sheetgrid .nu-colbtn.is-first .nu-cnfull{ display: none; }
#pan-band .nu-sheetgrid .nu-colbtn.is-first .nu-cnfirst{ display: block; }
#pan-band .nu-sheetgrid:not(.has-words) thead th.nu-colhead .nu-colname,
#pan-band .nu-sheetgrid:not(.has-words) thead th.nu-colhead .nu-colinstr,
#pan-band .nu-sheetgrid .nu-colbtn.is-glyphonly .nu-colname,
#pan-band .nu-sheetgrid .nu-colbtn.is-glyphonly .nu-colinstr,
#pan-band .nu-sheetgrid .nu-colbtn.is-noinstr .nu-colinstr,
#pan-band .nu-sheetgrid:not(.has-words) tbody > tr > td > .nu-cellword .nu-w,
#pan-band .nu-sheetgrid:not(.has-words) tfoot td.nu-mixcell .nu-w{
  display: none; }
/* ---- AND WHERE THE GLYPH AND THE WORD WILL NOT SHARE A LINE, THEY STACK
   (2026-09-06, TABLE.md §14 item 2). `stick()` measures both in the cell's own
   type and writes `.is-stack` on the table: the mark over the word, the word
   with the WHOLE column to ellipsise against instead of the column less the
   mark and the gap. MEASURED on Coach House at 390: 20px of word per cell,
   which is the difference between 99 of 140 cells printing a whole word and
   138 of them. IT COSTS NO HEIGHT — the row is `--tap` in both states, because
   the mark's line and the word's line together are 30px inside a 44px floor —
   so nothing is traded for it but the mark's size, which comes down to the
   head's 1rem for the same reason a head's is smaller than a cell's: it is
   the second voice in the cell now, not the first. */
/* ---- AND IT STACKS AT EVERY WIDTH NOW (2026-09-08) --------------------
   Paul: *"always put the label on a section cell BELOW the little icon."*

   IT WAS A MEASUREMENT, AND THE MEASUREMENT WAS ABOUT FIT. `stick()` compared
   the column against the mark plus a nine-character word and stacked only when
   they would not share a line — so a phone read `● / counter` and a desktop
   read `● counter`, which is the same cell wearing two faces depending on how
   wide the window happens to be. That is the thing a hand cannot learn: the
   grammar of a cell should not change when you rotate the screen.
   SO THE STACK IS THE FACE. The word gets the whole column to ellipsise
   against at every width (the reason the narrow case existed), the mark is the
   second voice above it, and the row is still `--tap` tall in both states —
   the mark's line and the word's line together are 30px inside a 44px floor,
   which is why this costs no height on a desktop either.
   `is-stack` IS STILL WRITTEN and is no longer read here: it remains the
   honest name for "these two would not have fitted on one line", which
   `--wordw` and `has-words` are measured beside. */
#pan-band .nu-sheetgrid .nu-cellword .nu-ic{
  flex-direction: column; align-items: stretch; gap: 0; }
#pan-band .nu-sheetgrid .nu-cellword .nu-g{ font-size: 1rem;
  line-height: 1; }
#pan-band .nu-sheetgrid .nu-cellword .nu-w{ text-align: center;
  inline-size: 100%; }
/* ---- AND THE SWIPE IS NOT SNAPPED, WHICH IS A REFUSAL WITH A MEASUREMENT
   UNDER IT (2026-09-06, §14 item 2). A table a phone reads a few players at a
   time wants its swipe to land a whole column, so this block held
   `scroll-snap-type: x proximity` on the pane and `scroll-snap-align: start`
   with `scroll-margin-inline-start: var(--headw)` on the column heads.
   MEASURED on Coach House at 390, at rest, before a hand had touched it: the
   pane opened at `scrollLeft: 40` — the browser re-snaps a snapport when its
   content is re-laid-out, and `stick()`'s own widening (the `--wordw` write)
   IS that re-layout, so the first player was pushed half off the left edge of
   a table nobody had scrolled. With the snap off, the same measurement reads
   `scrollLeft: 0`. A table that opens on a column it has hidden is a worse
   fault than a swipe that stops between two, so the snap is refused and the
   pane's own free scroll (§11c) stands. */
#pan-band .nu-rowjump{ font: var(--fw-label) var(--t2)/1.2 var(--sans, inherit);
  overflow: hidden; text-overflow: ellipsis; }
/* AND THE NAME GETS THE LINE. The mark and the section's number are inline
   siblings of it, so in 36px they took the first line and left the name a
   scrap of the second; as a block the name has the whole column, and the
   ellipsis is its own — `text-overflow` on an inline box paints nothing, which
   is why the clipping had to be read off the button. */
#pan-band .nu-wordgrid .nu-srowname{ display: block; }
#pan-band .nu-wordgrid tbody th.nu-srowh{ overflow: hidden; }
/* ...AND THE CORNER KEEPS ITS OWN TYPE. `.nu-corner` is .62rem uppercase mono
   — a head's word, not a value — and `#pan-band .nu-rowjump` above outranked
   it by an id, printing "SECTION" at `--t2` sans into a column it does not
   fit: "S…". A head is the one word on this surface that is allowed to be
   small, because no hand edits it. */
#pan-band .nu-wordgrid .nu-corner{
  font: var(--fw-block) var(--t0)/1.3 var(--mono, monospace);
  letter-spacing: .04em; }
#pan-band .nu-rowjump:hover,
#pan-band .nu-corner:hover{ background: var(--hand-tint); border-color: transparent; }
#pan-band .nu-trims tbody th small{ font-size: var(--t0); }
/* the sheet's label column is the QUIET half of the row and the value is the
   loud one — the reverse of what a 700-weight label beside a 500-weight value
   was saying. */
#pan-band .nu-sheetlab{
  font: var(--fw-body) var(--t2)/1.25 var(--sans, inherit); color: var(--dim);
  text-transform: uppercase; letter-spacing: .04em; }

/* 7 · THE BAR IS A PLATE, AND ITS MARKS ARE 44 -----------------------
   DESIGN.md §2.12 names the bar as one of the two plates on the page, so it
   keeps its ground and its edge. What it did not keep is four marks measured
   UNDER the tap floor at all three widths (scratchpad/design/before). */
.nu-bar button, .nu-bar select, .nu-bar .nu-barbtn{
  min-block-size: var(--tap); min-inline-size: var(--tap); }

/* 8 · THE SEATED WIDGETS, WHERE THEY ARE THE TABLE'S OWN --------------
   The census after §1–§7 left the plates in exactly one place: inside a
   `.nu-noderow`, which is where a CALLER'S OWN widget is seated (sheet.ts:
   "a caller's own widget gets the whole row"). Most of those callers are other
   surfaces — the desk's channel strip, the rack, the crate, the knob table —
   and their look is theirs. Four of them are not: the tempo panel, the
   producer's cast chips and the motif bank are drawn BY this table FOR this
   table, and a strip of chips is a strip of chips wherever it is seated.
   MEASURED at 390 before this rule: time 16 plates, produce 17, motifs 16. */
#pan-band .nu-vsheet [data-k^="tempo-"],
#pan-band .nu-castchip,
#pan-band .nu-bankname,
#pan-band .nu-bankopen{
  background: transparent; box-shadow: none;
  border: var(--bw) solid var(--rule); border-radius: var(--r2); }
#pan-band .nu-vsheet [data-k^="tempo-"]:hover,
#pan-band .nu-castchip:hover,
#pan-band .nu-bankname:hover,
#pan-band .nu-bankopen:hover{ background: var(--hand-tint); }
/* ...AND THE HOT ONE STAYS FILLED, which is the one thing a restyle may not
   take: `aria-pressed` is the state, and DESIGN.md §2 draws a state. */
#pan-band .nu-castchip[aria-pressed="true"],
#pan-band .nu-bankname[aria-pressed="true"],
#pan-band .nu-bankopen[aria-pressed="true"]{
  background: var(--hand); border-color: var(--hand); color: var(--on-fill); }
/* A NUMBER YOU TYPE IS A CUT, NOT A PLATE — §3's own rule, said for the two
   entries the time panel draws itself (`bpm.typed`, `meter.num/den.typed`). */
#pan-band .nu-bpmbig, #pan-band .nu-signum{
  background: transparent; box-shadow: none;
  border: 0; border-block-end: var(--bw) solid var(--rule);
  border-radius: var(--r0); }
/* A STRIP OF CHIPS IS NOT A BOX OF CHIPS. `ui/menus.js` seats its chips in a
   `.nu-wchips.nu-menu` that wears the combo chassis's plate; the chips are the
   control and the strip is only where they wrap. */
#pan-band .nu-wchips.nu-menu{ background: transparent; border: 0;
  box-shadow: none; }

/* ======================================================================
   §11c · EVERY EXPANDED INTERFACE IS PROPER SOFTWARE (2026-09-05)
   Paul: *"Don't bother with 8, typed in cell. Instead just nicely structure
   each expanded interface as proper software that's easy to scan and nicely
   grouped."* `src/table/model.ts` states the GROUPS and their order (the
   composer's, DESIGN.md §5); `src/table/sheet.ts groupChunks` chunks the
   consecutive fields that share one; this is what a group looks like.

   MEASURED BEFORE: a cell sheet was 15 rows in ONE list, a column sheet 11, a
   row sheet 36 — the longest run of unbroken fields on this page, and the
   thing a hand scrolls past looking for "where does it come in".
   `--s5` (1.4em) between subjects is DESIGN.md §1's own space law, said out
   loud: *"between controls that answer one question: s3; between questions:
   s4; between subjects: s5."*  A HEADING IS NOT A PLATE: a word in the label
   weight over a hairline, which is the whole of the frame it replaces. */
#pan-band .nu-vsheet > .nu-sheetgroup{
  grid-column: 1 / -1;
  display: grid; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: var(--s2) var(--s3);
  margin-block-start: var(--s5); }
#pan-band .nu-vsheet > .nu-sheetgroup:first-child{ margin-block-start: 0; }
#pan-band .nu-grouphead{
  grid-column: 1 / -1; margin: 0;
  font: var(--fw-block) var(--t1)/1.3 var(--sans, inherit);
  text-transform: uppercase; letter-spacing: .08em; color: var(--dim);
  padding-block-end: var(--s1);
  border-block-end: var(--bw) solid var(--rule); }
/* THE THREE THAT TAKE THE WHOLE WIDTH TAKE IT INSIDE A GROUP TOO. The rule
   two thousand lines up is `.nu-vsheet > .nu-sheetops` — a DIRECT child — and
   a group is now between them, so an ops bar inside a group would have gone
   back to one 20rem column. Said again for the new parent rather than loosened
   to a descendant selector, because the claim is about a grid's own children
   and there are now two grids. */
#pan-band .nu-sheetgroup > .nu-sheetops,
#pan-band .nu-sheetgroup > .nu-noderow,
#pan-band .nu-sheetgroup > .nu-wchips,
#pan-band .nu-sheetgroup > .nu-wgroups,
#pan-band .nu-sheetgroup > .nu-lzfield{ grid-column: 1 / -1; }

/* ======================================================================
   THE SHEET AS A TABLE (TABLE.md §19 · DESIGN.md component 22, 2026-09-07)
   ======================================================================
   Paul, of a chair's playing options: *"When I open an instrument gives me
   all these playing options that I can't differentiate and they are spread
   all over the place. Give them the same treatment as the instrument voice
   selector … we need to make it smaller and it can also go horizontally wider
   than the screen."*

   ONE COLUMN PER SUBJECT, ONE SETTING PER LINE. The sheet stops being a grid
   of 20rem boxes that reflow into a tall stack on a phone and becomes a TRACK
   of subject columns a thumb pushes sideways — the same shape the instrument
   picker takes one tier down, which is what "the same treatment" means.
   `overflow-x` is on the TRACK and never on the sheet or the pane, and the
   gesture STOPS DEAD at the track's ends: `overscroll-behavior-inline: none`
   and not `contain`. TABLE.md §15a settled this on 2026-09-07 (the
   design-system round it was assigned to): `contain` stops the flick CHAINING
   to the pane behind and still lets the track rubber-band inside itself, and a
   bounce translates the sticky heads with it — which is the whole of Paul's
   *"it all feels reel wobbly"*. `none` is what the pane and the page already
   carry (§11c) and this is the same law on the same axis. */
/* ===== AND IT IS ONE COLUMN AGAIN, 2026-09-08 (TABLE.md §24) ===========
   Paul: *"The two column thing is a disaster it should be one column
   scrolling."*

   THIS REVERSES §19, WHICH WAS ALSO HIS ASK, and both sentences are right
   about what they were looking at. §19 was told *"When I open an instrument
   gives me all these playing options that I can't differentiate and they are
   spread all over the place … we need to make it smaller and it can also go
   horizontally wider than the screen"* — and the complaint it fixed was
   DIFFERENTIATION: settings in one undifferentiated stack, with no way to see
   that these four are about the envelope and those three about tone. Columns
   gave the subjects edges.
   WHAT THEY COST, AND IT IS WHAT THE WORD "disaster" IS ABOUT: a phone is 378px
   of card and a subject column is 22ch, so TWO of them fit — and a two-column
   grid on a narrow glass is not two columns, it is a READING ORDER THAT
   ZIGZAGS. Measured on the cell card at 390: MOTIF beside VARIATION, then
   ARTICULATION under MOTIF while RAMP LIMIT sits under VARIATION, so the eye
   goes down-left, up-right, down-left. And the third subject and the fourth
   are off the right edge of a card that gives no sign they are there.
   THE SUBJECTS KEEP THEIR EDGES AND LOSE THEIR AXIS. `flex-direction: column`
   is the whole of it: every group is full width, in one scroll, in the order
   the record is made in — and a group still opens with its own heading and its
   own rule, which is the differentiation §19 was actually asked for. The
   sideways scroll goes with the axis, so a card has ONE scroller again and the
   `overscroll-behavior-inline` argument below retires with the overflow it was
   about.
   (§19's rows are kept whole below because most of them are about a SUBJECT
   COLUMN's insides — one setting per line, the label over its control — and
   those are right in a column of one.) */
#pan-band .nu-vsheet.is-table{ display: block; }
#pan-band .nu-sheettrack{ display: flex; align-items: stretch;
  flex-direction: column;
  /* DESIGN.md §1's space law, on the axis the subjects now stand on:
     *"between controls that answer one question: s3; between questions: s4;
     between subjects: s5."* The subjects are side by side, so s5 is a COLUMN
     gap rather than a block margin — the same number, the same law, turned
     with the sheet. */
  /* THE GAP IS STILL `--s5` AND IS STILL §1's SPACE LAW — *"between one
     subject and the next: s5"* — and it is a BLOCK gap again rather than a
     column one, which is the same number turned back with the sheet.
     `overflow-x: auto` AND `overscroll-behavior-inline: none` ARE DELETED with
     the axis they were about. The second was argued hard (§15a: `contain`
     rubber-bands and translates the sticky heads with it, which is *"it all
     feels reel wobbly"*), and a track that does not scroll sideways cannot
     rubber-band sideways either — the law is kept by having nothing to apply
     it to. */
  gap: var(--s5);
  padding-block-end: var(--s2); }
/* A SUBJECT COLUMN IS ITS OWN STACK, and it is as wide as a setting needs and
   no wider: 22ch holds "double-tracked" and a label on one line at every
   width this page is measured at. It is `0 0 auto` so a column never gives up
   its width to a neighbour — the track grows instead, which is the whole
   point of a track. */
/* A SUBJECT IS THE WHOLE WIDTH NOW. It was `flex: 0 0 auto` at `22ch` —
   *"as wide as a setting needs and no wider … it is `0 0 auto` so a column
   never gives up its width to a neighbour"* — which is exactly right for a
   column in a track and exactly wrong for the only column there is. */
#pan-band .nu-sheettrack > .nu-sheetgroup{
  flex: 0 0 auto; display: block;
  inline-size: 100%; max-inline-size: 100%;
  margin-block-start: 0; }
#pan-band .nu-sheettrack > .nu-sheetgroup > .nu-sheetrow{
  margin-block-start: var(--s2); }
/* A SETTING IS A LINE: the label over its control at this width, because a
   label beside a control in a 22ch column is a control four characters wide. */
#pan-band .nu-sheettrack .nu-sheetrow{ flex-wrap: wrap; }
#pan-band .nu-sheettrack .nu-sheetlab{ flex: 1 0 100%; min-inline-size: 0;
  font-size: var(--t1); }
#pan-band .nu-sheettrack .nu-sheetrow > .nu-wcell{ inline-size: 100%; }
/* ...AND THE COLUMN A THUMB IS WORKING IN TAKES THE SHEET'S WIDTH, so a
   picker opened inside it is not a scrollport inside a scrollport. One thing
   is open at a time (§18), so exactly one column is ever wide. */
#pan-band .nu-sheettrack > .nu-sheetgroup.is-open{
  inline-size: min(100%, calc(100vw - 3 * var(--s4)));
  min-inline-size: 22ch; }

/* ---- A SETTING THAT IS SEVERAL SETTINGS (DESIGN.md component 24) ------
   Paul: *"if things have multiple settings to make that really clear and
   shading or a little bit of a fill behind them."* A fill behind the row and
   a count at the end of its label — so a hand reading down a column can see
   which lines are one answer and which are a whole control. */
#pan-band .nu-sheetrow.is-compound{
  background: var(--panel); border-radius: var(--r1);
  padding: var(--s1) var(--s2);
  box-shadow: inset 2px 0 0 0 var(--hand); }
/* THE COUNT IS A BADGE AND NOT A BAND. A `.nu-noderow` is a COLUMN flex box
   with `align-items: stretch` (a channel strip needs the whole width), so a
   `flex: 0 0 auto` child stretched across it and the count drew as a filled
   bar over the envelope — measured on the phone, first drawing. `align-self`
   is the one line that says "not this child". */
.nu-noderow > .nu-many{ align-self: flex-start; }
.nu-many{ flex: 0 0 auto;
  font: var(--num-fw) var(--t0)/1 var(--mono, monospace);
  font-variant-numeric: tabular-nums;
  min-inline-size: 1.6em; text-align: center;
  padding: 2px .4ch; border-radius: var(--r-pill);
  background: var(--hand); color: var(--paper); }

/* ---- THE SPINNER (DESIGN.md component 23) ----------------------------
   Paul: *"turn them into spinners for the status changes"*. One control
   saying the state, a step each side, and the position under it. The two
   steps are 44px of thumb each and draw their mark as a CSS triangle: this
   page's marks come from ui/glyph.js and a stepper is not a mark, it is the
   edge of the control (the same way a slider's cap is not a glyph). */
.nu-spin{ display: flex; align-items: center; gap: var(--s1);
  min-inline-size: 0; }
.nu-spinstep{ flex: 0 0 auto; inline-size: var(--tap); min-block-size: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: var(--bw) solid var(--rule);
  border-radius: var(--r2); color: var(--ink); cursor: pointer; padding: 0; }
.nu-spinstep::before{ content: ""; inline-size: 0; block-size: 0;
  border-block-start: .34em solid transparent;
  border-block-end: .34em solid transparent; }
.nu-spinstep.is-prev::before{ border-inline-end: .42em solid currentColor; }
.nu-spinstep.is-next::before{ border-inline-start: .42em solid currentColor; }
.nu-spinstep:hover{ background: var(--panel); }
.nu-spinstep[aria-disabled="true"]{ border-style: dashed; color: var(--dim);
  cursor: not-allowed; }
.nu-spinword{ flex: 1 1 auto; min-inline-size: 0; text-align: center; }
.nu-spinpos{ flex: 0 0 auto;
  font: var(--num-fw) var(--t0)/1 var(--mono, monospace);
  font-variant-numeric: tabular-nums; color: var(--dim); }

/* ---- AN EXCLUSIVE STRIP IS A RAIL, NOT A ROW OF BUTTONS --------------
   Paul: *"Each exclusive of each other."* A single-select strip of chips and
   a multi-select set of them were the same picture — separate plates with a
   gap — so a hand could not tell whether pressing the next word would replace
   this one or join it. An exclusive strip is JOINED: no gap, one hairline
   between segments, rounded at the two ends only, and the standing word
   filled. Nothing about the address, the roles or `aria-pressed` moves. */
#pan-band .nu-wchips.is-exclusive{ gap: 0; }
#pan-band .nu-wchips.is-exclusive > .nu-wchip{
  border-radius: 0; margin: 0; border-inline-start-width: 0; }
#pan-band .nu-wchips.is-exclusive > .nu-wchip:first-of-type{
  border-inline-start-width: var(--bw);
  border-start-start-radius: var(--r2); border-end-start-radius: var(--r2); }
#pan-band .nu-wchips.is-exclusive > .nu-wchip:last-of-type{
  border-start-end-radius: var(--r2); border-end-end-radius: var(--r2); }
#pan-band .nu-wchips.is-exclusive > .nu-wchip[aria-pressed="true"]{
  background: var(--hand); color: var(--paper); }

/* ======================================================================
   THE LOZENGE FIELD (DESIGN.md component 16 · TABLE.md §11d, 2026-09-05)
   ======================================================================
   Paul: *"a novel interface for when there are tons of options and some of
   them can be multiple… tight lozenges, organized by color and clustered
   semantically by the kind of things they present… visibility into all of the
   options."*

   `nukernel/src/lozenge/` -> `nukernel/ui/lozenge.js` draws it; this is
   everything it looks like, and it is the whole of the component's colour,
   size and geometry — the TS sets no style and no height and stamps only
   `data-hue`, which is the cluster's index modulo eight.

   WHAT THIS REPLACES, MEASURED at 390 on the rendered page before it existed
   (scratchpad/design/before/measure.json):
     the drummer's does-sheet    ONE of sixty-eight words visible
     the scale picker            ONE of sixty-three
     the quality picker          ONE of forty-two
   — because a coarse pointer earns the phone's own wheel and a wheel shows one
   row. Every one of them is on the glass now.

   ---- THE HUES: HUE MEANS THE KIND, WEIGHT MEANS THE STATE ---------------
   Eight, and eight is a decision rather than a palette that ran out: past
   eight, hue stops being a category anyone can hold and becomes noise, which
   is the "rainbow" DESIGN.md §2/16 names and refuses. They are ONE ink each,
   used two ways — as the outline and word of a cluster's cold lozenges, and as
   the FILL of its hot ones — so a cluster reads as one family at a glance and
   the thing you have chosen reads as chosen from ten feet away.
   THEY ARE NOT THE SEMANTIC FOUR AND THEY ARE NOT THE VOICE SIX. `--hand`,
   `--clock`, `--meter` and `--flag` mean four specific things on this page
   (you set it · scheduled · measured · refused) and `--v0..3`/`--vb`/`--drum`
   mean WHICH PLAYER; a cluster hue that collided with either would say
   something it does not mean. These are their own ramp, at the same weight —
   every one dark enough for white text (the lightest, `--lz-h4` #B26B00, is
   4.0:1 on white) because the hot state fills. */
/* (`:root{ --lz-h0 … --lz-h7 }` STOOD HERE and is in nukernel/tokens.css with
   every other token. The eight are lifted for the deck — a hue chosen to sit
   on #F0EDE4 is not the same hue on #16191A — and the sentence above about
   white text is answered there by `--on-lamp`, which is the deck's own
   darkest rather than white. The paragraph above stays: it argues the SET,
   which is what this file draws.) */
.nu-lzcluster[data-hue="0"]{ --lz: var(--lz-h0); }
.nu-lzcluster[data-hue="1"]{ --lz: var(--lz-h1); }
.nu-lzcluster[data-hue="2"]{ --lz: var(--lz-h2); }
.nu-lzcluster[data-hue="3"]{ --lz: var(--lz-h3); }
.nu-lzcluster[data-hue="4"]{ --lz: var(--lz-h4); }
.nu-lzcluster[data-hue="5"]{ --lz: var(--lz-h5); }
.nu-lzcluster[data-hue="6"]{ --lz: var(--lz-h6); }
.nu-lzcluster[data-hue="7"]{ --lz: var(--lz-h7); }

/* ---- THE FIELD, AND ITS CLUSTERS -------------------------------------- */
.nu-lzfield{ display: block; inline-size: 100%; min-inline-size: 0; }
/* ---- AND THE CLUSTER IS `<nu-colhead>` NOW (2026-09-07) ---------------
   Paul: *"Make the table system with lozenges just list the items as cells.
   The lozenges are getting in the way. Add the tables and cells to the design
   system."* The tables and cells are in the system — `<nu-table>`,
   `<nu-colhead>`, `<nu-rowhead>`, `<nu-cell>`, drawn at THE ELEMENTS at the
   foot of this file — and `src/lozenge/field.ts` renders them where it used to
   render `.nu-lztrack` / `.nu-lzhead` / `.nu-lz`.

   SO EVERY RULE THAT DREW THE PILL IS DELETED FROM THIS FILE rather than left
   standing with no user (docs/DESIGN-SYSTEM.md §5). Gone, by selector:
   `.nu-lz` and its hover / hot / refused / quiet / is-off states, `.nu-lzword`
   and its `::after` bold-width reserve, `.nu-lzn`, `.nu-lzhead` and its hover,
   `.nu-lzheadword`, `.nu-lzcount`, `.nu-lzheld`, `.nu-lzcont`, `.nu-lzwrap`,
   `.nu-lztrack`, `.nu-lzcluster` and its `is-col` / `is-folded` / `is-standing`
   drawings. WHAT STAYS is the field itself (`.nu-lzfield`), its ONE say line
   (`.nu-lzsay`), the whole-field refusal line, and the eight cluster hues —
   and what the class names on the elements are FOR now is written on the two
   of them that keep one: `nu-cell` wears `nu-lz` and `nu-colhead` wears
   `nu-lzcluster` because `test/selects.js` asserts through those two strings
   and may not be edited. They are ADDRESSES on the tags, not looks; no rule in
   this file draws either one.

   ---- WHAT THE FIELD ADDS TO THE ELEMENTS, AND IT IS FOUR RULES ---------
   Everything below is ARRANGEMENT — which axis a column runs on, whose say
   line speaks, where the hue lands. The cell's own look (square at `--r2`, the
   44px floor, filled in `--lamp` when it stands, dashed when refused, quiet
   when inert) is the element's, once, at THE ELEMENTS. */

/* 1 · THE FIELD HAS ONE SAY LINE AND IT IS `.nu-lzsay`. `<nu-table>` and
   `<nu-cell>` each render a `.nu-elsay` of their own so that a table standing
   alone can still refuse out loud; inside a field they would be a second and a
   hundred-and-fiftieth copy of one sentence, and an empty inline box under
   every cell measured 11px of row pitch — 149 cells, 1,639px of reserved room
   for a sentence that has a place already. */
.nu-lzfield nu-table > .nu-elsay,
.nu-lzfield nu-cell > .nu-elsay{ display: none; }

/* 1b · AND AN UNHEADED COLUMN DRAWS NO HEADING. The leftovers — a word a
   caller's cluster list forgot, the absent detent that belongs to no family —
   are a real column with real cells and NO word, because this component
   prints no prose of its own (src/lozenge/api.ts). `<nu-colhead>` always
   renders its head so that a column standing alone can be named; a head with
   no word is a 44px empty box with a rule under it, which is a heading
   belonging to nothing — the exact thing Paul read off the phone in September
   and called *"a hairline rule above each cluster"*. The column keeps its
   fold-free open state either way (field.ts `shy`), so nothing is hidden. */
.nu-lzfield nu-colhead:not([label]) > .nu-elcolhead{ display: none; }

/* 2 · THE HUE SURVIVED, AND IT MOVED TO THE HEADING. ARGUED, BECAUSE IT IS
   THE ONE THING THIS PORT COULD HAVE THROWN AWAY BY ACCIDENT.
   The eight were the LOZENGE's idea: hue means the kind, weight means the
   state, and a cluster's ink was on the outline of its cold pills AND was the
   FILL of its hot one. A table of cells cannot keep the second half. §1 says
   there is ONE selection treatment on this page — a thing that is chosen is
   LIT, in `--lamp` — and eight different fills for "this is the answer" is the
   *"selected boxes are oddly selected"* bug wearing a palette. So the fill is
   the lamp's, for every cell in every family, and the hue keeps the half it is
   good at: telling one family from the next while a thumb pushes nineteen
   columns sideways past the edge of the screen. It is the heading's word and
   the heading's rule, which is where a reader is looking when they ask which
   family this is, and it is the only saturated colour left in the field.
   It stands down under `[current]`: the column holding the record's own answer
   is drawn in the lamp by the element's own rule, and a hue that outranked it
   would hide the answer to "where am I" behind the answer to "what kind". */
/* THE FIELD SETS THE HUE; THE ELEMENT DRAWS IT. This rule used to set both
   `color` and `border-block-end-color` three selectors deep, which put the
   family hue out of reach of any skin rule targeting the tag — §1a's law
   failing quietly rather than loudly. `.nu-elcolhead` reads `var(--lz, …)`
   itself now, so all that is left here is to stand the hue DOWN on the column
   holding the standing answer: `[current]` is the lamp's, and a family colour
   that outranked it would hide the answer to "where am I" behind the answer to
   "what kind".

   ===== AND IT OVERRIDES THE PAINT, NOT THE VARIABLE ====================
   This read `nu-colhead[current]{ --lz: initial }` for about an hour and
   `test/table.browser.js` T12g caught it: *"13 clusters, 12 headings, 9 of 8
   hues spent."* The census counts DISTINCT COMPUTED `--lz` VALUES across the
   clusters, so unsetting the variable on one of them does not remove a hue —
   it invents a ninth. The eight are a closed set (a ninth cluster reuses the
   first rather than inventing a colour, tokens.css says so on their own line),
   and a gate that counts them is right to fail an empty string.
   So the hue stays SET on every column, and what changes is what is drawn
   with it. A variable is what a thing IS; a declaration is what it LOOKS
   LIKE — and "this column holds the answer" is a look. */
.nu-lzfield nu-colhead[current] > .nu-elcolhead{
  color: var(--lamp); border-block-end-color: var(--lamp); }

/* 3 · THE WRAPPED SHAPE IS THE SAME COLUMN WITH ITS AXIS TURNED. A vocabulary
   that fits (TABLE.md §15 state A: the feel, the three-word sets) is not worth
   a sideways track, and a column of `18ch` lines would make a three-word field
   as tall as a nineteen-family one. So in the wrapped shape the column runs
   ACROSS and its cells are as wide as their words — still square, still
   starting at the same edge, still 44px of thumb, and never a pill. */
.nu-lzfield[data-table="false"] nu-colhead{
  flex-direction: row; flex-wrap: wrap; align-items: stretch;
  inline-size: 100%; max-inline-size: none; gap: var(--s2);
  margin-block-start: var(--s3); }
.nu-lzfield[data-table="false"] nu-colhead:first-child{ margin-block-start: 0; }
.nu-lzfield[data-table="false"] nu-cell{ inline-size: auto; min-inline-size: 0; }

/* ---- ONE OPTION IS `<nu-cell>`, AND THE PILL IS GONE -----------------
   Paul, 2026-09-07: *"The lozenges are getting in the way."*

   WHAT WAS DELETED HERE, AND WHY EACH RULE HAD TO GO RATHER THAN BE RETUNED.
   `.nu-lz` was a `--r-pill` inline-flex box that CENTRED its word, sized
   itself to that word, filled itself with its cluster's hue when it stood, and
   went `--fw-label` when it did — so it needed `.nu-lzword::after` to reserve
   the bold width or the box grew under the thumb that pressed it, and `.nu-lzn`
   needed a second pill inside the first to print a chain position.
   tokens.css: `--r-pill` is *"a noun, and only a noun — the LOZENGE wears it,
   and nothing else on this page does"*. An option in a list is not a noun you
   carry about; it is a LINE YOU READ. `<nu-cell>` is that line — square at
   `--r2`, the width of its column, its word at the column's start edge, 44px
   of thumb as a FLOOR and never as an overlap (law 2 survives the port whole,
   and the photograph behind it still applies) — and every state the pill was
   carrying is a cell state at THE ELEMENTS: `[aria-pressed]` fills in `--lamp`,
   `[aria-disabled]` dashes, `.is-quiet` goes inert without dashing, and
   `.nu-elorder` prints a chain position with no pill around it.
   NOT ONE OF THOSE RULES IS RESTATED HERE. Twelve selectors left this file:
   `.nu-lz`, `.nu-lz:hover`, `.nu-lz.is-hot`, `.nu-lz[aria-pressed="true"]`,
   `.nu-lz.is-hot > .nu-lzword`, `.nu-lz.is-hot:hover`, `.nu-lz[disabled]`,
   `.nu-lz[aria-disabled="true"]`, `.nu-lz.is-quiet`, `.nu-lzword`,
   `.nu-lzword::after` and `.nu-lzn`. */
/* THE SENTENCE A LONG PRESS SAYS. It reserves its own line whether or not
   there is one to say, for the reason `[data-k="board.legend"]` reserves two:
   GEOMETRY IS RESERVED AT CREATION, or a sentence arriving under a thumb moves
   the thing the thumb is on. */
/* 1.35em of line plus the .2em of padding above and below it: the box a
   one-line sentence needs, reserved before there is one to print — 1.4em
   measured 4px short of it once the padding was inside the border box, and a
   say line that grows when it speaks moves the field's own foot. */
.nu-lzsay{ margin: var(--s2) 0 0; min-block-size: 1.75em;
  padding: var(--s1) var(--s2);
  font: var(--fw-body) var(--t1)/1.35 var(--sans, inherit); color: var(--dim);
  /* ...AND IT IS READ WHERE THE THUMB IS. A field of forty-two words is
     taller than a phone, so a sentence printed at the foot of it is a sentence
     nobody scrolled to. It sticks to the bottom edge of whatever part of the
     field is on the glass — the room is still reserved at creation, so
     nothing moves when a sentence arrives; only where it is drawn changes. */
  position: sticky; inset-block-end: 0; z-index: 1; }
/* ...AND IT ONLY PAINTS WHEN IT IS SAYING SOMETHING. A sticky line needs an
   opaque ground to hold still over the pills passing under it, and a ground on
   an EMPTY line is a white band across the foot of every field on the page
   (measured, first drawing: `--paper` is `--panel` inside a sheet). The
   attribute is the widget's own (`?data-said`), the padding and the room are
   the same in both states, and what changes when a sentence arrives is a
   COLOUR AND NOT A BOX — measured: the say line's top moves 0px. */
.nu-lzsay[data-said]{ background: var(--well); color: var(--ink); }
/* THE WHOLE FIELD REFUSED: dashed and dim, with `menuField`'s own `.nu-why`
   line under it — one refusal line in this file, not two. Every cell in it is
   `refused` on its own account (field.ts law 7 hands the field's sentence down
   to each one), so the dash is the element's and there is nothing to say here
   but the sentence. */
.nu-lzfield.is-off > .nu-why{ display: block; color: var(--dim);
  font: var(--fw-body) var(--t1)/1.35 var(--sans, inherit); }
/* IT IS A FIELD IN A SHEET, AND IT TAKES THE SHEET'S WHOLE WIDTH — the same
   `grid-column: 1 / -1` an ops bar and an open chip strip take, for the same
   reason: all of it answers one question and a column three-fifths of the way
   across would be a second reading order. */
#pan-band .nu-vsheet > .nu-lzfield{ grid-column: 1 / -1; }

/* ======================================================================
   THE FIELD AS A TABLE (TABLE.md §19 · DESIGN.md component 22, 2026-09-07)
   ======================================================================
   Paul, of the instrument picker: *"For the instrument selector make it a
   horizontal table wider than the screen with the instruments in tables one
   per line per column."*

   ONE COLUMN PER FAMILY, ONE INSTRUMENT PER LINE, AND THE TRACK IS THE ONLY
   THING THAT SCROLLS. `overflow-x` on the TRACK and not on the field, because
   the field's say line has to stay outside the scrollport (it sticks to the
   foot and a sentence that scrolled sideways away from the word it is about
   is a sentence nobody reads). `overscroll-behavior-inline: none` — NOT
   `contain`, which is the value TABLE.md §15a forbids by name: `contain` stops
   the gesture chaining to the pane under it and still lets the track
   rubber-band inside itself, and a bounce translates the column heads with it.
   `none` stops the gesture at the track's ends and hands it to nobody, which
   is the same law §11c gave the pane and the page. Settled 2026-09-07.

   ---- AND EVERY WORD OF THAT IS NOW `<nu-table>`'s AND `<nu-colhead>`'s ----
   THE ARRANGEMENT DID NOT CHANGE; ITS OWNER DID. `.nu-lztrack` was deleted
   from this file the day `src/lozenge/field.ts` began rendering `<nu-table>`,
   and the track it drew is `.nu-eltrack` at THE ELEMENTS — the same flex row,
   the same `--s3` gap, the same `overflow-x: auto` with the same
   `overscroll-behavior-inline: none` and the same argued refusal of `contain`.
   `.nu-lzcluster.is-col` (`18ch`, `calc(100vw - 4 * var(--s4))`) is `nu-colhead`
   there, arrived at by measuring the same words; `.nu-lzwrap`'s downward
   `nowrap` stack is what `nu-colhead` IS; `.nu-lzcont` is
   `.nu-elcolhead.is-cont`, still a readout and still never a second door to
   one room; and `.nu-lzcluster.is-col.is-standing` is `[current]`, drawn in
   the lamp rather than in `--hand` because `aria-current` has one treatment on
   this page and a widget does not get its own.
   FOUR SELECTORS LEFT THIS FILE HERE and none of them was replaced by a copy:
   `.nu-lztrack`, `.nu-lzcluster.is-col`, `.nu-lzcont`,
   `.nu-lzcluster.is-col.is-standing`. */

/* ---- ONE OF A SET, OR SEVERAL AT ONCE (DESIGN.md component 24) --------
   Paul, 2026-09-07: *"Each exclusive of each other … and if things have
   multiple settings to make that really clear and shading or a little bit of a
   fill behind them."* A single-select field fills ONE cell and leaves the rest
   plain — which the element already does — and a CHAIN gives every cell a tick
   box at its start, so a hand can see from the shape of the control whether
   the words are alternatives or a set. The mark is the cell's own `::before`
   and costs no element and no string. It is the FIELD's fact and not the
   cell's — one cell cannot know whether its neighbours are alternatives — so
   it hangs off `[data-exclusive]` on the field, which is where the TS stamps
   it and where a gate reads it. */
.nu-lzfield[data-exclusive="false"] .nu-elcell::before{
  content: ""; flex: 0 0 auto; inline-size: .8em; block-size: .8em;
  margin-inline-end: var(--s1);
  border: var(--bw) solid currentColor; border-radius: var(--r0);
  opacity: .55; }
.nu-lzfield[data-exclusive="false"] .nu-elcell[aria-pressed="true"]::before{
  background: currentColor; opacity: 1; }

/* ---- THE FORMULA BAR MIRRORS THE CELL'S FIRST GROUP (2026-09-05) ------
   DESIGN.md §2 component 5: *"the selected cell's vector as chips/values"*.
   §11c gives "vector" a shape — four groups — and the bar holds the first of
   them, which is what this cell PLAYS. It is a READOUT (`src/table/grid.ts
   firstGroup` says why in full: one owner per fact, and the control is in the
   sheet), so it is type and not buttons: the label small and dim, the value in
   the label weight, quiet where nothing was written. */
.nu-fvec{ display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--s1) var(--s3); flex: 1 1 100%; min-inline-size: 0; }
.nu-fvechead{ font: var(--fw-block) var(--t0)/1.2 var(--sans, inherit);
  text-transform: uppercase; letter-spacing: .08em; color: var(--dim); }
.nu-fvecpair{ display: inline-flex; align-items: baseline; gap: var(--s1);
  min-inline-size: 0; }
.nu-fvecpair > small{ font: var(--fw-body) var(--t0)/1.2 var(--sans, inherit);
  color: var(--dim); }
.nu-fvecpair > b{ font: var(--fw-label) var(--t1)/1.2 var(--mono, monospace);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-inline-size: 16ch; }
/* QUIET IS DERIVED, BOLD IS WRITTEN — the page's own law, on a readout. */
.nu-fvecpair.is-derived > b{ font-weight: var(--fw-body); opacity: .75; }

/* ---- A GROUP HEADING WEARS ITS MARK (2026-09-05) ---------------------
   Paul: *"use more icons. Ideally the table is a large set of icons."* The
   grid was already 118 marks of 139 controls (§11b); a SHEET was none, and its
   headings are the thirteen things a hand scans. `ui/glyph.js GLYPH.group` is
   the one owner of the pictures; this is what one looks like beside its word —
   the heading's own dim ink, the same size, a hair of air, and `aria-hidden`
   because the word is right there. */
#pan-band .nu-grouphead{ display: flex; align-items: baseline; gap: var(--s2); }
#pan-band .nu-grouphead > .nu-g{ flex: 0 0 auto; font-size: var(--t2);
  line-height: 1; opacity: .8; }
#pan-band .nu-groupword{ min-inline-size: 0; }
/* ...AND THE STRIP OF CHIPS IS NOT A BOX OF CHIPS ANYWHERE (2026-09-05). The
   `.nu-menu` form was named first because that is where the census found it;
   at 1280 the sheet's own `.nu-wchips` wears the same plate and T12b counted
   it. One rule for both, because it is one strip. */
#pan-band .nu-wchips{ background: transparent; border: 0; box-shadow: none; }
/* ---- THE REFUSAL SAID OUT LOUD (2026-09-06, TABLE.md §15) --------------
   `.nu-lzsay`'s twin, for the widgets that are not a lozenge field: the chip
   strip, the ops bar, a refused field's own head, the slider. Same law, same
   shape — THE ROOM IS RESERVED AT CREATION so a sentence arriving under a
   thumb moves nothing, and only a COLOUR changes when one arrives. */
.nu-wsay{ margin: var(--s2) 0 0; min-block-size: 1.75em;
  padding: var(--s1) var(--s2);
  /* IT IS ITS OWN LINE. `.nu-sheetrow` is a wrapping FLEX row, so a say line
     with no basis would sit beside the control it explains and squeeze it;
     `flex: 1 0 100%` puts it on the line under, which is where a sentence
     goes. `grid-column` is for the two callers that are grids. */
  flex: 1 0 100%; grid-column: 1 / -1;
  font: var(--fw-body) var(--t1)/1.35 var(--sans, inherit); color: var(--dim); }
.nu-wsay[data-said]{ background: var(--well); color: var(--flag, var(--ink)); }
/* A REFUSED CHIP IS `aria-disabled` NOW AND NOT `disabled` (§15), so the
   rules that dressed `[disabled]` have to name the other spelling too — or a
   word a hand may not choose would be drawn exactly like one it may. */
.nu-wchip[aria-disabled="true"]{ opacity: .55; cursor: not-allowed;
  border-style: dashed; }
.nu-opbtn[aria-disabled="true"]{ opacity: .5; cursor: not-allowed; }

/* ======================================================================
   THE EQ CURVE AND THE XY PAD (2026-09-05 · DESIGN.md component 9 ·
   TABLE.md §11's third item)

   Paul: *"Look for places where UX could help like eq editors too."* These
   are the third and fourth modes of the ONE curve editor, so almost nothing
   is written here: the plate, the handle, the 44px target, the 11px dot, the
   focus ring, the refused state and the printed numbers are all
   `.nu-envplate` / `.nu-envh` / `.nu-envdot` / `.nu-envsays` above, unchanged
   and unduplicated. What a mode owns is the FURNITURE ITS OWN AXES NEED, and
   that is four rules.

   IT IS APPENDED AT THE END RATHER THAN EDITED INTO THE ENVELOPE BLOCK for
   the reason the RESTYLE block gives about itself: another hand was writing
   above it in the same round, and a block that can be pulled out whole is a
   block a reader can check.

   THE PLATE SURVIVES THE DE-PLATING because both drawings carry `.nu-env` on
   their root, which the `#pan-band` RESTYLE exempts by name along with
   `.nu-plate`, `#rack` and `.nu-strip`. DESIGN.md names the curve editor as
   one of exactly two components that may wear a plate, so this is the
   exemption working rather than an escape from it.
   ---------------------------------------------------------------------- */

/* 1 · A LINE ON THESE PLATES IS A NUMBER, NEVER A DECORATION.
   On the EQ it is the band's own frequency (fields.js EQ_BANDS: 120 Hz,
   1 kHz, 7.2 kHz — silkscreened on a real desk, so silkscreened here); on the
   pad it is a decade of the cutoff's own log axis. A hairline, at the rule's
   own weight, quieter than the zero line, because it is what the curve is
   read AGAINST and not part of it. */
.nu-eqrule{ stroke: var(--rule); stroke-width: var(--bw); opacity: .28; }

/* 2 · THE CROSSHAIRS ARE THE HANDLE, EXTENDED. The pad draws no response
   curve — nothing hands this page the chair's own filter law, and a curve
   drawn from an invented Q would be a picture of physics nobody measured —
   so the two rules through the handle ARE the reading: they carry the hand's
   own cobalt, at the curve's weight, because what is drawn is what is being
   set. */
.nu-xycross{ stroke: var(--hand); stroke-width: var(--bw-hard); opacity: .5; }

/* 3 · WHERE EACH ONE SITS IN THE SURFACE AROUND IT. The EQ is a row of the
   channel strip and takes the row's full width (it replaced three `.nu-vcol`
   columns, so it must not inherit their flex); the pad is a picture over the
   knob table, the same seat `tractPad` takes, with the block of air DESIGN.md
   §1 puts between two questions. */
.nu-eqwrap{ display: block; inline-size: 100%; min-inline-size: 0; }
.nu-seatxy{ display: block; inline-size: 100%; min-inline-size: 0;
  margin-block: var(--s3) var(--s5); }

/* 4 · A REFUSED AXIS PRINTS ITS SENTENCE BESIDE ITS NUMBER — the
   no-silent-grey law, in the pad's own readout row rather than somewhere
   else on the page. */
.nu-envsay > .nu-why{ margin-inline-start: var(--s2); }

/* ---- A STICKY HEAD MUST BE OPAQUE (2026-09-05, and it was measured) ---
   The restyle took the table's own `background: var(--panel)` off (§1 above),
   which is right — a grid is not a plate — and it took the heads' ground with
   it, because a `<th>` had no ground of its own. MEASURED at 1280 on the row
   sheet: the sheet's FORM group scrolled straight THROUGH the column heads,
   "second ending / coda / to coda" printed over "stab lead vocal drone", and
   the corner too. A `position: sticky` box does not stop what is under it; a
   ground does.
   IT IS `--ground` AND NOT `--paper`, and the difference is what the eye sees:
   inside `.nu-pane` `--paper` is `--panel` (white), so a head painted with it
   would be the white strip the restyle just deleted. `--ground` is the page's
   own cream, which is exactly what is behind the table — so the head is
   invisible at rest and opaque under a scroll, which is the whole requirement.
   A GROUND IS NOT A PLATE: T12b counts a box with a border on all four sides
   AND a ground, and these have one hairline each. */
#pan-band .nu-wordgrid thead th,
#pan-band .nu-wordgrid tbody th.nu-srowh,
#pan-band .nu-wordgrid thead th.nu-cornerh,
#pan-band .nu-wordgrid tbody th.nu-plusrowh{ background: var(--ground); }

/* ...AND THE GROUND HAS TO COVER THE BORDER-SPACING, WHICH IS NOT THE CELL
   (repaired 2026-09-05). A ground on a `<th>` paints the CELL. This table is
   `border-collapse: separate; border-spacing: 3px` (the block above `.nu-srowh`
   says why the freeze is pinned at 3px), so between TIME and RULES, between
   RULES and PHRASES, and between the last of them and the column heads there
   is a 3px line of table that no cell owns — and a sheet or a lozenge field
   scrolling under the stack printed straight through it. MEASURED at 390 on
   the cell sheet ("…default | fill…" between TIME and RULES, "PHRASE" under
   PHRASES) and on the scale picker ("DIMINISHED, HALF-WHOLE" between TIME and
   RULES). Three rows that are opaque with two seams through them are not a
   sticky head; they are a venetian blind.
   A SHADOW AND NOT AN OUTLINE, and the difference is what the tape measure
   reads: `measure.cjs` counts any outline over a hairline as a THICK RULE, and
   the pass's own headline is "rules thicker than a hairline 9 → 0" — a ground
   that reported itself as twenty-one new rules would be a lie in the next
   measurement. An outer `box-shadow` takes no layout (the pane's scrollWidth
   is unchanged, measured), paints OUTSIDE the border box so each head keeps
   its own hairline, and paints in the head's own stacking context —
   z 2/4, over `.nu-vsheet`'s 0. 3px on every side closes each seam from both
   directions at once, and the column gap beside the frozen row head with it.
   IT IS `--ground`, the same token the cells carry, for the same reason: it is
   the page's own cream, so the seam is invisible at rest and opaque under a
   scroll. */
#pan-band .nu-wordgrid thead th,
#pan-band .nu-wordgrid tbody th.nu-srowh,
#pan-band .nu-wordgrid thead th.nu-cornerh,
#pan-band .nu-wordgrid tbody th.nu-plusrowh{
  box-shadow: 0 0 0 3px var(--ground); }
/* ...AND THE PLAYING SECTION'S ROW BEATS BOTH OF THEM (2026-09-05, §13f).
   MEASURED the hour it landed: the whole-row light was written as
   `.nu-sheetgrid tbody tr.is-playing > th` — higher specificity than the
   `--paper` rule it was aimed at and lower than THIS one, which is
   id-scoped — so the class went on, the memo said it had, and the row drew
   `rgb(240, 237, 228)`, the resting cream, at every width. A ground that is
   declared and never arrives is this box's characteristic bug and it does not
   get to happen in the round that is about being able to SEE the playhead. So
   the light is stated where the ground is, at the same weight, and the 3px
   ring the seam-closer paints round the row head is painted in the light too —
   a cream ring round a lit head is the seam reopened. */
/* ---- ...AND IT IS ONE BAND AND A QUIETER ONE (2026-09-08, §24) --------
   Paul: *"The playing bar is blocky in the sections"* and *"The orange is a
   strange highlight color."*

   TWO COMPLAINTS, ONE RULE. `background: var(--lit)` was set on every `<th>`
   and every `<td>` of the playing row, and `.nu-trims` sets `border-spacing:
   3px` — so the cells CANNOT touch, and a lit section drew as five or ten
   separate saturated rectangles with a gap between each, marching down the
   grid. That is the "blocky": not the height and not the brightness, the GAPS.
   And `--lit` is `--clock` at 85%, which is a near-full-strength alarm red
   used as a resting ground for a whole row — the "strange highlight colour".
   THE PAINT MOVES TO THE `<tr>`, which is the one box in a table that has no
   gaps in it: under `border-collapse: separate` a row's background paints
   behind the whole row, spacing included, so the same idea becomes ONE
   continuous band with the cells sitting on it.
   AND `--lit` COMES DOWN FROM 85% TO 22%, which is `--clock-tint`'s own
   strength — the number this page already uses when the clock has to be
   visible under type rather than instead of it. The row is still unmistakably
   lit and its words are still `--ink` on it, which is what the 85% was
   reaching for and was overshooting: at 85% the ink was fighting the ground.
   DESIGN.md COMPONENT 11 IS KEPT, NOT REVERSED. Paul's earlier sentence is on
   it — *"Really light up the sections as you move through them — not just a
   tiny halo around the number"* — so the row is still lit WHOLE. What it stops
   being is segmented and shouting. A 3px `--clock` rule down the start edge
   carries the leading edge, which is `.nu-here`'s own idiom (nu.css:203).
   THE 3px RING GOES WITH THE PER-CELL PAINT. Its job was to keep the
   seam-closer's cream ring from cutting a lit head out of a lit row; with the
   band behind the whole row there is no seam to reopen — the ring paints over
   a band that is already continuous underneath it. */
#pan-band .nu-wordgrid tbody tr.is-playing{ background: var(--lit); }
#pan-band .nu-wordgrid tbody tr.is-playing > th,
#pan-band .nu-wordgrid tbody tr.is-playing > td{
  background: transparent; color: var(--ink); }
#pan-band .nu-wordgrid tbody tr.is-playing > th.nu-srowh{
  box-shadow: inset 3px 0 0 var(--clock); }
/* ...AND THE PLAYHEAD SURVIVES IT. `.nu-colhead.is-sounding` spends its own
   `box-shadow` on the clock's edge bar under a sounding player; the rule above
   is id-scoped and would have outranked it, so the two are ONE list here, the
   lamp first. Nothing appears or disappears: the ground ring is on the head
   whether it is sounding or not. */
#pan-band .nu-wordgrid thead th.nu-colhead.is-sounding{
  box-shadow: inset 0 -3px 0 var(--clock), 0 0 0 3px var(--ground); }

/* ---- ...AND IT MUST BE ABOVE THE SHEET, NOT ONLY OPAQUE ---------------
   A ground stops what is UNDER a head; it does not stop what paints OVER it.
   `.nu-vsheet` is `position: sticky` too (it rides the pane's horizontal
   scroll — its own block says why), and two sticky boxes with no z-index of
   their own paint in document order, so the sheet — which comes later — won
   and its first rows printed across the column heads. MEASURED before this
   rule at 1280 on the drummer's cell: "pitched parts only" over "+ line",
   and the phrases row over the seven players. It was true on v281 too and
   the restyle only made it visible, because the heads had an opaque ground
   by accident (`table{ background: var(--panel) }`) and now have one on
   purpose.
   THE HEADS ARE 2 AND 5 (`thead th` and `.nu-spheadcell`, both already
   declared); the sheet takes 0, which is under both and still above the
   grid's own cells. */
#pan-band .nu-vsheet{ z-index: 0; }

/* ======================================================================
   WAVE C · THE GENRE INDEX IS SEARCHABLE (2026-09-06)
   ======================================================================
   docs/REDESIGN-SCOPE.md item 7, and docs/WAVE-C.md carries the numbers.
   APPENDED AS ONE BLOCK, AT THE END, DELIBERATELY: this wave ran beside
   another hand working in this stylesheet, so it adds and never rewrites.
   Fold it into the atlas section (beside `#atlasIndex`, ~3851) when the two
   hands meet.
   IT WAS FOUR RULES AND IS FOUR STILL, but two of them are different rules:
   the era strip's pair went with the chips on 2026-09-06 (Paul: *"Get rid of
   the buttons for eras like 'the old Stone Age' those all go."*) and the
   empty answer's pair arrived in their place. The block below is the whole
   of what the search strip needs.

   THE STRIP ITSELF NEEDS NO RULE. It is a `<p class="nu-row">` — the flex
   row nu.css has owned since the tempo verbs — so the gap, the wrap and the
   vertical centring are already right. What is left is the three things
   `.nu-row` cannot know: which child takes the slack, that a hidden row in a
   `display: grid` list has to be told to go away, and — since 2026-09-06,
   which is when two of the strip's three controls were deleted — that a status
   line nobody is talking to may take no box at all. */

/* 1 · A HIDDEN ROW IS HIDDEN. `.nu-ixli` sets `display: grid`, and an author
      rule beats the UA sheet's `[hidden]{ display: none }` — so without this
      line every filtered-out row stayed on screen and the filter did nothing
      visible at all. (It is the whole feature in one declaration, which is
      exactly the kind of rule that is easy to leave out and impossible to
      miss once it is measured: the gate counts rendered rows, not
      attributes.) */
.nu-ixli[hidden]{ display: none !important; }

/* 2 · THE FIELD IS THE LIST'S OWN HEAD (2026-09-08, TABLE.md §23) --------
      Paul: *"The 'Find a genre' should be integrated into it and the 'clear
      text' button is now on the right with no label, but it should be an (x)
      on the inside of the button."*

      WHAT STOOD HERE, and its one surviving clause: "THE FIELD TAKES THE
      SLACK, AND SINCE 2026-09-06 IT IS THE WHOLE ROW. `min-inline-size: 0` is
      the flex-child law this sheet applies everywhere a text control sits in a
      row: without it an <input>'s intrinsic width is its `size` attribute and
      the row overflows at 320." That law is untouched and is why the field
      still carries `min-inline-size: 0` below.
      WHAT CHANGED IS WHERE THE ROW IS. It was `#atlasFind`, a `.nu-row` with
      `margin-block-start: var(--s4)` standing ABOVE the list; it is the first
      child of `#atlasIndex` now (ui/atlas.js `insertBefore`), STICKY at the
      top of the scrollport. Two plates with a gap between them read as a
      filter and a list; one plate with a field across its top reads as a list
      you can type into.
      STICKY AND NOT STATIC, because the box scrolls 502 rows: a search field
      that leaves the screen at row nine is a search field you have to scroll
      back to. It is the only pinned thing inside this box, so the one-band law
      §13 wrote about the table's own head is not in question here.
      IT PAYS THE GUTTER THE BOX STOPPED PAYING, AND IT PAYS THE ROWS'. The
      list is full-bleed now (`#atlasIndex` above), so only its EDGES moved:
      `.nu-ixli` has carried `padding-inline: var(--s4)` since it was written,
      and the head takes the same `--s4` so the field's left edge stands on the
      same line as every year in the column under it. That is what makes the
      two read as one object rather than as a field parked on a list.
      (`var(--gl)`/`var(--gr)` WERE TRIED FIRST AND ARE WRONG HERE: `--gl` is
      `0px` — *"the views run to the glass"* — so the field went edge to edge
      and its text touched the screen while the rows stayed inset. The gutter
      this box owes is the PANEL's `--s4`, which is what it was inside before
      it went full-bleed.) */
/* ---- AND IT IS A LIGHT STRIP WITH NO EDGES (2026-09-08) ---------------
   Paul: *"Make the search bar a light color and get rid of all borders, just
   make it 100% and keep the text aligned with the dates below. Light color
   with dark search term and dismiss button."*

   THREE THINGS GO: the head's own bottom rule, the field's border, and the
   field's corner. What is left is a BAND OF LIGHT across the top of the list —
   the one place on this page a hand types — and a band needs no frame to say
   where it is. MEASURED at 390: the field was 364.4px inside a 390px head with
   a 1px border and a 6px radius; it is the full 390 now.
   THE COLOUR IS THE PAGE'S OWN PAIR, REVERSED — `--legend` as the ground and
   `--ground` as the ink, which are the two tokens every skin already defines
   against each other. So this strip is light on the phosphor skin and DARK on
   `skins/paper.css` without a second declaration: it inverts with the room
   instead of being painted white here (§20's skin law, and the reason the
   round before this one could prove it with one file).
   THE TEXT STANDS ON THE YEARS. `.nu-ixli` pays `--s3` at this width (its own
   rule, and the measurement: every year in the column starts 8.8px in), so the
   field pays exactly that and no more — its caret and the dates under it are
   on one line. The head keeps `--s1` of block padding and spends NO inline
   padding at all: the field is the head. */
#atlasFind{ position: sticky; inset-block-start: 0; z-index: 1;
  margin-block-start: 0; padding: var(--s1) 0;
  background: var(--legend);
  border-block-end: 0; }
/* THE FIELD IS THE WHOLE HEAD, and the × rides INSIDE it (below), so the only
   thing this rule has to do is leave a thumb's worth of room at the end for a
   mark that is drawn over the text. */
#atlasQ{ flex: 1 1 auto; min-inline-size: 0; block-size: var(--tap);
  inline-size: 100%;
  padding-inline: var(--s3) var(--s5);
  background: var(--legend); color: var(--ground);
  border: 0; border-radius: 0; box-shadow: none; }
#atlasQ::placeholder{ color: color-mix(in oklab, var(--ground) 55%, var(--legend)); }
#atlasQ:focus-visible{ outline: var(--bw-hard) solid var(--ground);
  outline-offset: calc(-1 * var(--bw-hard)); }
/* ONE ×, AND IT IS OURS. Paul: *"The 'dismiss search text' icon has an x in a
   circle and another x right after that. Keep the one in the circle."* The
   field is `type="search"`, so the PLATFORM draws a clear mark of its own —
   a circled × on Safari, a bare one on Chrome — beside the button this page
   draws for the platforms that draw nothing (iOS Safari draws nothing, which
   is why `#atlasClear` exists at all). Two marks, one job. The platform's is
   removed and ours takes the circle: a ring in the field's own ink, so the
   control looks the same on every device instead of looking like whatever the
   browser felt like. */
#atlasQ::-webkit-search-cancel-button,
#atlasQ::-webkit-search-decoration{ -webkit-appearance: none; appearance: none;
  display: none; }
/* ---- THE CLEAR IS AN × INSIDE THE FIELD (2026-09-08) ------------------
   Paul: *"the 'clear text' button is now on the right with no label, but it
   should be an (x) on the inside of the button."*

   IT WAS A SEPARATE SQUARE BESIDE THE FIELD — its own plate, its own border,
   its own 44px of the row — and it was EMPTY, which is the "no label" in that
   sentence: ui/atlas.js built its glyph with `el("span", "\u2715")`, and `el`'s
   second argument is a property bag, so `Object.assign(span, "✕")` returned a
   blank span. The mark is on the button itself now and the button is INSIDE
   the field's box: absolutely positioned at its inline end, over the text,
   which is where every search control on every platform puts it and what "on
   the inside" means.
   NO PLATE OF ITS OWN. A border and a ground around a mark that sits inside
   another control's border draws a box inside a box; the target is still the
   full `--tap` square (it is a hit area, not a picture), and what says it is
   pressable is the mark going from `--dim` to `--ink` under a hand.
   IT IS STILL `hidden` UNTIL THERE IS SOMETHING TO CLEAR, which is the 
   2026-09-06 clause worth keeping whole: a control that is always present and
   inert half the time is the silent grey this page legislates against. */
/* THE MARK AND THE TARGET ARE DIFFERENT SIZES, and the reason is written at
   `.nu-elixclear` in the design system, which is the same control: a `--tap`
   hit area overhanging left over the text, its glyph pushed to the box's end
   so what is DRAWN sits in the field's `--s5` of end padding. The app is not
   walked by D5's spacing ramp (that gate reads `#gal`), and it obeys it
   anyway — one control, one construction, or the two drift the first time
   either is touched. */
#atlasClear{ position: absolute; inset-block-start: 50%;
  inset-inline-end: var(--s4); transform: translateY(-50%);
  inline-size: var(--tap); block-size: var(--tap);
  display: flex; align-items: center; justify-content: flex-end;
  padding: 0 var(--s2);
  background: none; color: var(--ground);
  border: 0; border-radius: 0;
  font: 1.25rem/1 var(--sym, var(--sans, inherit)); cursor: pointer; }
/* THE RING IS ON THE MARK AND NOT ON THE TARGET — the target is the `--tap`
   square above, which stays invisible; what is drawn is a 24px circle round
   the ×, in the strip's own ink. */
/* THE BUTTON'S OWN MARK IS A TEXT NODE, WHICH IS WHY THERE WERE TWO
   (2026-09-08). Paul: *"There are still two close buttons in the search bar."*
   ui/atlas.js sets the ✕ with `textContent` — deliberately, and its own comment
   says why ("THE MARK IS SET AND NOT `el`-ED, AND THAT IS A BUG BEING FIXED")
   — so `#atlasClear > *{ display: none }` matched no element and hid nothing:
   the ring drawn by `::before` landed BESIDE the character the button already
   said. `font-size: 0` is what reaches a text node, and the ring takes an
   absolute size because an `em` of nothing is nothing. */
#atlasClear{ font-size: 0; }
#atlasClear::before{ content: "\2715"; display: flex;
  align-items: center; justify-content: center;
  inline-size: 24px; block-size: 24px; font-size: 12px; line-height: 1;
  border: var(--bw) solid currentColor; border-radius: var(--r-pill); }
#atlasClear:hover{ color: var(--ground); background: none; opacity: .7; }
#atlasClear[hidden]{ display: none; }
/* 3 · THE COUNT AND THE ERA CHIPS ARE DELETED (2026-09-06) --------------
      Paul: *"Get rid of the buttons for eras like 'the old Stone Age' those
      all go."* and *"Get rid of 'All 479 records'."* `#atlasJump` (the
      sideways strip and its chips) and `#atlasCount` (the caption that rode
      the field's line) went with the controls, so the strip is ONE line of 44
      px — the field — against the 118 it cost yesterday.

      WHAT KEPT ONE RULE: the empty answer. A search that matches nothing says
      so in `#atlasNone`, inside the list's own box, where the rows would be. It
      is a live region, so it is never `display: none` and never `hidden` — a
      status removed from the accessibility tree and put back does not announce
      — it is EMPTY at rest and an empty one has no box: no height, no margin,
      nothing to see, and still in the tree. `#atlasSay` takes the same rule for
      the same reason (it is the atlas's status line — "Writing Kingston 1969…",
      the record it wrote, every refusal a link can earn — and it is blank
      almost always). */
#atlasNone{ margin: var(--s3); }
#atlasSay:empty, #atlasNone:empty{ display: block; block-size: 0;
  margin-block: 0; padding: 0; overflow: hidden; }

/* ======================================================================
   THE ELEMENTS (docs/DESIGN-SYSTEM.md steps 2 and 3, 2026-09-07)

   Paul: *"Styles should be in CSS and elements should be lit web components
   that are carefully defined."* So: `nukernel/src/ui/**` carries the
   STRUCTURE — the tags, the attributes, the keyboard, the refusal — and this
   block carries the LOOK, all of it, for every one of them. There is no
   `css``` block anywhere in src/ui and there may not be one: an element that
   painted itself would be a second stylesheet, and a component's colour would
   stop being a token the moment somebody wanted to change it.

   THEY RENDER INTO THE LIGHT DOM, which is why these are ordinary selectors
   and not `::part`. The decision and its four reasons are written down in
   `nukernel/src/ui/base.ts`; the short form is that a shadow root is a wall
   this file cannot reach through, and this file is meant to be in charge.

   THE FIVE STATES A HAND CAN ASSERT ARE ATTRIBUTES ON THE HOST —
   `[selected] [open] [refused] [busy]`, and rest is their absence — so every
   one of them is one selector here and one query in test/design-system.js.
   THE TWO THE BROWSER OWNS ARE PAIRED WITH `[data-demo]` IN THE SAME RULE:
   `:hover, [data-demo="hover"]` and `:focus-visible, [data-demo="focus"]`.
   The gallery forces the demo spelling, so what it shows is drawn by the
   declaration that draws the live page, and the two cannot diverge without a
   syntax error.

   ONE SELECTION TREATMENT AND ONE FOCUS TREATMENT, AND THEY ARE DIFFERENT
   (§1, Paul: *"Selected boxes are oddly selected"*):
     SELECTED  the lamp is ON — the control is FILLED in `--lamp` and its word
               is `--on-fill`. Nothing else on this page fills.
     FOCUS     a lamp RING — `--bw-hard` of `--lamp` OUTSIDE the box, offset,
               so a control can be focused and selected at once and read as
               both.
     HOVER     the AMBER edge — armed, about to matter. It is a border colour
               and never a fill, so a pointer passing over a strip cannot be
               mistaken for a hand having chosen something.
     OPEN      the lamp's GLOW — a `--hand-tint` ground and a `--lamp`
               hairline: something of this control's is standing under it.
     REFUSED   dashed, quiet, and it SAYS WHY (component 14). Never `disabled`.
     BUSY      an amber hairline along the foot. Static, because a page that
               animates a wait is a page that animates under `prefers-reduced-
               motion` too.
   ====================================================================== */

/* ---- the host boxes ---------------------------------------------------
   A host is a layout box and nothing else: it draws no ground, no border and
   no radius, so `<nu-button>` in a flex row behaves exactly as the `<button>`
   inside it would have. */
nu-button, nu-icon-button, nu-rail, nu-spinner{
  display: inline-flex; flex-direction: column; align-items: stretch;
  min-inline-size: 0; }
nu-lamp{ display: inline-flex; align-items: center; }
nu-legend, nu-value{ display: inline-flex; align-items: baseline;
  gap: var(--s1); min-inline-size: 0; }

/* ---- THE SAY LINE, ONE PER WIDGET (component 14) ---------------------
   `role="status"`, so the sentence is heard as well as seen, and EMPTY IT
   TAKES NO ROOM — a reason arriving under a thumb may not move the control
   the thumb is on, and a reserved blank line under every button on the page
   would be the other way of getting that wrong. The room is bought back by
   the host being a column: the sentence grows DOWN, away from the finger. */
.nu-elsay{ display: block; font: var(--fw-body) var(--t2)/1.3 var(--sans);
  color: var(--flag); padding-block-start: var(--s1); }
.nu-elsay:empty{ display: none; }

/* ---- <nu-button> · <nu-icon-button> ---------------------------------- */
.nu-elbtn{ display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s2);
  min-block-size: var(--tap); min-inline-size: var(--tap);
  padding: var(--s1) var(--s3);
  font: var(--btn-fw) var(--btn-fs)/1.2 var(--sans);
  color: var(--legend); background: var(--grey1);
  border: var(--bw) solid var(--rule); border-radius: var(--r1);
  cursor: pointer; -webkit-tap-highlight-color: transparent; }
.nu-elicon{ padding: 0; inline-size: var(--tap); flex: 0 0 auto; }
.nu-elmark{ font-family: var(--sym); font-size: var(--t4); line-height: 1;
  flex: 0 0 auto; }
.nu-elword{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* THE THREE TONES. A tone says what KIND of thing a press does, at rest,
   before anything is selected: the panel's own, the one that is ON, and the
   one that destroys. It is a border colour and never a fill, because a fill
   is what SELECTED means and one look may not mean two things. */
.nu-elbtn{ border-color: var(--rule); }
[tone="lamp"] > .nu-elbtn{ border-color: var(--lamp); color: var(--lamp); }
[tone="clip"] > .nu-elbtn{ border-color: var(--clip); color: var(--clip); }

[size="tight"] > .nu-elbtn{ min-block-size: var(--ctl); }

.nu-elbtn:hover, [data-demo="hover"] > .nu-elbtn{
  border-color: var(--armed); background: var(--grey2); }
.nu-elbtn:focus-visible, [data-demo="focus"] > .nu-elbtn{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: var(--press); }
.nu-elbtn[aria-pressed="true"]{
  background: var(--lamp); border-color: var(--lamp); color: var(--on-fill); }
.nu-elbtn[aria-pressed="true"]:hover,
[data-demo="hover"] > .nu-elbtn[aria-pressed="true"]{
  background: var(--lamp-hot); border-color: var(--lamp-hot); }
.nu-elbtn[aria-expanded="true"]{
  background: var(--hand-tint); border-color: var(--lamp); color: var(--legend); }
.nu-elbtn[aria-disabled="true"]{
  border-style: dashed; border-color: var(--legend-faint);
  color: var(--legend-faint); background: transparent; }
.nu-elbtn[aria-busy="true"]{
  box-shadow: inset 0 calc(-1 * var(--bw-hard)) 0 0 var(--armed);
  color: var(--legend-dim); }

/* ---- <nu-lamp> — the one place saturated colour is allowed ----------- */
.nu-ellamp{ display: block; background: var(--grey3);
  border-radius: var(--r-pill); }
[shape="dot"] > .nu-ellamp{ inline-size: 10px; block-size: 10px; }
[shape="bar"] > .nu-ellamp{ inline-size: 4px; block-size: 18px;
  border-radius: var(--r2); }
nu-lamp[on][means="hand"]  > .nu-ellamp{ background: var(--hand); }
nu-lamp[on][means="clock"] > .nu-ellamp{ background: var(--clock); }
nu-lamp[on][means="meter"] > .nu-ellamp{ background: var(--meter); }
nu-lamp[on][means="flag"]  > .nu-ellamp{ background: var(--flag); }

/* ---- <nu-legend> — printed, and it never lights ---------------------- */
.nu-ellegend{ display: inline-flex; align-items: center; gap: var(--s1);
  font: var(--fw-label) var(--t2)/1.2 var(--sans);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--legend-dim); }
label.nu-ellegend{ cursor: pointer; }
.nu-ellegend .nu-elmark{ font-size: var(--t2); }

/* ---- <nu-value> — the screen ----------------------------------------- */
.nu-elval{ font: var(--num-fw) var(--t3)/1.2 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--value); }
.nu-elunit{ font: var(--fw-body) var(--t1)/1.2 var(--mono);
  color: var(--legend-dim); }
/* DERIVED IS QUIET AND BLANK IS THE DEFAULT (§3): a value nobody set reads in
   the second voice at body weight, so "somebody chose this" is legible across
   a sheet without a word being spent saying it. */
nu-value[derived] .nu-elval{ color: var(--legend-dim); font-weight: 400; }
.nu-elval[data-blank]{ color: var(--legend-dim); font-style: normal; }
nu-value[means="meter"] .nu-elval{ color: var(--meter); }
nu-value[means="clock"] .nu-elval{ color: var(--clock); }
nu-value[means="flag"]  .nu-elval{ color: var(--flag); }

/* ---- <nu-rail> — ONE OF A SET LOOKS LIKE ONE OF A SET ----------------
   TABLE.md §19, Paul: *"Each exclusive of each other."* JOINED — no gap, one
   hairline between segments, rounded at the two ends only — so the strip
   reads as one control holding one answer rather than as N buttons that
   happen to be near each other. `data-exclusive="false"` is a CHAIN and comes
   apart into separate pills, which is the same fact said the other way. */
.nu-elrail{ display: inline-flex; align-items: stretch; min-inline-size: 0; }
.nu-elseg{ min-block-size: var(--tap); padding: var(--s1) var(--s3);
  font: var(--btn-fw) var(--btn-fs)/1.2 var(--sans);
  color: var(--legend); background: var(--grey1);
  border: var(--bw) solid var(--rule); border-radius: 0;
  cursor: pointer; white-space: nowrap;
  -webkit-tap-highlight-color: transparent; }
.nu-elrail[data-exclusive="true"] > .nu-elseg + .nu-elseg{
  border-inline-start-width: 0; }
.nu-elrail[data-exclusive="true"] > .nu-elseg:first-child{
  border-start-start-radius: var(--r1); border-end-start-radius: var(--r1); }
.nu-elrail[data-exclusive="true"] > .nu-elseg:last-child{
  border-start-end-radius: var(--r1); border-end-end-radius: var(--r1); }
.nu-elrail[data-exclusive="false"]{ gap: var(--s2); flex-wrap: wrap; }
.nu-elrail[data-exclusive="false"] > .nu-elseg{ border-radius: var(--r-pill);
  padding-inline: var(--s4); }
.nu-elseg:hover, [data-demo="hover"] .nu-elseg:first-child{
  border-color: var(--armed); background: var(--grey2); }
.nu-elseg:focus-visible, [data-demo="focus"] .nu-elseg[aria-pressed="true"]{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: calc(-1 * var(--bw-hard)); }
.nu-elseg[aria-pressed="true"]{
  background: var(--lamp); border-color: var(--lamp); color: var(--on-fill); }
.nu-elseg[aria-disabled="true"]{ border-style: dashed;
  color: var(--legend-faint); background: transparent; }
nu-rail[busy] .nu-elseg{ box-shadow: inset 0 calc(-1 * var(--bw-hard)) 0 0 var(--armed); }

/* ---- <nu-spinner> — a state, not a shopping list ---------------------
   TABLE.md §19, Paul: *"turn them into spinners for the status changes"*, and
   then: *"The spinner can just be a single button without arrows left and
   right. Click to rotate it."*

   SO THE SPINNER IS ONE BUTTON AND `.nu-elstep` IS GONE — the rule and the
   two `::before` marks with it, its last user having left the tree. The box
   that was the group is now the button itself: the word, and the position
   after it in the quiet register, on one 44px tap. A press rotates; the arrow
   keys are the way back, and a key needs no glass.
   THE WORD IS A SPAN AND NOT THE BUTTON, so the position can sit beside it
   without the two sharing one text style — the word is the SCREEN's phosphor
   and the count is a printed legend, which is §0's split inside one control. */
.nu-elspin{ display: inline-flex; align-items: center; min-inline-size: 0;
  gap: var(--s2);
  min-block-size: var(--tap);
  /* `padding-block: 0` IS LOAD-BEARING: a <button> the page does not size
     carries the user agent's own `1px 6px`, which is a value off the ramp
     nobody chose. */
  padding-block: 0; padding-inline: var(--s3);
  border: var(--bw) solid var(--rule); border-radius: var(--r1);
  background: var(--grey1); color: var(--value);
  /* THE FONT IS SET HERE AND NOT ONLY ON THE WORD, because the ramp is in
     `em`: a <button> the page leaves unstyled resolves `--s2` against the
     user agent's 13.3px Arial, which is a gap nobody chose in a face nobody
     chose. */
  font: var(--num-fw) var(--btn-fs)/1.2 var(--sans);
  cursor: pointer; text-align: start;
  -webkit-tap-highlight-color: transparent; }
.nu-elspinword{ flex: 1 1 auto; min-inline-size: 0;
  font: var(--num-fw) var(--btn-fs)/1.2 var(--sans);
  color: var(--value); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.nu-elpos{ flex: 0 0 auto;
  font: var(--fw-body) var(--t1)/1 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--legend-dim); }
.nu-elspin:hover, [data-demo="hover"] > .nu-elspin{
  border-color: var(--armed); }
[data-demo="hover"] > .nu-elspin > .nu-elspinword{ color: var(--armed); }
.nu-elspin:focus-visible, [data-demo="focus"] > .nu-elspin{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: var(--press); }
nu-spinner[refused] > .nu-elspin{ border-style: dashed; }
nu-spinner[refused] .nu-elspinword,
nu-spinner[refused] .nu-elpos{ color: var(--legend-faint); }
nu-spinner[busy] > .nu-elspin{
  box-shadow: inset 0 calc(-1 * var(--bw-hard)) 0 0 var(--armed); }

/* ---- <nu-table> · <nu-colhead> · <nu-rowhead> · <nu-cell> -------------
   Paul, 2026-09-07: *"Make the table system with lozenges just list the items
   as cells. The lozenges are getting in the way."*

   THE SHAPE IS `.nu-lztrack`'S AND IS NOT INVENTED HERE. A sideways track of
   `18ch` columns; each column a `nowrap` stack of full-width items, one item
   per line; the TRACK scrolling and the PAGE not (§11c). That is the
   arrangement the instrument picker already ships and the one Paul liked, and
   these rules are it with tags instead of `.nu-lz*` classes. The lozenge
   field's own rules are untouched and are ported onto these tags in a round of
   their own — one look, arrived at twice, is not two looks.

   WHAT IS DIFFERENT IS THE CELL, AND IT IS THE WHOLE POINT. `--r-pill` is a
   NOUN in tokens.css — *"the LOZENGE wears it, and nothing else on this page
   does"* — and an option in a list is not a noun you carry, it is a line you
   read. So a cell is SQUARE (`--r2`), it fills its column, and its word starts
   at the column's start edge. Everything the pill was carrying survives as a
   cell STATE, because none of it was ever the pill's doing.

   FOUR REGISTERS AND THEY ARE FOUR, NOT TWO:
     SELECTED   the standing answer: FILLED in `--lamp`, word in `--on-fill`.
                The system's one selection treatment, unchanged.
     CURRENT    where you ARE — a heading whose column holds the answer. It is
                the lamp as a HAIRLINE UNDER the word, never a fill, because
                `aria-current` is not `aria-pressed` and two facts that read
                the same are one fact lost.
     REFUSED    dashed, the refused register, and it says why in the TABLE's
                say line. Never `disabled`.
     QUIET      INERT, WHICH IS NOT REFUSED. No border, no dash, the dim
                legend ink and no press — greying an inert word with the
                refused grey is the bug this register exists to prevent. */
nu-table{ display: flex; flex-direction: column; align-items: stretch;
  inline-size: 100%; min-inline-size: 0; max-inline-size: 100%; }
/* THE TRACK IS THE ONLY THING HERE THAT SCROLLS SIDEWAYS, and it contains the
   gesture: `overscroll-behavior-inline: none` — NOT `contain`, which TABLE.md
   §15a forbids by name, because `contain` still lets the track rubber-band
   inside itself and a bounce translates the column heads with it. */
.nu-eltrack{ display: flex; align-items: flex-start; gap: var(--s3);
  min-inline-size: 0;
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-inline: none;
  padding-block-end: var(--s2);
  /* the edge is drawn always and coloured only when refused, so a dashed
     table does not move the words in it by a hairline */
  border: var(--bw) solid transparent; border-radius: var(--r2);
  scrollbar-width: thin; }
/* GROUPS DOWN THE SIDE: the track stops being a track and becomes a stack,
   and each row runs out across. Nothing scrolls; a row wraps. */
nu-table[flow="down"] > .nu-eltrack{ flex-direction: column;
  align-items: stretch; gap: var(--s2); overflow-x: hidden; }
.nu-eltrack:focus-visible, [data-demo="focus"] > .nu-eltrack{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: var(--press); }
nu-table[refused] > .nu-eltrack{ border-style: dashed;
  border-color: var(--legend-faint); }
nu-table[busy] > .nu-eltrack{
  box-shadow: inset 0 calc(-1 * var(--bw-hard)) 0 0 var(--armed); }

/* A COLUMN IS AS WIDE AS ITS WORDS AND NEVER WIDER THAN THE PHONE — 18ch and
   `calc(100vw - 4 * var(--s4))`, both of them `.nu-lzcluster.is-col`'s own
   numbers, arrived at by measuring "acoustic grand piano". THE STACK DOES NOT
   WRAP: one word per line is the whole ask. */
nu-colhead{ display: flex; flex-direction: column; flex-wrap: nowrap;
  gap: var(--s1); flex: 0 0 auto;
  inline-size: 18ch; max-inline-size: calc(100vw - 4 * var(--s4)); }
/* THE HEAD IS RENDERED LAST AND DRAWN FIRST. lit-html appends its part at the
   end of a light-DOM host, so the heading arrives after the cells the author
   wrote; `order: -1` puts it back on top. Layout, in the stylesheet, which is
   where §1a says it belongs — the alternative was the element moving its own
   children about, and a component that reorders the DOM to look right is a
   component that paints. */
.nu-elcolhead{ order: -1;
  display: flex; flex-wrap: wrap; align-items: center;
  column-gap: var(--s2); row-gap: var(--s1);
  inline-size: 100%; min-block-size: var(--tap);
  padding-block: var(--s1); padding-inline: var(--s2);
  font: var(--fw-label) var(--t1)/1.2 var(--sans);
  text-transform: uppercase; letter-spacing: .08em;
  /* THE CALLER'S HUE, AT THE ELEMENT'S OWN SPECIFICITY (2026-09-07).
     `--lz` is set by whoever owns the column — the lozenge field sets one of
     eight per family — and it FALLS BACK to the legend, so a caller that sets
     nothing is unchanged. It is read HERE, in the element's own one-class
     rule, rather than in a `.nu-lzfield nu-colhead[data-hue]:not([current])`
     rule three selectors deep, because docs/DESIGN-SYSTEM.md §1a says a skin
     is a stylesheet that targets the ELEMENTS: a look that can only be reached
     by out-specifying a caller's rule is a look a skin cannot reach with one
     line, which is the law failing quietly. `nukernel/skins/paper.css` now
     overrides this with `nu-colhead .nu-elcolhead`, one selector, no `!important`. */
  color: var(--lz, var(--legend-dim)); background: transparent;
  border: 0;
  border-block-end: var(--bw) solid var(--lz, var(--rule));
  text-align: start; cursor: pointer;
  -webkit-tap-highlight-color: transparent; }
.nu-elcolhead > .nu-elword{ flex: 1 1 auto; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-elcount{ flex: 0 0 auto;
  font: var(--fw-body) var(--t0)/1 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--legend-dim); }
/* THE HELD WORD IS A READOUT AND SITS ON ITS OWN LINE — the screen's ink under
   the panel's legend, which is §0's split inside one control. `order: 1` puts
   it after the fold mark, which is a generated last child. */
.nu-elheld{ order: 1; flex: 1 0 100%; min-inline-size: 0;
  font: var(--fw-body) var(--t1)/1.2 var(--sans);
  text-transform: none; letter-spacing: 0; color: var(--value);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-elcolhead[aria-expanded]::after{ content: "\25BE"; flex: 0 0 auto;
  font-family: var(--sym); color: var(--legend-dim); }
.nu-elcolhead[aria-expanded="false"]::after{ content: "\25B8"; }
/* THE FOLD IS ONE RULE. A column that is not open has no cells on the glass,
   and nothing in TypeScript knows that. */
nu-colhead:not([open]) > nu-cell{ display: none; }
.nu-elcolhead:hover, [data-demo="hover"] > .nu-elcolhead{
  color: var(--armed); border-block-end-color: var(--armed); }
.nu-elcolhead:focus-visible, [data-demo="focus"] > .nu-elcolhead{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: var(--press); }
.nu-elcolhead[aria-current="true"]{ color: var(--lamp);
  border-block-end-color: var(--lamp);
  border-block-end-width: var(--bw-hard); }
/* A CONTINUATION IS A READOUT AND NOT A SECOND DOOR TO ONE ROOM (`.nu-lzcont`'s
   own law). The same word, a dashed rule, no count, no fold, no press — and
   it is NOT drawn in the refused register, because nothing here is refused. */
.nu-elcolhead.is-cont{ border-block-end-style: dashed; cursor: default; }

/* ---- the same group with the axis turned ---------------------------- */
nu-rowhead{ display: flex; flex-direction: row; flex-wrap: wrap;
  align-items: stretch; gap: var(--s2); inline-size: 100%;
  min-inline-size: 0; }
.nu-elrowhead{ order: -1; flex: 0 0 auto;
  display: flex; flex-direction: column; justify-content: center;
  inline-size: 10ch; min-block-size: var(--tap);
  padding-block: var(--s1); padding-inline: var(--s2);
  font: var(--fw-label) var(--t1)/1.2 var(--sans);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--legend-dim); background: transparent;
  border: 0; border-inline-end: var(--bw) solid var(--rule);
  text-align: start; cursor: pointer;
  -webkit-tap-highlight-color: transparent; }
nu-rowhead > nu-cell{ flex: 0 0 auto; inline-size: 12ch; }
.nu-elrowhead:hover, [data-demo="hover"] > .nu-elrowhead{
  color: var(--armed); border-inline-end-color: var(--armed); }
.nu-elrowhead:focus-visible, [data-demo="focus"] > .nu-elrowhead{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: var(--press); }
.nu-elrowhead[aria-current="true"]{ color: var(--lamp);
  border-inline-end-color: var(--lamp);
  border-inline-end-width: var(--bw-hard); }

/* ---- one option, drawn as a line ------------------------------------
   THE ROW PITCH IS `--tap` AS A FLOOR AND NOT AS A HEIGHT: 44px is the least
   a thumb may be given, and a two-word name that wraps grows the cell rather
   than overlapping the one under it. */
nu-cell{ display: block; inline-size: 100%; min-inline-size: 12ch; }
.nu-elcell{ display: flex; align-items: center; gap: var(--s2);
  inline-size: 100%; min-block-size: var(--tap);
  padding-block: var(--s1); padding-inline: var(--s2);
  font: var(--btn-fw) var(--btn-fs)/1.3 var(--sans);
  color: var(--legend); background: var(--grey1);
  border: var(--bw) solid var(--rule);
  /* SQUARE, AND `--r-pill` IS NOT REACHABLE FROM HERE. */
  border-radius: var(--r2);
  text-align: start; cursor: pointer;
  -webkit-tap-highlight-color: transparent; }
.nu-elcell > .nu-elword{ flex: 1 1 auto; min-inline-size: 0;
  overflow-wrap: anywhere; }
.nu-elcell > .nu-elmark{ font-size: var(--t3); }
.nu-elorder{ flex: 0 0 auto;
  font: var(--fw-body) var(--t0)/1 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--legend-dim); }
.nu-elcell:hover, [data-demo="hover"] > .nu-elcell{
  border-color: var(--armed); background: var(--grey2); }
.nu-elcell:focus-visible, [data-demo="focus"] > .nu-elcell{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: var(--press); }
.nu-elcell[aria-pressed="true"]{ background: var(--lamp);
  border-color: var(--lamp); color: var(--on-fill); }
.nu-elcell[aria-pressed="true"] .nu-elorder{ color: var(--on-fill); }
.nu-elcell[aria-disabled="true"]{ border-style: dashed;
  border-color: var(--legend-faint); color: var(--legend-faint);
  background: transparent; }
.nu-elcell[aria-busy="true"]{
  box-shadow: inset 0 calc(-1 * var(--bw-hard)) 0 0 var(--armed);
  color: var(--legend-dim); }
/* INERT IS NOT REFUSED: no edge, no dash, the dim legend ink, no pointer. */
.nu-elcell.is-quiet{ background: transparent; border-color: transparent;
  color: var(--legend-dim); cursor: default; }

/* ---- <nu-plate> · <nu-menu-row> --------------------------------------
   Paul, 2026-09-07: *"Icons in hamburger should all have same width not float
   left it looks uneven."*

   THE FIX IS A FIXED ADVANCE AND THE PRECEDENT IS THE RECORD'S NAME PLATE
   (nu.css ~7443: `grid-template-columns: 1.4em minmax(0,1fr)` with the mark
   centred in column 1). `--sym` is a PROPORTIONAL stack — measured in this
   tree, `⊙` advances 26.8px and `⌗` 18.0px against a 19.2px tofu box — so a
   mark that carries its own width starts every word in a different place, and
   the eye uses the start edge of a list as its ruler. Give the mark a COLUMN,
   centre it there, and the words line up whatever glyph a row is given. The
   argument for it is one paragraph in `src/ui/panels.ts`; the rule is here,
   and it holds anywhere a mark precedes a word.

   AND A PLATE IS NEVER A FULL-WIDTH BAND OF GLASS. Its inline size is capped
   at the viewport less a TAP and a step of air, at every width, because a tap
   OUTSIDE a panel must have somewhere to land or the panel has one door. It
   stops short of the bottom band and scrolls inside itself rather than growing
   past the glass. Both are laws of the element and not of the page that hangs
   it. */
nu-plate{ display: none; }
nu-plate[open]{ display: flex; flex-direction: column; gap: var(--s1);
  inline-size: min(22rem, calc(100vw - var(--tap) - var(--s4)));
  max-inline-size: 100%;
  max-block-size: calc(100dvh - var(--top-h) - var(--bar-h) - var(--s4));
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--s2);
  background: var(--panel);
  border: var(--bw) solid var(--rule); border-radius: var(--r1);
  scrollbar-width: thin; }
/* THE ANCHOR IS SAID TWICE AND NEITHER IS A MARGIN. `align-self` places it
   when the plate stands in flow (the gallery, a sheet); the `inset` pair
   places it when the page has made it `position: fixed` (the app's own
   shell) and is inert otherwise. An `auto` margin would have done both and
   would have resolved to a used pixel value nobody chose, which is the one
   thing the spacing ramp cannot forgive. */
nu-plate[anchor="start"]{ align-self: start;
  inset-inline-start: 0; inset-inline-end: auto; }
nu-plate[anchor="end"]{ align-self: end;
  inset-inline-start: auto; inset-inline-end: 0; }

nu-menu-row{ display: block; inline-size: 100%; min-inline-size: 0; }
.nu-elmenurow{ display: grid;
  /* THE FIXED ADVANCE. Every mark gets 1.4em and is centred in it, so every
     word starts on one vertical line. `em` and not `rem`, so the column scales
     with the row's own type. */
  grid-template-columns: 1.4em minmax(0, 1fr) max-content;
  align-items: center; column-gap: var(--s2);
  inline-size: 100%; min-block-size: var(--tap);
  padding-block: var(--s1); padding-inline: var(--s2);
  font: var(--btn-fw) var(--btn-fs)/1.2 var(--sans);
  color: var(--legend); background: transparent;
  border: var(--bw) solid transparent; border-radius: var(--r1);
  text-align: start; cursor: pointer;
  -webkit-tap-highlight-color: transparent; }
.nu-elmenurow > .nu-elmark{ grid-column: 1; justify-self: center; }
.nu-elmenurow > .nu-elword{ grid-column: 2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-elmenurow > .nu-elcount{ grid-column: 3; }
.nu-elmenurow:hover, [data-demo="hover"] > .nu-elmenurow{
  background: var(--grey2); border-color: var(--armed); }
.nu-elmenurow:focus-visible, [data-demo="focus"] > .nu-elmenurow{
  outline: var(--bw-hard) solid var(--lamp);
  outline-offset: calc(-1 * var(--bw-hard)); }
.nu-elmenurow[aria-pressed="true"]{ background: var(--lamp);
  border-color: var(--lamp); color: var(--on-fill); }
/* THE COUNT FOLLOWS THE FILL. `--legend-dim` is the quiet register on a
   PANEL; on a lamp it is a number nobody can read, which is the whole of the
   "declared but never arriving" bug said in a colour. */
.nu-elmenurow[aria-pressed="true"] .nu-elcount{ color: var(--on-fill); }
/* WHERE YOU ARE IS A LAMP BAR DOWN THE START EDGE and never a fill: a fill is
   what SELECTED means, and a destination you are standing in was not pressed.
   An inset shadow rather than a border, so the words do not move a hairline
   when the bar arrives. */
.nu-elmenurow[aria-current="page"]{ color: var(--lamp);
  box-shadow: inset var(--bw-hard) 0 0 0 var(--lamp); }
.nu-elmenurow[aria-disabled="true"]{ border-style: dashed;
  border-color: var(--legend-faint); color: var(--legend-faint); }
.nu-elmenurow[aria-busy="true"]{
  box-shadow: inset 0 calc(-1 * var(--bw-hard)) 0 0 var(--armed);
  color: var(--legend-dim); }

/* ---- <nu-index> — 502 rows, and the field over them ------------------
   The atlas's own list, given a tag. A YEAR, A NAME, A PLACE, in three
   columns whose template is fixed so every row's columns agree; on a narrow
   phone it becomes two lines rather than three squeezed ones. The rows a
   query filters out are HIDDEN AND NOT DETACHED, so the list keeps its
   length, its scroll and its tree.

   THE EMPTY ANSWER IS A LIVE REGION AND IS NEVER REMOVED (`#atlasNone`'s own
   law): a `role="status"` taken out of the tree and put back does not
   announce. So it is empty at rest, an empty one has NO BOX, and it is never
   `display: none`. */
/* ---- IT IS ONE BOX WITH A FIELD ACROSS ITS TOP (2026-09-08, §23) ------
   Paul: *"The genre list should be 100% wide with no left or right borders.
   The 'Find a genre' should be integrated into it and the 'clear text' button
   … should be an (x) on the inside of the button. These should be in the
   design system."*

   THE ELEMENT IS THE BOX, so the box's rules are on the host: full width, a
   rule ABOVE and BELOW and none down the sides, no corner (a corner is a thing
   a box has, and a box whose sides are the glass has run out of box to have
   one on), and its own scrollport — which is what makes the head below a
   sticky head rather than a decoration. The app's `#atlasIndex` says the same
   four things in the same order, and the day either moves the other is one
   grep away.
   THE HEIGHT IS THE APP'S OWN `min(60vh, 560px)`, so twelve gallery rows and
   502 app rows are read through the same window. */
nu-index{ display: block; inline-size: 100%;
  min-inline-size: 0; max-inline-size: 100%;
  max-block-size: min(60vh, 560px);
  overflow-y: auto; overscroll-behavior: contain;
  border-block: var(--bw) solid var(--ink); border-inline: 0;
  background: var(--panel); }
/* THE FIELD IS THE HEAD, PINNED. A search that leaves the screen at row nine
   is a search you have to scroll back to; it is the only pinned thing inside
   this box, so there is no second band for it to argue with. `position:
   relative` comes free with `sticky` and is what the × below is placed
   against. */
.nu-elixfind{ position: sticky; inset-block-start: 0; z-index: 1;
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s1) var(--s4);
  background: var(--panel);
  border-block-end: var(--bw) solid var(--rule); }
/* ---- AND THE ROOM IT LEAVES IS ON THE RAMP (2026-09-08) ---------------
   `padding-inline-end: var(--tap)` STOOD HERE FOR AN HOUR and D5 refused it
   at all three widths: *"every one of 3358 rendered spacing values is on the
   five-step ramp (1 off: input.nu-elixq padding-inline-end 44.0)"*. The gate
   is right and the law is right — 44px is a THUMB, and a thumb is not air.
   THE MARK AND THE TARGET ARE DIFFERENT SIZES, which is the way out and is an
   idiom this page already has (`.nu-vs-wide .nu-vs-track::before` expands a
   4px trough to a 32px grab). The button keeps its full `--tap` box, so the
   hit area is a thumb and overhangs LEFT over the text; its glyph is pushed to
   the box's own end (`justify-content: flex-end` plus `--s2`), so what is
   DRAWN sits in the field's end padding. The field therefore has to clear the
   mark and not the target, and `--s5` — a block, the ramp's own top step — is
   what clears it. MEASURED on the rendered app at 390: the field's right edge
   is at 377.2 with 22.4px of end padding, so its text stops at 354.8; the
   mark's own box ends at 367, between the two. The glyph stands in the
   padding, the text never reaches it, and the target is 44 x 44. */
.nu-elixq{ flex: 1 1 auto; min-inline-size: 0; min-block-size: var(--tap);
  padding-block: var(--s1);
  padding-inline: var(--s2) var(--s5);
  font: var(--fw-body) var(--t2)/1.2 var(--sans);
  color: var(--value); background: var(--well);
  border: var(--bw) solid var(--rule); border-radius: var(--r1); }
.nu-elixq:focus-visible{ outline: var(--bw-hard) solid var(--lamp);
  outline-offset: calc(-1 * var(--bw-hard)); }
/* ---- THE CLEAR IS AN × INSIDE THE FIELD -------------------------------
   It was a separate plate BESIDE the field — its own border, its own ground,
   its own 44px of the row. It sits INSIDE the field's box now, absolutely
   placed at the inline end and drawn over the text, which is where every
   search control on every platform puts it and what "on the inside" means.
   NO PLATE OF ITS OWN: a border and a ground around a mark that stands inside
   another control's border draws a box inside a box. The TARGET is still the
   full `--tap` square — it is a hit area, not a picture — and what says it is
   pressable is the mark going from `--legend-dim` to `--armed` under a hand. */
.nu-elixclear{ position: absolute; inset-block-start: 50%;
  inset-inline-end: var(--s4); transform: translateY(-50%);
  inline-size: var(--tap); block-size: var(--tap);
  display: inline-flex; align-items: center; justify-content: flex-end;
  padding: 0 var(--s2); background: none;
  border: 0; border-radius: 0;
  color: var(--legend-dim); font: 1.25rem/1 var(--sym, var(--sans));
  cursor: pointer; -webkit-tap-highlight-color: transparent; }
.nu-elixclear:hover{ color: var(--armed); }
.nu-elixclear[hidden]{ display: none; }
.nu-elixlist{ list-style: none; margin: 0; padding: 0; display: block; }
.nu-elixli{ display: block; }
/* `[hidden]` IS SAID EXPLICITLY because the rule above sets `display` and
   would otherwise win: a filtered row that stayed on the glass is a filter
   that does nothing. */
.nu-elixli[hidden]{ display: none; }
.nu-elixrow{ display: grid;
  grid-template-columns: 4ch minmax(0, 1.6fr) minmax(0, 1fr);
  align-items: center; column-gap: var(--s2); row-gap: var(--s1);
  inline-size: 100%; min-block-size: var(--tap);
  padding-block: var(--s1); padding-inline: var(--s2);
  font: var(--fw-body) var(--t2)/1.3 var(--sans);
  color: var(--legend); background: transparent;
  border: var(--bw) solid transparent; border-radius: var(--r2);
  text-align: start; cursor: pointer;
  -webkit-tap-highlight-color: transparent; }
.nu-elixy{ grid-column: 1; font-family: var(--mono);
  font-variant-numeric: tabular-nums; color: var(--legend-dim); }
.nu-elixw{ grid-column: 2; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-elixp{ grid-column: 3; min-inline-size: 0; color: var(--legend-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nu-elixrow:hover, [data-demo="hover"] .nu-elixli:first-child .nu-elixrow{
  border-color: var(--armed); background: var(--grey2); }
.nu-elixrow:focus-visible,
[data-demo="focus"] .nu-elixli:first-child .nu-elixrow{
  outline: var(--bw-hard) solid var(--lamp);
  outline-offset: calc(-1 * var(--bw-hard)); }
/* EXACTLY ONE ROW IS `aria-current`, and it is a lamp bar and not a fill —
   the same treatment a menu row's destination gets, for the same reason. */
.nu-elixrow[aria-current="true"]{ color: var(--lamp);
  box-shadow: inset var(--bw-hard) 0 0 0 var(--lamp); }
.nu-elixrow[aria-current="true"] .nu-elixy{ color: var(--lamp); }
.nu-elixnone{ margin: 0; padding: var(--s2);
  font: var(--fw-body) var(--t2)/1.35 var(--sans); color: var(--flag); }
.nu-elixnone:empty{ display: block; block-size: 0;
  margin-block: 0; padding: 0; overflow: hidden; }
@media (max-width: 460px){
  /* TWO LINES RATHER THAN THREE SQUEEZED COLUMNS: the place goes under the
     name, in the name's own column, so the year stays the ruler down the
     start edge. */
  .nu-elixrow{ grid-template-columns: 4ch minmax(0, 1fr); }
  .nu-elixp{ grid-column: 2; }
}

/* ---- <nu-globe> — a sphere, and nothing scheduled --------------------
   THE PROJECTION IS ARITHMETIC AND LIVES IN `src/ui/atlas.ts`; EVERY FILL,
   STROKE AND OPACITY IS HERE. That division is what makes this element pass
   §1a on the same terms as every other one: the TypeScript writes `cx`, `cy`,
   `r` and `d`, which are the projection's output, and not one paint. A skin
   restyles the globe by writing these six selectors and nothing else. */
nu-globe{ display: inline-flex; }
.nu-elglobe{ display: block; inline-size: 12rem; block-size: 12rem; }
.nu-elglobe:focus-visible, [data-demo="focus"] > .nu-elglobe{
  outline: var(--bw-hard) solid var(--lamp); outline-offset: var(--press); }
.nu-elsea{ fill: var(--well); stroke: none; }
.nu-elgratline{ fill: none; stroke: var(--rule);
  stroke-width: calc(var(--bw) / 2); }
.nu-ellimb{ fill: none; stroke: var(--rule-strong); stroke-width: var(--bw); }
.nu-elyear{ fill: var(--value); font-family: var(--mono);
  font-size: var(--t1); font-variant-numeric: tabular-nums;
  text-anchor: middle; }
/* A YEAR THAT HOLDS ONE MARK OR NONE: the stamp is the whole picture, so it
   is the size of a block's own name rather than of an annotation. */
nu-globe[empty] .nu-elyear{ font-size: var(--t4); }
/* A MARK THE SWEPT YEAR DOES NOT HOLD LEAVES THE SPHERE. */
.nu-elplace[data-when="0"]{ display: none; }
.nu-elplace{ cursor: pointer; }
.nu-elpin{ fill: var(--lamp); stroke: none; }
.nu-elring{ fill: none; stroke: var(--lamp); stroke-width: var(--bw);
  opacity: 0; }
/* THE RECORD THAT IS PLAYING WEARS THE RING, at full opacity, and it is the
   one thing on the sphere that does. */
.nu-elplace[aria-current="true"] .nu-elring{ opacity: 1; }
.nu-elplace:hover .nu-elpin{ fill: var(--armed); }
.nu-elplace:focus-visible{ outline: var(--bw-hard) solid var(--lamp); }

/* ======================================================================
   THE GALLERY (nukernel/design.html, docs/DESIGN-SYSTEM.md §3)

   The page that IS the system. It is drawn by `src/ui/gallery.ts` out of the
   element declarations and out of the resolved values of tokens.css, so this
   block styles a SHAPE — a section, a swatch, a matrix, a state cell — and
   never a component, of which it knows none.

   IT OBEYS ITS OWN LAWS, which is the whole reason it is worth having: every
   length below is a token, every gap is a step of the ramp, and the page is
   one column at 320 and 390 with nothing scrolling sideways but the matrix,
   which is a table and is allowed to (§11c: the track is the only thing that
   scrolls sideways, and it contains itself). test/design-system.js measures
   all three of those on the rendered page.
   ====================================================================== */
.dg-body{ margin: 0; background: var(--deck); color: var(--legend);
  font: var(--fw-body) var(--t3)/1.45 var(--sans);
  padding: var(--s4);
  padding-inline: max(var(--s4), env(safe-area-inset-left, 0px));
  overscroll-behavior: none; }
.dg{ display: block; max-inline-size: 76rem; margin-inline: auto; }
.dg-bar{ display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); flex-wrap: wrap;
  padding-block-end: var(--s3);
  border-block-end: var(--bw) solid var(--rule); }
.dg-title{ margin: 0;
  font: var(--fw-display) var(--t5)/1.1 var(--sans);
  letter-spacing: .02em; text-transform: uppercase; color: var(--legend); }
.dg-sec{ padding-block: var(--s5); }
.dg-sec + .dg-sec{ border-block-start: var(--bw) solid var(--rule); }
.dg-h{ margin: 0;
  font: var(--fw-block) var(--t4)/1.2 var(--sans);
  text-transform: uppercase; letter-spacing: .08em; color: var(--legend); }
.dg-h3{ margin: 0; padding-block-start: var(--s4);
  font: var(--fw-label) var(--t2)/1.2 var(--sans);
  text-transform: uppercase; letter-spacing: .08em; color: var(--legend-dim); }
.dg-blurb{ margin: 0; padding-block-start: var(--s2);
  max-inline-size: 46rem;
  font: var(--fw-body) var(--t2)/1.45 var(--sans); color: var(--legend-dim); }

/* ---- swatches --------------------------------------------------------- */
.dg-swatches{ display: flex; flex-wrap: wrap; gap: var(--s3);
  padding-block-start: var(--s3); }
.dg-swatch{ display: flex; flex-direction: column; gap: var(--s1);
  inline-size: 8.5rem; }
.dg-chip{ block-size: var(--tap); border-radius: var(--r2);
  border: var(--bw) solid var(--rule); }
.dg-radchip{ background: var(--grey2); }
.dg-tokname{ font: var(--fw-label) var(--t1)/1.2 var(--mono);
  color: var(--legend); }
.dg-tokval{ font: var(--fw-body) var(--t0)/1.2 var(--mono);
  color: var(--legend-dim); font-variant-numeric: tabular-nums; }

/* ---- the contrast matrix --------------------------------------------- */
.dg-matrix{ inline-size: 100%; border-collapse: collapse;
  margin-block-start: var(--s3);
  font: var(--fw-body) var(--t2)/1.3 var(--mono); }
.dg-matrix th, .dg-matrix td{ padding: var(--s2);
  border-block-end: var(--bw) solid var(--rule); text-align: start; }
.dg-matrix thead th{ color: var(--legend-dim);
  font-weight: var(--fw-label); }
.dg-fg{ display: table-cell; color: var(--legend); font-weight: var(--fw-body); }
.dg-dot{ display: inline-block; inline-size: 10px; block-size: 10px;
  border-radius: var(--r-pill); margin-inline-end: var(--s2);
  border: var(--bw) solid var(--rule); }
.dg-num{ text-align: end; font-variant-numeric: tabular-nums;
  color: var(--value); }
/* UNDER THE FLOOR SAYS SO IN THE ONE COLOUR THAT MEANS "THIS IS WRONG". */
.dg-num.is-under{ color: var(--clip); font-weight: var(--num-fw); }
.dg-num.is-under::after{ content: " \2716"; font-family: var(--sym); }
.dg-band td{ color: var(--legend-dim); background: var(--panel);
  text-transform: uppercase; letter-spacing: .08em;
  font-size: var(--t1); }

/* ---- the scales ------------------------------------------------------- */
.dg-stack{ display: flex; flex-direction: column; gap: var(--s2);
  padding-block-start: var(--s3); }
.dg-typerow{ display: flex; align-items: baseline; gap: var(--s3);
  flex-wrap: wrap; }
.dg-typespec{ color: var(--legend); min-inline-size: 14rem; }
.dg-rampbar{ display: block; block-size: 14px; background: var(--lamp);
  border-radius: var(--r2); }

/* ---- the element cards ------------------------------------------------ */
.dg-el{ padding-block: var(--s4); }
.dg-el + .dg-el{ border-block-start: var(--bw) solid var(--rule); }
.dg-elhead{ display: flex; align-items: baseline; gap: var(--s3);
  flex-wrap: wrap; }
.dg-tag{ font: var(--fw-body) var(--t2)/1.2 var(--mono); color: var(--value); }
.dg-what{ margin: 0; padding-block-start: var(--s1);
  font: var(--fw-body) var(--t3)/1.4 var(--sans); color: var(--legend); }
.dg-from{ margin: 0; padding-block-start: var(--s1);
  font: var(--fw-body) var(--t1)/1.4 var(--sans); color: var(--legend-dim); }
/* THE STATES, SIDE BY SIDE. `flex-wrap` and not a track: a state grid is a
   handful of small things and wrapping is what a phone wants; the one thing
   on this page that may scroll sideways is the matrix. */
.dg-states{ display: flex; flex-wrap: wrap; gap: var(--s4);
  padding-block-start: var(--s3); }
/* A STATE CELL MAY NOT BE WIDER THAN THE PAGE, and until <nu-table> arrived
   nothing on this page was ever wide enough to test it: a flex item's base
   size is its MAX-CONTENT, so a track holding three 18ch columns sized the
   cell to 538px at 390 and pushed the document 161px sideways — D7's own
   failure, found by the gate on the first run. `min-inline-size: 0` lets the
   item shrink past its content and `max-inline-size: 100%` stops it at the
   section's width; the track then does what a track is for and scrolls
   INSIDE itself, which is §11c working rather than being bypassed. */
.dg-state{ display: flex; flex-direction: column; gap: var(--s2);
  align-items: flex-start;
  min-inline-size: 0; max-inline-size: 100%; }
.dg-states{ min-inline-size: 0; }
.dg-stagebox{ min-inline-size: 0; max-inline-size: 100%; }
.dg-statename{ font: var(--fw-label) var(--t0)/1.2 var(--mono);
  text-transform: uppercase; letter-spacing: .08em; color: var(--legend-dim); }
.dg-stagebox{ display: flex; align-items: center; min-block-size: var(--tap); }
.dg-attrs{ inline-size: 100%; border-collapse: collapse;
  margin-block-start: var(--s4);
  font: var(--fw-body) var(--t2)/1.35 var(--sans); }
.dg-attrs th, .dg-attrs td{ padding: var(--s2); text-align: start;
  vertical-align: top; border-block-end: var(--bw) solid var(--rule); }
.dg-attr{ font-family: var(--mono); font-weight: var(--fw-label);
  color: var(--value); white-space: nowrap; }
.dg-attrtype{ font-family: var(--mono); color: var(--legend-dim);
  font-size: var(--t1); }
.dg-attrnote{ color: var(--legend); }
.dg-laws{ display: grid; grid-template-columns: max-content 1fr;
  gap: var(--s2) var(--s3); margin: 0; padding-block-start: var(--s3);
  font: var(--fw-body) var(--t2)/1.35 var(--sans); }
.dg-laws dt{ color: var(--legend-dim); text-transform: uppercase;
  letter-spacing: .08em; font-size: var(--t1); font-weight: var(--fw-label); }
.dg-laws dd{ margin: 0; color: var(--legend); }

/* AT 320 AND 390 THE TABLES FIT RATHER THAN SCROLL, and this comment used to
   claim otherwise. It named an `overscroll-behavior-inline: contain` that the
   rule below never declared — the tree's characteristic bug ([[declared but
   never arriving]]), found by the 2026-09-07 law audit in the design system's
   own stylesheet. THE TRUTH, MEASURED: both tables are `inline-size: 100%`
   with `table-layout` left to the browser and a `word-break` on the note
   column, so at 320 they wrap instead of overflowing and `test/design-system.js`
   D7 finds 0px of sideways scroll at the page level. Nothing here scrolls
   sideways, so nothing here needs the law §15a wrote for the things that do —
   and if a future column makes one of them overflow, it takes
   `overscroll-behavior-inline: none`, which is the value that law names. */
.dg-matrix, .dg-attrs{ display: table; }
.dg-sec > .dg-matrix, .dg-el > .dg-attrs{ inline-size: 100%; }
@media (max-width: 460px){
  .dg-swatch{ inline-size: 6.5rem; }
  .dg-typespec{ min-inline-size: 100%; }
  .dg-laws{ grid-template-columns: 1fr; }
}
/* A SELECTED READOUT IS LIT, LIKE EVERYTHING ELSE THAT IS SELECTED — and it is
   an `outline`, not a border or a shadow, so the number does not move by a
   pixel when the ring lands on it. */
nu-value[selected] .nu-elval{ outline: var(--bw-hard) solid var(--lamp);
  outline-offset: var(--s1); color: var(--value); }
