/*
 *  style.css -- FORTERIA prototype. THE LOOK LAW, and nothing else.
 *
 *  rico, verbatim (2026-09-21 10:04): "we keep colors whitish for now. background black. minialist
 *  design language. roundedrects with small roundings, no outlines, we let the typografical raster
 *  peak the boundaries. fine alignment strategies, nothing stays undefined. positions are always
 *  aligned left/right/center."
 *
 *  Read as rules this file can be checked against:
 *    * ONE SEED, ONE LADDER (lane P22). One background (black) and ONE SEED COLOUR, from which the
 *      milky ink and the five identity hues -- layers A, B, C, D and the entity -- all fall out by
 *      one formula. Every TONE is that ink (or one of those hues) at a NUMBERED TIER from the
 *      ladder below; the tier owns the alpha, the hue owns the identity. The emblem's diamond is
 *      still the one colour that belongs to the company and not to the palette.
 *    * ONE UNIT. --u: 4px. Every size, gap, inset and position below is a whole multiple of it,
 *      and there is exactly ONE radius (1u), ONE label gap (1u), ONE control gap (3u), ONE group
 *      gap (6u) and ONE outer margin (4u). Each is defined once, here, and used by name.
 *    * NOTHING IS DRAWN THAT CAN BE READ. No border, no outline, no divider, no box around a group,
 *      no shadow, no glow. A group is known by the column it stands on and the gap around it.
 *      (Lines are allowed INSIDE the space -- wave rings, probe lines, the level arc -- and
 *      those live in the canvas, not here.)
 *    * EVERY ROW SITS ON ONE CENTRE LINE. The header is one band with everything vertically
 *      centred, never top-aligned; so is every title row.
 *
 *  The DEV strip at the bottom is the ONE place allowed to look like a debug tool.
 */

:root {
    --u: 4px;

    /* ── ONE SEED (lane P22, H-6 / H-7) ───────────────────────────────────────────────────────
       rico 2026-09-26: "I see milky colors, friendly, but still technical." / "I leave it to you
       what the colors are, we need color schemes in a todo settings dialog anyway."
       THE SEED IS MILK HONEY #E4C190 -- rico's OWN pastel-milky seed out of
       `docs/state/tombola-system-color-2026-09-14.md`, twelve days earlier, same word, same man.
       EVERY COLOUR BELOW FALLS OUT OF IT BY ONE FORMULA, written out in forteria-app.js:
           ink        = oklch(min(0.97, L0 + 0.13), C0 x 0.09, h0)
           identity k = oklch(L0, 0.13, h0 + 72k)      k = 0..4 -> A, B, C, D, ENTITY
       ...so A COLOUR SCHEME IS A SINGLE VALUE, and the settings dialog he asked for in the same
       breath becomes a list of seeds rather than a page of forty swatches. THE APP DERIVES THESE
       AT RUNTIME from `SEED`; the lines here are the same numbers in this file's own home, and
       this comment is the seam -- change the seed and both move together. */
    --bg: #000;
    --seed: #E4C190;
    --ink: 244, 241, 237;

    /* (THE FOUR LAYERS AND THE ENTITY, --lay-a .. --lay-e, stood here until lane LAYERS-RETIRE: a hue is still an
       identity and never a brightness (H-9), but the identity is now a THING's own `hue`, computed by the page's
       `thingInk` from the one seed -- so no fixed swatch is left for this sheet to hold.) */

    /* ── THE TRANSPARENCY LADDER (lane P22, H-1 / H-2) ────────────────────────────────────────
       rico 2026-09-26: "we need transparency tiers." / "nearly everything has to find its grey
       tone, so the image gets depth and better understandable complexity." / "like (just for
       understanding): selected: white (or transtier * 2). unselected device: transtier 6. path
       unselected: transtier 3. pulses/light/beams: transtier 5. background grid (quantize)
       transtier 1."
            tt(n) = 2^((n - 8) / 2)        tier 8 = white; every TWO rungs halve
       DOUBLING A TIER IS +2 RUNGS, which is what makes his "transtier * 2" a real step instead of a
       phrase -- and his two anchors then say one thing twice: tt(6) = 0.5 IS the unselected device,
       and 0.5 x 2 = 1.0 = tt(8) IS "selected: white".
       Four decimals here against the app's exact `Math.pow`: 1e-4 is a fortieth of ONE step of an
       8-bit alpha, so no pixel can tell the two homes apart.
       LANE P43 (K-9) LIFTS THE WHOLE LADDER HALF A RUNG, in both homes: rico 2026-09-29, "I think all
       the trandparency tiers should be a bit brighter." tt(n) = min(1, 2^((n + 0.5 - 8) / 2)), every
       tier x 1.189 and tier 8 still white (ASSUMED READING 4 of lane P43, `TT_LIFT`, gone since P49).
       LANE P49 (L-1) RAISES THE LOW END INSTEAD, in both homes: rico 2026-09-29, "and its all very
       dark." tt(n) = min(1, 2^((n - 8) / 3)) -- a cube-root-of-2 ratio per rung, +3 rungs double,
       tier 8 still white (ASSUMED READING 2, forteria-app.js `TT_PER_DOUBLE`), and it replaces
       P43's lift. The P43 values stand beside each line; tier 7 is the one that dims. */
    --tt0: 0.1575;   /* P43: 0.0743                 */
    --tt1: 0.1984;   /* the quantize grid    0.1051 */
    --tt2: 0.2500;   /* P43: 0.1487                 */
    --tt3: 0.3150;   /* a path, unselected   0.2102 */
    --tt4: 0.3969;   /* P43: 0.2973                 */
    --tt5: 0.5000;   /* pulses / light       0.4204 */
    --tt6: 0.6300;   /* a device, unselected 0.5946 */
    --tt7: 0.7937;   /* P43: 0.8409                 */
    --tt8: 1.0000;   /* selected = white, capped at 1 */

    /* THE ALPHA TABLE -- the only place a tone is decided, and since lane P22 every entry of it is
       A SEAT ON THE LADDER ABOVE rather than a number of its own. Each took the rung NEAREST the
       value it held, so the panel keeps the hierarchy lane P5a tuned into it while the SPACE --
       which is where "its hard to see anything" was aimed -- comes up a rung. */
    --a-text: var(--tt8);        /* was 0.92 */
    --a-dim: var(--tt6);         /* was 0.55 */

    /* ── THE GROUNDS (lane P5a, Y-7) ──────────────────────────────────────────────────────────
       rico 2026-09-25, verbatim: "the parameter background (not the headers) in the inspector must
       be a bit brighter." A PARAMETER ROW HAS EXACTLY TWO GROUNDS and a caption has none, so his
       parenthesis is satisfied by construction rather than by a second rule: the plate a choice or
       a toggle stands on (--a-plate), and the knob's own track (--a-track). Every
       caption in the panel is INK (--a-dim, `.section > .cap` and `.cols > .cap`) and not one of
       them is touched here, so the hierarchy lane P9b fixed -- a caption may never shout over the
       section it names -- reads exactly as it did.
       EACH GROUND MOVES UP ONE SEAT IN THE TABLE IT ALREADY HAD, so no new tone is invented:
           --a-plate     0.10 -> 0.16   (what --a-plate-hi was)
           --a-plate-hi  0.16 -> 0.22   (what --a-off was)
           --a-track     0.08 -> 0.14
       ...and --a-off rises with them, because it is the one INK that sits ON a ground: a disabled
       plate used to read 0.22 over 0.10 and would now read 0.22 over 0.16, which is a control
       nobody can see. At 0.30 over 0.16 it keeps the step it had, and greyed is still far below
       the 0.55 a live label reads at.
       ONE TABLE, SO IT CARRIES: the header band's plates and the tape strip's clips stand on
       --a-plate too and rise with it. That is the table doing its job, not a side effect.
       LANE P22 SEATED ALL FOUR ON THE LADDER, and the order P5a fought for is untouched:
           --a-plate     0.16 -> tt3 0.1768     --a-track   0.14 -> tt2 0.1250
           --a-plate-hi  0.22 -> tt4 0.2500     --a-off     0.30 -> tt5 0.3536
       `off` still sits a clear two rungs under a live label (tt6) and two rungs over the plate it
       stands on, so a greyed control reads exactly as greyed as it did. */
    --a-plate: var(--tt3);
    --a-plate-hi: var(--tt4);
    /* THE EDITOR PLATES, ONE RUNG UP (lane P60, K-3). rico 2026-09-30: "we are waiting for the brighter
       editor-plates in the inspector. so we can distinguish it from the headers better." -- and on
       09-25, the first time: "the parameter background (not the headers) in the inspector must be a
       bit brighter." Lane P5a answered that by moving --a-plate ITSELF, which raised the header band's
       plates and the panel's title row with the editors, so nothing read apart and he is still waiting.
       So THE RISE IS THE EDITORS' ALONE: every plate inside an inspector SECTION (a value, choice or
       toggle plate, and its hover) takes these two seats (`#inspector .section` below), one rung over
       --a-plate / --a-plate-hi; the header band, the panel's title row, the popup and the part field
       keep the two above, and every caption keeps its ink.
       ASSUMED READING 1 (lane P60), one line to flip: delete the `#inspector .section` rule and every
       plate reads --a-plate again -- or point --a-plate at --a-plate-edit to raise them all. --a-off
       stays tt5, so a greyed plate's word now sits ONE rung over its plate (tt5 on tt4), not two: the
       one rung the ladder has between a greyed word and a live label (tt6). */
    --a-plate-edit: var(--tt4);
    --a-plate-edit-hi: var(--tt5);
    /* the hairline bracket that holds an open rack to its parameter (lane P60, K-7): the editor plates'
       rung, so the line reads as part of the editor it encloses and never louder than a caption */
    --a-bracket: var(--tt4);
    --a-track: var(--tt2);
    --a-fill: var(--tt7);        /* was 0.80 */
    --a-lit: var(--tt8);
    --a-raster: var(--tt1);      /* was 0.06 -- his "background grid (quantize) transtier 1" */
    /* greyed = disabled AT ITS DEFAULT LOOK, never hidden: a control that cannot be turned right now
       still holds its place on the raster, it just stops claiming the eye. One more NAMED entry in
       the one table -- which is what the table is for. */
    --a-off: var(--tt5);
    /* a readout a hand is holding (lane P59, Q-1: the strip's follow is off) -- one rung under --a-text.
       The canvas twin (forteria-app.js `A`) has no such name: the canvas draws no held readout. */
    --a-held: var(--tt7);

    /* ── THE GRAPHITE LAYERS (lane IN-1, P88 phase 2) ──────────────────────────────────────────
       rico 2026-10-02: "but you still havent understood the problem. its just all black in black."
       RULED (question dialog, his pick): "Graphite layers (Recommended)" -- tone.md (a), the target
       `Documents/SONDE-design-2026-10-02/mockup-B1-graphite.html`. ONE GROUND PLUS A SURFACE LADDER:
           surface(k) = ink x tt(3k - 2)        k = 0 panel, 1 card, 2 control, 3 active
       each step DOUBLES the alpha (+3 rungs = his "transtier x 2"). The space keeps --bg. Each surface
       is the INK MIXED INTO THE GROUND at its rung -- "by transparency from the background" -- and
       OPAQUE, so a card standing on the panel is exactly ink x tt1 and never the panel showing through
       it. The percentages are the ladder's own rungs x 100 (tt(-2) = 2^(-10/3) = 0.0992, one rung
       family below --tt0). LIT = THE KNOB'S ARC (tt7), never white: gui_design_rules 3, "lit = the
       slider's brightness", which today's tt8 lit plate broke (tone.md, cause 5). TEXT keeps today's
       rungs and is measured against the card it sits on: value tt8, label tt6, disabled tt5; a
       hairline tt3 stands only between a card's sub-sections. The canvas twin is forteria-app.js `AC`
       (the same rungs, as alphas that land on them over the card). */
    --g-panel:  color-mix(in srgb, rgb(var(--ink)) 9.92%, var(--bg));    /* k 0, tt(-2): the panel     */
    --g-card:   color-mix(in srgb, rgb(var(--ink)) 19.84%, var(--bg));   /* k 1, tt1: a section card   */
    --g-hair:   color-mix(in srgb, rgb(var(--ink)) 31.50%, var(--bg));   /* tt3: hairline + knob track */
    --g-ctl:    color-mix(in srgb, rgb(var(--ink)) 39.69%, var(--bg));   /* k 2, tt4: a control        */
    --g-ctl-hi: color-mix(in srgb, rgb(var(--ink)) 50.00%, var(--bg));   /* tt5: a control under hand  */
    --g-act:    color-mix(in srgb, rgb(var(--ink)) 79.37%, var(--bg));   /* k 3, tt7: active = the arc  */
    --g-text:   rgb(var(--ink));                                          /* tt8: a value               */
    --g-lab:    color-mix(in srgb, rgb(var(--ink)) 63.00%, var(--bg));   /* tt6: a label               */
    --g-off:    color-mix(in srgb, rgb(var(--ink)) 50.00%, var(--bg));   /* tt5: disabled              */

    --text: rgba(var(--ink), var(--a-text));
    --dim: rgba(var(--ink), var(--a-dim));
    --plate: rgba(var(--ink), var(--a-plate));
    --plate-hi: rgba(var(--ink), var(--a-plate-hi));
    --lit: rgba(var(--ink), var(--a-lit));
    --off: rgba(var(--ink), var(--a-off));
    /* THE SCRIM behind a full-screen overlay (lane P fix round 1, F7): the GROUND itself at 97 %, the
       colour law's "by transparency from the background" -- never a literal rgba. MEASURED in the
       `presets` drive's shot: at 94 % the inspector's white text still reached 15 / 255 beside the
       card and read; at 97 % it is ~7 -- covered, with the space only just there behind it. */
    --scrim: color-mix(in srgb, var(--bg) 97%, transparent);

    /* the one brand colour on the screen: the emblem's diamond (Factory 2's, ruling Q19) */
    --brand-red: #ED1C24;

    /* the raster */
    --r: var(--u);
    --gap-label: var(--u);
    --gap-ctl: calc(var(--u) * 3);
    --gap-group: calc(var(--u) * 6);
    --margin: calc(var(--u) * 4);

    /* the three type sizes (SPEC ruling Q15: one ladder, Segoe UI) */
    --fs-title: 16px;
    --fs-label: 12px;
    --fs-word: 20px;

    --h-row: calc(var(--u) * 8);        /* one control row */
    --h-plate: calc(var(--u) * 6);      /* THE plate size, buttons and menus alike */
    /* THE PANEL'S WIDTH IS A SUM AND EVERY TERM OF IT IS HERE -- nothing about the panel's width is
       decided anywhere else, which is why narrowing it is an edit to THIS TABLE and never a second
       rule somewhere in the sheet.
         TIME | PLACE | TONE   3 x --w-col + 2 group gaps (12u)
         + --w-gutter, the strip at the right the columns may not enter
       rico 2026-09-22: "reduce the distance between the knobs and their values." / "this should make
       the whole inspector narrower." / "so we have more real estate for the world." -> a column is as
       wide as knob (8u) + gap (3u) + the widest label ("Window", 44 px) + gap (1u) + the widest value
       ("10000 ms", 48 px) = 140 px, and no real row pairs those two, so the column is as wide as the
       WIDEST REAL PAIR, rounded up to the raster; the value keeps its right edge on the column's edge.

       ── LANE P19, G-4: 34u -> 33u, AND THE PAIR THAT SETS IT IS NAMED ────────────────────────────
       rico 2026-09-26: "the inspector is still touching the scrollbar. the parameter plates can be
       narrower." Both halves are this table's, and both are MEASURED by `drive.js 1 <port> fold`'s own
       census rather than chosen: the tightest real pair in the whole panel is MIDI's `Note hi` beside
       "G9 (127)" -- label 39.3 + gap 4 + value 44.6 = 87.9 px -- so the label+value box has to be 22u
       = 88 px, and the column is knob 8u + gap 3u + 22u = 33u = 132 px. That is the MOST the census
       allows: 32u would cut the box to 84 and "G9 (127)" would break at its own space. Three columns
       give back 12 px.
       ── AND THE GUTTER IS 3u, NOT 1u ──────────────────────────────────────────────────────────────
       It was 1u, which is exactly the 1u scrollbar `#inspector::-webkit-scrollbar` asks for -- so the
       third column's right edge landed ON the scrollbar's left edge with ZERO px between them, which
       is the touching he saw (measured: `insp` read the .cols grid at 456 px inside a 456 px client
       box). 3u is the 1u scrollbar plus 2u of air. The gutter is RESERVED, not discovered: a scrollbar
       that appears later must not push a column off its raster line, and `fitInspector` adds whatever
       THIS engine really reserves on top of the air, so the air survives a 16 px scrollbar too.
       Net: the panel goes 115u -> 114u (460 -> 456 px, 4 px to the world) and the columns stop at
       8 px from the bar instead of 0.
       ── LANE ROWLAW (P97 D8-1): FOUR FIXED TRACKS, AND THE COLUMN IS THEIR SUM ───────────────────
       rico 2026-10-03: "all parameters, do it like this: name, mod>, knob, value." Every row of every
       card stands on the same four tracks, so every name, opener, knob and value of a column is on
       ONE x: NAME 14u (the widest knob-row name, MIDI's "Transpose", 52.9 px) . MOD 3u (the opener's
       cell, empty where a parameter takes no card) . KNOB 8u . 1u . VALUE 13u (the widest value a knob
       prints walked floor -> ceiling, the emitter's "13 c/beat", 50.0 px) = 39u = 156 px a column (it
       was 33u). RULED 2026-10-03, rico's pick "Widen to 528 px (Recommended)": no word is cut, the
       panel goes 456 -> 528 px -- his 09-22 "more real estate for the world" stays answered by the
       title row's `>` hide plate, which still gives the world the whole screen.
       ── LANE ROW-LOOK (V1): THREE TRACKS, THE MOD TRACK GOES ────────────────────────────────────────
       rico 2026-10-04: "the \"mod\" triangle being the worst thing I have seen EVER, and its moving into the knob as
       if its not wanting to exist?" -- his pick "V1: mod ring on the knob (Recommended)". No opener glyph: a modulated
       knob wears its mod range as an arc on its OWN ring pad (F2's r + 3, OKIView.cpp:452-453), so the KNOB track takes
       the MOD track's 3u less 2u -- 9u, the same 26 px knob with the pad around it -- and the VALUE track the last 2u
       (15u: "Immediate", 56.6 px, fits now). NAME 14u . KNOB 9u . 1u . VALUE 15u = 39u: the column keeps its width.
       --x-ctl is where a CONTROL's ink starts in the knob track: the ring's left ink (18 - 13 - 1 = 1u), so a knob, a
       bar of plates and a menu chip of one column stand on ONE x (the plates' and the menus' left edge). */
    --w-lab: calc(var(--u) * 14);       /* the NAME track */
    --w-knob: calc(var(--u) * 9);       /* the KNOB track: the 26 px knob + its mod-ring pad */
    --w-val: calc(var(--u) * 15);       /* the VALUE track (after a 1u gap) */
    --x-ctl: var(--u);                  /* a control's ink x inside the knob track: the ring's left ink */
    --w-col: calc(var(--w-lab) + var(--w-knob) + var(--u) + var(--w-val));   /* one inspector column, 39u */
    --w-air: calc(var(--u) * 2);        /* THE AIR between the last column and the scrollbar */
    --w-gutter: calc(var(--u) + var(--w-air));   /* 1u scrollbar + that air */
    /* ── LANE IN-1: THE CARD PAYS FOR ITSELF (tone.md ASSUMED READING 2, the mockup's numbers) ─────
       A section is a CARD now (3u inset on every side), so the panel's width gains 2 x 3u -- and gives
       them back: the column GAP goes 6u -> 2u (the opener's 3u cell still leaves ~20 px between a value
       and the next knob) and the panel gets a 2u LEFT margin, the air its scrollbar side already has.
       3 x 33u + 2 x 2u + 2 x 3u + 2u + 3u = 114u = 456 px, the width it had: the world loses nothing.
       Flip: --gap-col back to --gap-group and the card inset to 0 (style.css `.section`).
       LANE ROWLAW: 3 x 39u + 2 x 2u + 2 x 3u + 2u + 3u = 132u = 528 px (`fitInspector` reads the same
       tracks back, unchanged). */
    --gap-col: calc(var(--u) * 2);      /* the columns' gap inside a card */
    --card-pad: calc(var(--u) * 3);     /* a card's inset */
    --card-r: calc(var(--u) * 1.5);     /* a card's radius: the mockup's 6 px (a plate keeps 1u) */
    --w-margin-l: calc(var(--u) * 2);   /* the panel's left margin */
    /* VESTIGIAL (lane P3c-2 took the receiver cards off the panel; `.cards` is built by nothing).
       It is left at its own value rather than re-derived from --w-col, because a width derived for a
       grid nobody builds would be arithmetic pretending to be a law. */
    --w-card: calc(var(--u) * 24);      /* one receiver card */
    --w-inspector: calc(var(--w-col) * 3 + var(--gap-col) * 2 + var(--card-pad) * 2 + var(--w-margin-l) + var(--w-gutter));
}

* { box-sizing: border-box; margin: 0; padding: 0; border: 0; outline: 0; }

html, body {
    height: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: "Segoe UI", sans-serif;
    font-size: var(--fs-label);
    -webkit-font-smoothing: antialiased;
    overflow: hidden;
    user-select: none;
}

body { display: flex; flex-direction: column; }

/* ── THE HEADER: ONE BAND, ONE CENTRE LINE ──────────────────────────────────────────────────── */
#header {
    height: calc(var(--u) * 16);
    flex: 0 0 auto;
    padding: 0 var(--margin);
    display: flex;
    align-items: center;                 /* the centre line, for everything in the band */
    gap: var(--gap-group);
}
#header .grp { display: flex; align-items: center; gap: var(--gap-ctl); }
#header .grp.tight { gap: var(--gap-label); }
/* FOLLOW OFF IS VISIBLE (lane P59, Q-1). While the song plays in SONG and a hand holds the strip's view
   (lane P58's follow is off), the position readout wears the house's state-by-ink mark -- the plan's
   ASSUMED READING 1: it drops ONE RUNG (tier 8 -> 7) and a thin underline in its own ink appears. A PLAY,
   a touch on the readout or a jump hands the strip back to the song and the mark goes with it. No new
   colour and no box; a text decoration takes no layout, so the band's centre line does not move. */
#posBars.held {
    color: rgba(var(--ink), var(--a-held));
    text-decoration-line: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: calc(var(--u) / 2);
}

/* LANE KEY-SHOW (rico 2026-10-09): ROOT | SCALE ARE MENUS -- the house's 'value ▾' chip (`.plate.menu`), the caret drawn
   here so the plate's text stays the bare value. WHILE A SCALE MASTER PLAYS they SHOW the scale it commands and are no
   control (`.driven`, forteria-app.js drawHeader): no plate face, no hover, no caret, the arrow cursor -- a readout in the
   value's own ink, marked by the house's state-by-ink underline (#posBars.held's) in the dim ink.
   ASSUMED READING KS-3: the driven plate is a bare readout with a dim underline -- flip: `.plate.off`'s greyed plate. */
#keyRoot::after, #keyScale::after, #intCap::after {     /* LANE INT-CAP: the Interactions menu too */ content: '▾'; flex: 0 0 auto; opacity: var(--a-dim); }
#keyRoot.driven, #keyScale.driven, #keyRoot.driven:hover, #keyScale.driven:hover { background: transparent; cursor: default; }
#keyRoot.driven::after, #keyScale.driven::after { content: none; }
#keyRoot.driven .nm, #keyScale.driven .nm {
    text-decoration-line: underline; text-decoration-color: var(--dim);
    text-decoration-thickness: 1px; text-underline-offset: calc(var(--u) / 2);
}

/* LANE P90: THE DOCUMENT'S NAME -- a FIXED 34u slot between Presets and Export (WORLD went, lane HEADER), so the header never shifts when a name or
   its mark changes (the mockup's alignment audit): the name semibold (Untitled in the label ink), then ONE
   mark -- a dot while the world is edited, a check for 4 s after a save. A longer name ends in an ellipsis. */
#docName { flex: 0 0 auto; width: calc(var(--u) * 34); display: flex; align-items: center; gap: calc(var(--u) * 1.5);
           padding: 0 var(--u); box-sizing: border-box; }
#docName .nm { color: var(--text); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
#docName .nm.untitled { color: var(--dim); font-weight: 400; }
#docName .mark { flex: 0 0 calc(var(--u) * 3); width: calc(var(--u) * 3); display: flex; align-items: center; }
.edot { width: calc(var(--u) * 1.5); height: calc(var(--u) * 1.5); border-radius: 50%; background: var(--text); display: inline-block; flex: 0 0 auto; }
#docName .ck, .pp-status .ck { color: var(--g-act); font-weight: 600; }
/* ...and the header's RIGHT END answers a save: "Saved 20:41:07", 4 s, then it fades */
#docStatus { margin-left: auto; white-space: nowrap; color: var(--text); opacity: 0; transition: opacity 0.6s; }
#docStatus.on { opacity: 1; transition: none; }
#docStatus.bad { color: var(--dim); }      /* LANDING 6 (P90 review 4): a refusal, the dialog's own refusal tone (.pp-status.bad) */

/* the Factory 2 brand block: the emblem, 8 px of air, the all-caps name, all on one centre line */
#brand { display: flex; align-items: center; gap: calc(var(--u) * 2); }
#brand svg { display: block; height: calc(var(--u) * 6); width: auto; }
#brand .word {
    font-size: var(--fs-word);
    letter-spacing: 0.08em;
    font-weight: 600;
    line-height: 1;
    /* ONE formula for every brand ink: top = the ink, bottom = tint(ink, -0.15) = 85 % of it */
    background: linear-gradient(180deg, rgba(var(--ink), var(--a-text)) 0%, rgba(204, 204, 201, var(--a-text)) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* LANE HEADER-USER (rico 2026-10-18: "the sugar bytes symbol must sit a bit higher, its currently not centered to Sonde."):
       the word's box was its LINE (line-height 1), and where the caps stand in a line is the FONT's (its ascent and descent):
       measured here (tools/check-account.js AC1) the emblem's ink centre stood 0.25 px (Inter, the "Segoe UI" fallback) to
       0.75 px (Liberation Sans) BELOW the caps' centre at 2x, more in a font whose ascent runs taller. The box is now trimmed to
       the CAPS -- cap height over the baseline -- so the band's one centre line runs through the emblem's middle and the caps'
       middle alike, in every font: 0.00 px. A browser without `text-box` keeps the line box (as before).
       ASSUMED READING LOGO-CAP: SONDE is all caps, so it reads centred on its cap height (no x-height in it) and the emblem keeps
       its seat on the centre line -- flip: a fixed nudge of the emblem up, measured in one font. */
    text-box: trim-both cap alphabetic;
}

/* ── LANE HEADER-USER: THE ACCOUNT, TOP RIGHT (index.html #acctPlate / #acctPop, forteria-app.js "LANE HEADER-USER") ─────────
   THE PLATE: the person hollow (no account) or filled (an account is known, live or cached). THE POPUP drops from it, right-
   aligned to it, in DROP-LOOK's card (k 1, its hairline and ground-tone shadow) on the graphite ladder the sheet uses -- fields
   SUNK one step (the panel's tone on the card), the house plates on it; its rows read like a dropdown's (no plate, the whole row
   lit under the hand). A row a server lacks wears `.soon`: the off ink and a small-caps SOON. */
#acctPlate .ac { fill: none; }
#acctPlate.in .ac { fill: currentColor; }
#acctPop { position: fixed; z-index: 22; display: none; flex-direction: column; gap: calc(var(--u) * 2);
           width: calc(var(--u) * 80); box-sizing: border-box; padding: calc(var(--u) * 3);
           max-height: calc(100vh - var(--u) * 20); overflow-y: auto;
           --text: var(--g-text); --dim: var(--g-lab); --off: var(--g-off); --plate: var(--g-ctl); --plate-hi: var(--g-ctl-hi); --lit: var(--g-act);
           color: var(--text); background: var(--g-card); border-radius: var(--card-r);
           box-shadow: 0 0 0 1px var(--g-hair), 0 calc(var(--u) * 3) calc(var(--u) * 8) color-mix(in srgb, var(--bg) 70%, transparent); }
#acctPop.open { display: flex; }
#acctPop .fld { background: var(--g-panel); width: 100%; }
#acctPop .ac-head { display: flex; align-items: baseline; gap: calc(var(--u) * 2); font-size: var(--fs-title); }
#acctPop .ac-head .lab, #acctPop .ac-say, #acctPop .ac-ver, #acctPop .ac-text { color: var(--dim); }
#acctPop .ac-say:empty { display: none; }
#acctPop .ac-say.ok { color: var(--text); }
#acctPop .ac-text { line-height: 1.4; }
#acctPop .ac-acts { display: flex; justify-content: flex-end; gap: var(--u); }
#acctPop .ac-who { display: flex; align-items: center; gap: calc(var(--u) * 2); font-size: var(--fs-title); font-weight: 600; min-width: 0; }
#acctPop .ac-who svg { flex: 0 0 auto; }
#acctPop .ac-who .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#acctPop .ac-tag, #acctPop .ac-cap, #acctPop .soon .k { font-size: 11px; font-weight: 400; letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); }
#acctPop .item { flex: 0 0 auto; height: calc(var(--u) * 9); display: flex; align-items: center; gap: calc(var(--u) * 3);
                 padding: 0 calc(var(--u) * 2); margin: 0 calc(var(--u) * -2); border-radius: var(--r); cursor: pointer; font-size: var(--fs-title); }
#acctPop .item:hover { background: var(--g-ctl); }
#acctPop .item .k { margin-left: auto; color: var(--dim); font-size: var(--fs-label); }
#acctPop .item.soon, #acctPop .item.busy { color: var(--off); cursor: default; }
#acctPop .item.soon:hover, #acctPop .item.busy:hover { background: transparent; }
#acctPop .ac-sep { flex: 0 0 1px; height: 1px; background: var(--g-hair); }
#acctPop .ac-or { display: flex; align-items: center; gap: calc(var(--u) * 2); color: var(--dim); }
#acctPop .ac-or::before, #acctPop .ac-or::after { content: ''; flex: 1 1 0; height: 1px; background: var(--g-hair); }
#acctPop .ac-google { display: flex; justify-content: center; min-height: 40px; align-items: center; color: var(--dim); }
#acctPop .ac-row { display: grid; grid-template-columns: calc(var(--u) * 22) minmax(0, 1fr); column-gap: calc(var(--u) * 2); align-items: center; }
#acctPop .ac-pair { display: flex; gap: var(--u); min-width: 0; }
#acctPop .ac-pair .plate { flex: 1 1 auto; min-width: 0; justify-content: center; padding: 0 var(--u); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A NARROW DESK (a tablet lays the desk out at 1120, index.html VIEWPORT-1): the band ran 1274 px of 1120 before this plate, its
   Interactions cut off at the window. The groups stand --gap-ctl apart under 1366 px (1342 needs the 24 px gaps) and, under
   1280, Interactions gives its room to the plate -- the account popup's Settings is its seat there (forteria-app.js HU-SET).
   ASSUMED READING HU-NARROW: tighter gaps, then Interactions out of the band -- flip: the band scrolls. */
@media (max-width: 1365px) { html:not(.phone) #header { column-gap: var(--gap-ctl); } }
@media (max-width: 1279px) { html:not(.phone) #header > .grp:has(> #intCap) { display: none; } }

/* ── D1 THE BUTTON SIZE TIERS (LANE TIERS, P97 plan) ─────────────────────────────────────────────
   rico 2026-10-03: "buttons everywhere are wiggling around because their size depends on the text?
   we need fixed button size tiers." A FREE-STANDING plate (one not inside a bar or a column) takes
   ONE of five fixed widths; its words never move it. The ladder is 7u x 1, 2, 3, 4 (S = half of the
   old 14u transport plate), plus the SQUARE (the plate's own height) for one letter/sign. A word
   longer than its tier ends in an ellipsis (`overflow:hidden; text-overflow:ellipsis`, applied to
   the plate itself since most plates here hold a bare text node, not a wrapped <span> -- the
   mockup's own `.plate.t-m > span` selector assumes a child span that most of this live DOM does
   not have) and its full text sits in `title` so a hover still reads it; the plan's "full text as
   the title" is ASSUMED to apply only where a word is actually truncated -- none of the words this
   lane ships are (measured below), so no `title` rewrite was needed. Plates of one group share the
   larger tier. BAR plates (equal shares, ending on a column line) and COLUMN plates (filling their
   own grid column: a list/menu plate, Remove, Add modulator) are NOT tiers -- they stay auto. */
:root {
    --t-sq: calc(var(--u) * 6);    /* 24  one letter / one sign: x  >  <  M  S                       */
    --t-s:  calc(var(--u) * 7);    /* 28  one glyph or <= 3 characters: 120, C#                       */
    --t-m:  calc(var(--u) * 14);   /* 56  one short word or value: Presets, Load, Import..., 1/64     */
    --t-l:  calc(var(--u) * 21);   /* 84  two words / a long word: Overwrite, Minor pent              */
    --t-xl: calc(var(--u) * 28);   /* 112 a phrase: Confirm delete?, Load into <device>                */
}
.plate.t-sq, .plate.t-s, .plate.t-m, .plate.t-l, .plate.t-xl {
    min-width: 0; flex: 0 0 auto; overflow: hidden; text-overflow: ellipsis;
    /* FIX ROUND 1 (F1): `safe` -- an overflowing CENTRED item loses its START (a long word was cut at both ends);
       safe centring keeps the start and lets the END go. */
    justify-content: safe center;
}
/* FIX ROUND 1 (F1): a plate that holds DATA (a device / preset name) wraps its word in `<span class="tx">` (app `wordPlate`):
   text-overflow does not apply to a flex container's anonymous text, so the ellipsis lives on the child. */
.plate > .tx { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plate.t-sq { width: var(--t-sq); padding: 0; }
.plate.t-s  { width: var(--t-s);  padding: 0; }
.plate.t-m  { width: var(--t-m);  padding: 0 var(--u); }
.plate.t-l  { width: var(--t-l);  padding: 0 var(--u); }
.plate.t-xl { width: var(--t-xl); padding: 0 var(--u); }
/* D1-4: the header's QUANTIZE is a SWITCH like every two-state plate since lane IN-1 -- the on-dot,
   no ON / OFF word -- so "Quantize OFF" / "Quantize ON" can never move it (and "Quantize" + a dot
   fits L: 10 + 8 + ~50 px). The `.st` word stays in the DOM (every reader that asks the state still
   gets it, lane IN-1's own convention) and is only hidden here. */
#quant { justify-content: flex-start; gap: calc(var(--u) * 2); }
#quant .st { display: none; }
#quant .nm { flex: 0 0 auto; }
#quant::before { content: ''; flex: 0 0 10px; width: 10px; height: 10px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px currentColor; }
#quant.on { background: var(--plate); color: var(--text); font-weight: 400; }
#quant.on::before { background: currentColor; box-shadow: none; }

/* ── PLATES: the one button/menu size, everywhere ───────────────────────────────────────────── */
.plate {
    height: var(--h-plate);
    min-width: var(--h-plate);
    padding: 0 var(--gap-ctl);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--gap-label);
    background: var(--plate);
    border-radius: var(--r);
    color: var(--text);
    font-size: var(--fs-label);
    font-family: inherit;
    cursor: pointer;
    white-space: nowrap;
}
.plate:hover { background: var(--plate-hi); }
.plate.on { background: var(--lit); color: var(--bg); font-weight: 600; }
/* F8: a toggle plate carries its NAME on the left and its STATE on the right, inside the one plate.
   The name is what the plate switches; the state is what it is doing. Nothing else changes -- the
   lit plate is still black semibold on ink. */
.plate .nm { flex: 1 1 auto; text-align: left; }
.plate .st { flex: 0 0 auto; text-align: right; }
/* a disabled plate: greyed AT ITS PLACE, never hidden (a consumer that is not on Pulses) */
.plate.off { color: var(--off); pointer-events: none; }
.plate.off:hover { background: var(--plate); }
/* lane IN-1: a greyed plate that is the CHOSEN one (the waveguide's `+` while the bank rings) does not light --
   the mockup's `.off .plate.on`: the plate tone, the disabled ink */
.plate.on.off { background: var(--plate); color: var(--off); font-weight: 400; }
/* (lane IN-1's lit layer plates in their layer's hue left with the layers: lane LAYERS-RETIRE) */

/* A WRAPPING PLATE ROW, all at the ONE plate size. It held the probe panel's TARGETS until lane
   P3c-2 removed the routing; it is kept because the rack (P3c-3) is the next thing that needs it. */
.targets { display: flex; flex-wrap: wrap; gap: var(--gap-ctl); }
.plate.wide { min-width: calc(var(--u) * 14); }
.plate .caret { opacity: var(--a-dim); }
.plate.on .caret { opacity: 1; }

/* THE TRANSPORT PLATES, DRAWN AS SYMBOLS (LANE TR, rico 2026-10-02: "header transport controls need
   symbols instead of words"). The glyph itself is the inline <svg> in index.html; it needs nothing
   here to sit centred -- `.plate`'s own flex (align-items / justify-content: center, above) centres
   whatever is inside it, so a glyph whose own bbox is centred on its 16x16 viewBox lands on the
   plate's optical centre for free. `fill="currentColor"` / `stroke="currentColor"` on each shape
   means the glyph paints in the plate's OWN ink at its current state already -- unlit var(--text),
   lit var(--bg) (`.plate.on` above) -- no new colour rule needed.
   WIDTH (LANE HEADER, rico 2026-10-03: "make the transport controls half their width"): PAUSE / STOP / LOOP are
   S plates (28 px, `.t-s` in index.html -- the old 14u halved) and PLAY | KEYS is ONE M plate (56 px) of two S
   halves with a 1 px seam of the ground between them ("combine the playbutton with a keyboard button"). The
   halves carry the plate look themselves (the M plate's own background is none, so the seam shows the header).
   PLAY lights while the song plays (`#play.on`, the page's own toggle -> the PLAY half); KEYS lights while it is
   ARMED (`#keysHalf.on`, page state): armed, the next note -- a computer key or a MIDI key -- starts PLAY. */
#play.split { width: var(--t-m); padding: 0; gap: 0; background: none; overflow: hidden; }
#play.split.on, #play.split:hover { background: none; font-weight: 400; }
#play.split > .half { flex: 0 0 calc(50% - 0.5px); height: 100%; display: flex; align-items: center; justify-content: center;
                      background: var(--plate); color: var(--text); }
#play.split > .half + .half { margin-left: 1px; }
#play.split > .half:first-child { border-radius: var(--r) 0 0 var(--r); }
#play.split > .half:last-child  { border-radius: 0 var(--r) var(--r) 0; }
#play.split > .half:hover { background: var(--plate-hi); }
#play.split.on > #playHalf, #play.split > .half.on { background: var(--lit); color: var(--bg); }
#pause svg, #stop svg, #loopPlate svg, #play .half svg, #editPlate svg { display: block; }   /* LANE EDIT-MODE: the EDIT plate too */
/* LANE UNDO: the Undo | Redo plates (the header's, the phone's switch row's) -- glyphs as the transport's; greyed is `.plate.off` */
#undoPlate svg, #redoPlate svg, .ph-hist svg { display: block; }

/* a number you drag: a plate that shows a value */
.num { font-variant-numeric: tabular-nums; }

.lab { color: var(--dim); }
.val { color: var(--text); font-variant-numeric: tabular-nums; }
/* C-2's TYPED VALUE (lane MOD-C): the editor stands IN PLACE of `.val`'s own text (its updater
   skips the write while this is open), so it takes the same font and the same tabular numerals --
   no overlay, no second box to align. */
.val input.type-edit, #bpm input.type-edit {
    width: calc(var(--u) * 15);
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--text);
    color: var(--text);
    font: inherit;
    font-variant-numeric: tabular-nums;
    text-align: right;
    padding: 0;
    outline: none;
}
/* ...and the header BPM (fix round 1, item 5): the plate's own three digits wide, centred as its number is */
#bpm input.type-edit { width: calc(var(--u) * 6); text-align: center; }

/* ── THE TIMELINE STRIP: THE RULER AND THE LOOP BAR (lane P6b; PARKED P77 A-1, rico 2026-09-30) ── */
/* ONE ROW of the inspector's own row grid (8u): the ruler and the loop bar, in LIVE's own canvas.
   THE PARTS ROW and THE PART FIELD stood here as a second row (8u) and a gutter-wide panel beside it
   -- 19u and a `display:flex` row -- until P77 A-1 took the arranger off the GUI (rico 2026-10-01:
   "we still have use for the timeline", so only the row and the field left, not the strip). PARKED,
   not deleted: `#partField`'s two rules stay commented so a returning arranger finds its own layout
   unchanged, and QUESTION AT REVIEW names whether the strip goes back to 19u or stays its own height
   with the field elsewhere. */
/* LANE TL-RETIRE (rico 2026-10-12, verbatim: "and lets retire the timeline for now. the audio export then needs a length
   setting."): the strip is HIDDEN -- parked, not deleted, as the parts row before it: its canvas, gestures and these rules stay
   for the day it returns (`display` back to `flex` below). The export's length is the sheet's own Length row now. */
#timeline {
    flex: 0 0 auto;
    height: calc(var(--u) * 8);
    margin: 0 var(--margin);
    display: none;                     /* LANE TL-RETIRE; was `display: flex` */
    gap: var(--gap-group);
}
#timeline canvas { display: block; flex: 1 1 auto; min-width: 0; height: 100%; }
#partField { display: none; }
/* Its second row held two choice rows side by side where the panel would stack them: Gen | Fix in
   the first column, the last part's Loop | Stop across the other two.
#partField .cols > .ctl.choice.dice { grid-column: 1 / 2; }
#partField .cols > .ctl.choice.end { grid-column: 2 / 4; } */
/* (THE TAPE'S READ-ONLY RULE -- body.tape-lock -- stood here until lane P6b withdrew the lock.) */

/* ── THE MIDDLE: SPACE | INSPECTOR ──────────────────────────────────────────────────────────── */
#main {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    gap: var(--gap-group);
    padding: var(--gap-group) var(--margin) 0 var(--margin);
    position: relative;                  /* the hide plate's border home is measured from here */
}
#space { flex: 1 1 auto; min-width: 0; position: relative; }

/* THE HIDE PLATE (lane P5-0): ONE plate at THE plate size, no outline, two homes and no second
   control. Inside the panel's head row it is an ordinary plate on that row's own centre line, its
   right edge on the panel's content edge. Hidden, the app hands it to #main and THIS rule -- which
   matches only there -- stands it in the right border of the space: top on the content top, so it
   sits at the head row's own height, right edge on the window's content edge (the outer margin).
   Both homes are named positions on the raster; neither is placed by eye. */
body.insp-hidden #inspector { display: none; }
#main > #inspTab {
    position: absolute;
    top: calc(var(--gap-group) + var(--card-pad));     /* lane IN-1: the head row now stands under the panel's 3u top inset */
    right: var(--margin);
}
#space canvas { display: block; width: 100%; height: 100%; cursor: default; }

#inspector {
    flex: 0 0 var(--w-inspector);
    width: var(--w-inspector);
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-gutter: stable;
    display: flex;
    flex-direction: column;
    gap: var(--gap-group);
    /* THE AIR BESIDE THE SCROLLBAR IS A REAL BOX (lane P19, G-4). rico 2026-09-26: "the inspector is
       still touching the scrollbar." It is PADDING and not a number in a JS sum, for a measured
       reason: `fitInspector` first tried to read `--w-air` with `getComputedStyle().getPropertyValue`,
       and an UNREGISTERED custom property comes back as its own token stream ("calc(var(--u) * 2)"),
       so `parseFloat` was NaN, the air was 0 and the columns still touched the bar (measured: panel
       448 = grid 444 + scrollbar 4, gap 0). As padding it is a USED LENGTH the panel really has --
       `fitInspector` reads it back resolved, and no column can enter it whatever the sum says. */
    padding-right: var(--w-air);
    padding-bottom: var(--gap-group);
    /* LANE IN-1 (THE GRAPHITE LAYERS, :root): the panel is the ladder's first surface, k 0, with the
       mockup's insets -- 3u on top, 2u on the left (paid by the column gap, see --gap-col) -- and its
       cards stand 2u apart: groups come from SURFACE now, not from air. EVERY TONE THE PANEL'S RULES
       READ BY NAME is re-pointed here onto the opaque ladder, so a label, a value, a plate and a lit
       plate are the mockup's tones wherever they stand inside it (a custom property resolves where it
       is declared, so the names are declared again rather than only their alphas). */
    padding-left: var(--w-margin-l);
    padding-top: var(--card-pad);
    gap: var(--gap-col);
    background: var(--g-panel);
    --text: var(--g-text);
    --dim: var(--g-lab);
    --off: var(--g-off);
    --plate: var(--g-ctl);
    --plate-hi: var(--g-ctl-hi);
    --lit: var(--g-act);
}
#inspector::-webkit-scrollbar { width: var(--u); }
#inspector::-webkit-scrollbar-thumb { background: var(--plate); border-radius: var(--r); }
#inspector::-webkit-scrollbar-track { background: transparent; }

/* the panel's title row: the same centre-line law as the header */
.titlerow {
    height: var(--h-plate);
    display: flex;
    align-items: center;
    gap: var(--gap-ctl);
}
.titlerow .name { font-size: var(--fs-title); }
/* LANE NAME-EDIT: the name is a door to its own field -- a text cursor over it; the field wears the name's size on the
   sheet's field ground, as wide as a long name */
.titlerow .name.nameedit { cursor: text; border-radius: var(--r); }
.titlerow .name.nameedit:hover { color: var(--text); background: var(--g-plate-hover, rgba(255,255,255,0.06)); }
.titlerow .name-in { font: inherit; font-size: var(--fs-title); color: var(--text); background: var(--bg); border: 0;
                     border-radius: var(--r); padding: 0 var(--gap-label); width: calc(var(--u) * 40); min-width: 0; outline: none;
                     box-shadow: inset 0 0 0 1px var(--dim); }
.titlerow .spacer { flex: 1 1 auto; }
/* lane IN-1: what the object IS, dim beside its name (the mockup's "DRUM  Body", "E5  emitter · radio") --
   it replaces the title row's `Entity [Emitter]` plate, which was lit and did nothing (audit friction 9) */
.titlerow .kind { color: var(--dim); white-space: nowrap; }
/* LANE LAYERS-RETIRE: the thing's own colour, a dot beside its name; a press steps it one golden angle on (Shift back) */
.titlerow .swatch { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; cursor: pointer; }
#inspector > .titlerow { margin-bottom: var(--u); }      /* the mockup's 1u: title to the first card = 3u */
/* LANE PRESET-NAME: the Presets plate of an entity that WEARS a preset says that name, one tier wider (t-xl); the name
   ends in an ellipsis inside it (`.tx`), the edited dot (`.edot`, the header's own mark) after it in the plate's ink. On
   a narrow inspector (a phone) the plate gives way down to t-m before the row would overflow: it is the one item in the
   head row that may shrink, so the name, kind, Level and the hide plate keep their place. */
.titlerow .plate.worn { flex: 0 1 auto; min-width: var(--t-m); justify-content: flex-start; }
.titlerow .plate.worn .edot { background: currentColor; }

.section { display: flex; flex-direction: column; gap: var(--gap-ctl); }
/* ── A SECTION IS A CARD (lane IN-1, I-2; tone.md amendment 2) ─────────────────────────────────────
   One surface step over the panel (k 1), the mockup's 6 px radius, NO OUTLINE (the blob law), a 3u
   inset. THE EDITOR PLATES' OWN RUNG (lane P60 K-3's `#inspector .section` re-seat) IS GONE INTO THE
   LADDER: a plate now stands on the card at k 2, a full doubling over the surface it sits on, which is
   the "distinguish it from the headers" he asked P60 for, by construction. A section NESTED in another
   (the POOL, the KEYBOARD) is a SUB-SECTION of its card: no surface of its own, a tt3 hairline above it
   -- the one place a line is drawn inside a card (tone.md amendment 5). */
#inspector .section {
    background: var(--g-card);
    border-radius: var(--card-r);
    padding: var(--card-pad);
    flex: 0 0 auto;
}
#inspector .section .section {
    background: none;
    border-radius: 0;
    padding: var(--gap-ctl) 0 0 0;
    border-top: 1px solid var(--g-hair);
}
/* A CAPTION IS A CAPTION WHEREVER IT STANDS. It used to be dim only as a section's own child, so
   the three COLUMN captions inside `.cols` -- TIME | PLACE | TONE -- drew at full ink. Nobody saw
   it while they were the first thing in the panel; lane P9b put SOUND GENERATOR directly above
   them and the hierarchy read INVERTED in the shot, the sub-heading shouting over the heading.
   One rule for both, so a caption can never again be louder than the section that contains it.
   LANE P5a adds the third home -- inside a fold header -- to the SAME rule rather than copying its
   two values, because a caption that folds is still a caption. */
.section > .cap, .section > .foldhead > .cap, .cols > .cap { color: var(--dim); letter-spacing: 0.14em; }
/* LANE IN-1: TWO LEVELS OF TYPE (audit friction 10). A CARD'S TITLE is its name in the ink, semibold, in
   the word's own case ("Sound", "Filter", "CV seq") -- the mockup's head; a COLUMN caption inside it
   (BODY | EXCITER | TONE) stays the dim letter-spaced caps above. The title outranks the caption now
   instead of looking the same. */
#inspector .foldhead > .cap { color: var(--text); font-weight: 600; letter-spacing: 0; }

/* ── THE FOLD HEADER (lane P5a, amendment 2 I-2) ─────────────────────────────────────────────── */
/* rico 2026-09-25: "each sections properties are folded: can be expanded to show its parameters.
   the header just shows the name." -- so the header IS the section's caption, unchanged in tone and
   letter-spacing. (Lane P5a added a dim one-word summary in the value column, his R-FOLDHEAD pick;
   lane P27 took it back on his later word -- see below.)
   IT IS THE ROW LAW, NOT A NEW OBJECT: label left, value right, on one centre line, at the one
   plate height every head row in this panel stands at. No box, no outline, no divider, no caret.
   THE SECTION DIE'S SEAT (I-2) IS LEFT EMPTY AND RESERVES NO PIXELS. Lane P5b builds the dice and
   will place them on this row's right; a strip held open for an object whose size nobody has
   decided is exactly how unexplained air gets into a panel.
   LANE P27, D-6 -- THE HEADER IS ITS NAME. rico 2026-09-27: "the header should be: name.
   expand/shrink could be right-aligned, plus the double click on the header." The summary is gone
   and ONE glyph stands where it stood, right-aligned in the value column in the same dim ink:
   `+` shut, `−` open. The glyph takes a press; the whole header takes a double-click; the NAME
   takes a single press and does nothing with it -- so the pointer is the plain one over the name
   and the hand only over the glyph. Still no box, no outline, no divider. */
.foldhead {
    height: var(--h-plate);
    display: flex;
    align-items: center;
    gap: var(--gap-ctl);
    cursor: default;
    user-select: none;
}
.foldhead > .cap { flex: 1 1 auto; }
.foldhead > .fold { flex: 0 0 auto; color: var(--dim); text-align: right; cursor: pointer; }
/* The one hint that the row takes a gesture, and it is the plate tone this panel already uses for
   "you may press here" -- no new colour, no underline, no cursor invention. */
.foldhead:hover > .cap, .foldhead:hover > .fold { color: var(--text); }
/* ── THE ON-DOT (lane IN-1, I-2; rico 2026-10-02 RULED "Allow the dot (Recommended)") ─────────────
   A header is its NAME plus ONE dot: FILLED = on, RING = off -- the mod opener's own two-weight grammar
   (outline = nothing, solid = something). It stands BEFORE the name, so every title starts on one x
   whether its section switches or not (`.pslot` holds the seat where nothing switches). The dot is the
   section's old first ON / OFF row, moved: a press switches it, readable while the card is folded.
   Its visible disc is 10 px (lane TIERS, D7-1: "the section on/off buttons could be bigger," 6 -> 10
   px); the press zone is 24 px square around it -- the plate's own height, so the dot's zone reads
   as one more plate-sized target like everything else it stands beside (negative margins keep the
   10 px seat in the row). */
.foldhead { gap: calc(var(--u) * 2); }
.foldhead > .cap { flex: 0 1 auto; }
.foldhead > .sp { flex: 1 1 auto; }
.foldhead > .cnt { color: var(--dim); white-space: nowrap; }
/* LANE IN-2p: a FOLDED card's live one-line readout after its name (the MIDI card's door sentence) -- the
   label tone, cut at the header's room (the whole sentence is its tooltip); the spacer after it still
   pushes the fold glyph to the right edge. */
.foldhead > .sum { flex: 0 1 auto; min-width: 0; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.foldhead > .pslot { flex: 0 0 10px; width: 10px; height: 10px; }
/* LANE SPACE-HELP: THE CARD'S [?] (rico 2026-10-10: "a little [?] in the section headers") -- the fold glyph's dim ink,
   the caption's size, right after the title; lit (the ink) while its help shows. The help text stands under the header
   in the label tone, wrapped to the card, hidden until asked for (forteria-app.js SECTION_HELP). */
.foldhead > .help { flex: 0 0 auto; color: var(--dim); cursor: pointer; white-space: nowrap; }
.foldhead > .help.on, .foldhead > .help:hover { color: var(--text); }
.section > .helptext { color: var(--dim); white-space: normal; line-height: 1.4; padding: 0 0 var(--u) calc(10px + var(--u) * 2); }
.section > .helptext[hidden] { display: none; }
/* UNDER A FINGER (lane MOBILE-2's rule, Apple's 44 px): the [?]'s press box grows to 44 SCREEN px square round the same
   glyph, its extra height laid over the air above and below the header (negative margins: the row keeps the plate
   height, so no card grows on the phone). ASSUMED READING HELP-FINGER: the box grows, the glyph does not -- flip: a 44 px
   header row. */
@media (pointer: coarse) {
    .foldhead > .help { position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center;
                        min-width: calc(44px * var(--px-screen, 1)); height: calc(44px * var(--px-screen, 1));
                        margin: calc((var(--h-plate) - 44px * var(--px-screen, 1)) / 2) 0; }
}
.pdot { position: relative; flex: 0 0 24px; width: 24px; height: 24px; margin: 0 -7px; cursor: pointer; }
.pdot::after {
    content: '';
    position: absolute;
    left: 7px; top: 7px; width: 10px; height: 10px;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1.5px var(--text);
}
.pdot.on::after { background: var(--text); box-shadow: none; }
/* inside a LIT plate the dot takes the plate's own black ink (the mockup's `.plate.on .pd`) */
.plate.on .pdot::after { box-shadow: inset 0 0 0 1.5px var(--bg); }
.plate.on .pdot.on::after { background: var(--bg); box-shadow: none; }

#wave { height: calc(var(--u) * 16); }
#wave canvas { display: block; width: 100%; height: 100%; }

/* THE THREE COLUMNS ARE ONE ROW GRID, not three independent stacks. That is what lets an unfolded
   card span all three and push the rows below it down TOGETHER, so every row in every column keeps
   sitting on one common baseline (A-2). Row gap is the control gap, column gap the group gap. */
.cols {
    display: grid;
    grid-template-columns: repeat(3, var(--w-col));
    column-gap: var(--gap-col);            /* lane IN-1: 2u, the card pays for itself (--gap-col) */
    row-gap: var(--gap-ctl);
    align-items: start;
}
.col { display: flex; flex-direction: column; gap: var(--gap-ctl); }

/* ── THE KNOB, THE ONE RENDERING ────────────────────────────────────────────────────────────── */
.ctl { height: var(--h-row); display: grid; align-items: center; cursor: ns-resize; }
.ctl .row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--gap-label); }
/* F7: a label is NEVER truncated. Every row stands in a column wide enough for its own words --
   that is what "nothing stays undefined" means for type. If a label would not fit, the word gets
   shortened deliberately in META, never by an ellipsis at runtime. */
.ctl .row .lab { white-space: nowrap; }
.ctl canvas { display: block; }

/* knobs: THE ROW LAW (lane ROWLAW, P97 D8-1 + D8-4) -- rico 2026-10-03: "all parameters, do it like
   this: name, mod>, knob, value." The row stands on the four tracks of the width table (:root):
   NAME | MOD | KNOB | 1u | VALUE. The DOM is unchanged ([row: lab, val][canvas]); `.row` takes no box
   of its own (`display: contents`), so its name and its value stand in the row's own grid beside the
   knob, each on its track's left edge (a value is LEFT-aligned now: D8-4), all on the knob's centre
   line. The knob is the one rendering since lane P49; `ctl-knobs` stays on <body> so every rule here keeps
   the specificity it was tuned at.
   LANE ROW-LOOK (V1): NAME | KNOB | 1u | VALUE -- the MOD track is gone (the width table, :root). The knob's canvas
   is its 9u track SQUARE (36 px): the same 26 px knob (forteria-app.js KNOB_R) with its mod-ring pad, standing 2 px
   into the row gap above and below -- the pad is ink only where a knob is modulated, and the gap is 12 px. */
body.ctl-knobs .ctl { grid-template-columns: var(--w-lab) var(--w-knob) var(--u) minmax(0, 1fr); column-gap: 0; }
body.ctl-knobs .ctl canvas { grid-column: 2; grid-row: 1; width: var(--w-knob); height: var(--w-knob); }
body.ctl-knobs .ctl > .row { display: contents; }
body.ctl-knobs .ctl > .row > .lab { grid-column: 1; grid-row: 1; align-self: center; }
body.ctl-knobs .ctl > .row > .val { grid-column: 4; grid-row: 1; align-self: center; text-align: left; white-space: nowrap; }

/* a toggle row is ONE plate. LANE ROW-LOOK: it no longer fills the column -- rico 2026-10-04: "buttons that are big,
   only to fill the row?" -- an ACTION plate (actionPlate: Add modulator, Remove, Record, Clear, Edit, Randomize...)
   takes the TIER its words need (forteria-app.js `tierPlates`, the lane TIERS classes) and stands at its column's
   control x (name track + --x-ctl), the x every knob, bar and menu of that column starts on; its name and its state
   word stand CENTRED together, one label gap apart (F2 drawSwitchBand, F2SwitchButton.h:40-46). */
.ctl.toggle { cursor: pointer; grid-template-columns: 1fr; }
body.ctl-knobs .ctl.toggle { grid-template-columns: 1fr; }
/* `safe center` -- lane TIERS' F1 law (`.plate.t-*`): a caption that ever outgrows its tier keeps its START and lets the END
   go; a plain `center` here out-ranked the tier class and cut both ends (measured: drive.js tiers, Add modulator -7.9 px) */
.ctl.toggle:not(.switch) > .plate { justify-self: start; margin-left: calc(var(--w-lab) + var(--x-ctl)); justify-content: safe center; gap: var(--u); }
.ctl.toggle:not(.switch) > .plate .nm { flex: 0 0 auto; }
.ctl.toggle:not(.switch) > .plate .st:empty { display: none; }
/* ...and a bare tier plate a builder seats straight in a column (MOTION's Circloid) stands on the same x */
.plate.atctl { justify-self: start; margin-left: calc(var(--w-lab) + var(--x-ctl)); }
/* LANE MOTION-BP: a stored path's two plates, "On (recorded path)" and "Convert to breakpoints", say more than the widest
   tier (t-xl, 112 px) holds -- they take their words' own width (this outranks `.plate.t-*`), never a cut caption */
.section[data-section='MOTION'] .plate.legacy { width: max-content; padding: 0 calc(var(--u) * 2); }
/* ── A SWITCH IS A PLATE PLUS THE ON-DOT (lane IN-1; tone.md ASSUMED READING 3) ──────────────────
   A two-state toggle (`makeCtl` kind 'toggle', class `switch`) no longer LIGHTS and no longer prints
   ON / OFF: the dot before its name says it, filled = on, ring = off -- the same dot the card headers
   wear. LIT STAYS RESERVED FOR "WHICH ONE OF THESE" (a choice, a picked menu row), which is gui_design_
   rules 3's "a 2-state switch has NO on-state". The `.on` class and the `.st` word stay in the DOM (the
   word is simply not drawn), so every reader that asks a switch its state still gets it. Flip: delete
   these four rules and the switch lights again. */
/* LANE ROW-LOOK: a switch plate in a bar (Playhead's Beats / Retrig) holds its dot and its word CENTRED together */
.ctl.switch .plate { justify-content: safe center; gap: calc(var(--u) * 1.5); }     /* safe: TIERS' F1 (the action plates above) */
.ctl.switch .plate .nm { flex: 0 0 auto; }
.ctl.switch .plate .st { display: none; }
.ctl.switch .plate.on { background: var(--plate); color: var(--text); font-weight: 400; }
.ctl.switch .plate:hover, .ctl.switch .plate.on:hover { background: var(--plate-hi); }
/* D7-1 (lane TIERS): ONE dot everywhere -- the same 10 px disc / 1.5 px ring as the fold-head's
   `.pdot::after` above. `.sw-dot` is THIS lane's selector (plan A7: ROWLAW reads it, never resizes
   it) -- staged here for the switch-row plate a later lane (ROWLAW) builds; nothing in this DOM
   wears the class yet, so this rule is inert until it does. */
.ctl.switch .plate::before, .sw-dot::before {
    content: '';
    flex: 0 0 10px; width: 10px; height: 10px;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1.5px currentColor;
}
.ctl.switch .plate.on::before { background: currentColor; box-shadow: none; }
/* A FREE SWITCH ROW (lane ROWLAW, P97 D8-3): "switch = name + a square dot plate" -- its NAME in the name
   track like every row's, then ONE square plate (the plate height, --t-sq) holding nothing but the on-dot,
   standing where a bar starts (LANE ROW-LOOK: the control x, name track + --x-ctl). The dot itself is TIERS' (`.ctl.switch .plate::before` /
   `.sw-dot::before` above, plan A7: never restyled here); this rule only seats and sizes the plate. A switch
   seated IN A BAR (the Playhead row's Beats / Retrig) is not this: it keeps its word inside its plate
   (makeCtl's `plate` seat, ASSUMED READING ROWLAW-PLATE). */
body.ctl-knobs .ctl.toggle.swrow { grid-template-columns: var(--w-lab) minmax(0, 1fr); column-gap: 0; }
.ctl.swrow > .lab { grid-column: 1; grid-row: 1; white-space: nowrap; }
.ctl.toggle.swrow > .plate { grid-column: 2; grid-row: 1; width: var(--t-sq); padding: 0; justify-content: center; margin-left: var(--x-ctl); }
.ctl.swrow > .plate .nm { display: none; }     /* the name is the row's `.lab` now; the plate keeps it undrawn for its readers */

/* a CHOICE row: the label left, the plates right, on the row's one centre line. The plates ARE the
   value, so this is the same "label left, value right" law every other row obeys. */
/* A CHOICE / TRIGGER row: the label, then its plates directly after it. It is never squeezed into a
   column -- it is always given a row of its own that spans the full width of whatever holds it, so
   the plates keep the ONE plate size and their real words. A control may never leave its column. */
/* ── ONE LABEL COLUMN, ALIGNED BARS (lane IN-1, I-3; the mockup's `.ch`) ─────────────────────────
   A choice row stands on the KNOB ROW'S OWN COLUMNS: the knob's 8u seat left empty, the label where a
   knob's label stands (8u + 3u in), a 14u label box, and THE BAR of plates from there to a column edge.
   So every label in a card is on one x (a knob's or a choice's), and every bar starts on one x and
   ends on a column line -- where the plates used to start wherever each label happened to end (audit
   friction 1: Mode 43, Polarity 51, Excite 42, Tap 30 ...). The plates in a bar share it EQUALLY
   (1fr each, 1u apart). A row of TWO options ends at the second column's edge (`.two`), three or more
   at the third's (audit ASSUMED READING 5). The 14u box fits the widest choice label in the panel
   (`Polarity`, `Velocity`, measured by drive.js `inspector`).
   LANE ROWLAW (P97 D8-3): THE SAME LAW ON THE ROW TRACKS -- the name in the NAME track (no empty knob
   seat before it any more: a knob row's name stands first too), the bar from the MOD track's x to its
   column line. So every name of a card column is on ONE x and every bar starts at the column + 14u.
   A LIST row (`.list`: a menu that carried its name inside its plate -- Kind, Wave, Scan dir, Device)
   is the same name + menu plate, but keeps its own column instead of spanning the card. */
body.ctl-knobs .ctl.choice {
    grid-template-columns: var(--w-lab) minmax(0, 1fr);
    justify-content: stretch;
    column-gap: 0;
    row-gap: 0;
    cursor: default;
}
.cols > .ctl.choice:not(.list), .card-open > .ctl.choice:not(.list) { grid-column: 1 / -1; }
.cols > .ctl.choice.two { grid-column: 1 / 3; }
.ctl.choice > .lab { grid-column: 1; grid-row: 1; white-space: nowrap; }
/* ── LANE ROW-LOOK (V1): A BAR IS A ROW OF TIER PLATES, EVERY CAPTION CENTRED ────────────────────────────────────
   rico 2026-10-04: "left aligned fonts on buttons? buttons that are big, only to fill the row?" -- so lane ROWLAW's
   D6-1 (every word at a 2u inset) and IN-1's equal shares (1fr each, ending on a column line) both go. A bar now
   starts at its column's control x (name track + --x-ctl: the x the knobs' rings start on) and its plates stand 1u
   apart, each the lane TIERS width (28 / 56 / 84 / 112, the `.t-*` classes) its words need -- the plates of one
   GROUP share the largest (forteria-app.js `tierPlates`; a `.gap` starts a new group: Mode's + | − after Modal |
   Waveguide) -- and whatever a plate holds (a word, a sign, an FM icon + word, the on-dot + word, a menu's value + ▾)
   is centred on it as ONE block: F2 drawSwitchBand (F2SwitchButton.h:40-46), Tombola Widgets.h:162-193. The
   `.two` / span rules above only give a bar ROOM now; nothing is stretched to fill it. */
.ctl.choice .pair {
    grid-column: 2; grid-row: 1;
    display: flex; align-items: center;
    gap: var(--u);
    padding-left: var(--x-ctl);
    min-width: 0;
}
.ctl.choice .pair .plate { flex: 0 0 auto; justify-content: safe center; min-width: 0; }
.ctl.choice .pair .plate .nm { flex: 0 0 auto; }
/* a MENU is a CHIP: its value and the ▾ centred together, one label gap apart (F2 SBUniClientEditor.cpp:1199
   Align::Center, :1290 the appended " ▾"); as wide as the tier of its WIDEST option, so a pick never moves it */
.ctl.choice .pair .plate.menu { gap: var(--u); }
.ctl.choice .pair .plate.menu > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* a plain CELL carrying a plate's tooltip (the Playhead row): a greyed plate is pointer-events:none, so the title sits here */
.ctl.choice .pair > .tip { display: flex; min-width: 0; }
/* Mode's bar: Modal | Waveguide, then the waveguide's + | − -- the `.gap` cell between the two groups (3u in all) */
.ctl.choice .pair > .gap { flex: 0 0 var(--u); }
/* a row that says something AFTER its bar (Take's "last E3") gives it a third track of its own */
body.ctl-knobs .ctl.choice.tail { grid-template-columns: var(--w-lab) minmax(0, 1fr) max-content; }
/* a row whose plates ARE their own labels (CV seq's seven Draw lanes): the bar takes the whole width,
   as the mockup's lane row does -- seven words in a 75u bar would not fit their plates */
body.ctl-knobs .ctl.choice.bare { grid-template-columns: minmax(0, 1fr); }
.ctl.choice.bare > .lab { display: none; }
.ctl.choice.bare .pair { grid-column: 1; padding-left: 0; }     /* no name track: the bar starts on the column's edge */
/* LANE HAIRS-2: ...AND IT WRAPS, the house's WRAPPING PLATE ROW (`.targets`: every plate at the ONE size). rico
   2026-10-06: "the note seq isnt even reachable in the inspector!" -- seven plates sharing the 84 tier `Ratchet`
   needs are 612 px in a 484 px card, so `Note` stood off the card's edge, unclickable. Wrapped, the row is
   Value .. Curve, then Vel | Note under it, every plate whole at every inspector width (it is a fixed 528 px, and
   MOBILE-1's phone is the desk at DESK_MIN scaled, so the same row). ASSUMED READING H2-4, one rule to flip: WRAP,
   the plates keep their shared tier (flip: per-plate tiers on one row, shrinking each to its own word). */
.ctl.choice.bare .pair { flex-wrap: wrap; row-gap: var(--u); }
.ctl.choice.bare { height: auto; min-height: var(--h-row); }   /* a wrapped row grows, pushing the bars down; one row is one row */
/* a MENU stands in a bar like a plate: its value, then the combo's ▾ (gui_design_rules 7) */
.plate.menu .caret { opacity: var(--a-dim); }
/* (LANE FM-MODES, rico 2026-10-17: "Die Icon-Symbole brauchen wir nicht mehr." -- the FM Mode plate's traced icon, lane
   FM-1's `.plate .fmicon` slot, is gone with fm-icons.js's set 2: the plate prints its word like every other plate.) */

/* A READOUT has no knob, so it must not reserve a knob's gutter: it takes the whole column. (The
   "Points" row overflowed by 16 px in knobs mode until it did.) */
body.ctl-knobs .ctl.readout { grid-template-columns: 1fr; cursor: default; }
.ctl.readout .row { grid-column: 1; grid-row: 1; }
/* LANE IN-1 (I-3): ...BUT ITS LABEL STANDS ON THE ONE LABEL COLUMN -- a readout keeps the knob's 8u seat
   EMPTY (the mockup's read-only `Step` row), so "Lights", "Points", "Step" start where every knob's and
   every choice's label starts. Its value keeps the column's right edge; a readout that spans the card
   (`.wide`: the burst's Next, the arp's Step) says its value right after its label instead, so the two
   stay adjacent (gui_design_rules: a label and what it names stay together). */
/* LANE ROWLAW (P97 D8-3): "readout = name + value at the bar x" -- the name in the NAME track, the value at the
   MOD track's x, where a bar starts, so a read-only value lines up with the plates of its card. One rule for
   every readout: `.wide` (the burst's Next, the arp's Step: their value already sat right after the name) and
   `.flat` (MOTION's Length / Points among plates: it started on its column's edge) read the same now -- the
   name on the column's edge IS the plates' edge, and the value keeps the 25u after it ("2.00 beats" needs 13u). */
/* LANE ROW-LOOK: the bar x is the control x now (name track + --x-ctl), so a readout's value moves with it */
body.ctl-knobs .ctl.readout { grid-template-columns: var(--w-lab) minmax(0, 1fr); column-gap: 0; }
body.ctl-knobs .ctl.readout > .row > .lab { grid-column: 1; grid-row: 1; }
body.ctl-knobs .ctl.readout > .row > .val { grid-column: 2; grid-row: 1; padding-left: var(--x-ctl); }
/* a CHOICE built as a parameter row (Filter's Model) is still a choice: its row spans the card, its bar the
   label column's law (LANE ROW-LOOK: a `.phead` holds its control alone now -- no opener cell to reserve) */
.cols > .phead:has(> .ctl.choice:not(.list)) { grid-column: 1 / -1; }
.cols > .phead:has(> .ctl.choice.two) { grid-column: 1 / 3; }

/* DISABLED = greyed at its own place. It never moves, never shrinks, never hides. */
.ctl.off { pointer-events: none; }
.ctl.off .lab, .ctl.off .val { color: var(--off); }
/* LANE QUANT-SAFE: a Quant readout's second line -- the grid's length in beats, under the grid ("1/16" over "¼ beat") */
.val > .beats { display: block; font-size: 10px; line-height: 1.1; color: var(--dim); }
/* A disabled TOGGLE row: its plate drops out of the lit look and back to the plate tone, or a
   switch that changes nothing would still be the brightest object in its column. No new size, no
   outline -- the same plate, at the same place, in the table's disabled tone. */
.ctl.off .plate { background: var(--plate); color: var(--off); font-weight: 400; }
/* LANE EDIT-MODE: A MIXED ROW -- a multi-selection whose things do not all hold the same value here (forteria-app.js
   `markMixed`). Its value reads "—" in the disabled tone; its knob's arc and its lit plate drop to the disabled look, the
   `.off` row's own; the row keeps taking a hand, and the value it then sets lands on every selected thing.
   ASSUMED READING EM-MIXED (flip: grey the whole row as `.off`). */
.ctl.mixed .val, .plate.num.mixed, .ctl.mixed .plate.menu > span:not(.caret) { font-size: 0; }
.ctl.mixed .val::after, .plate.num.mixed::after, .ctl.mixed .plate.menu > span:not(.caret)::after {
    content: '\2014'; font-size: var(--fs-label); color: var(--off); }
.ctl.mixed canvas, .pdot.mixed { opacity: 0.35; }
.ctl.mixed .plate.on { background: var(--plate); color: var(--off); font-weight: 400; }
/* the head row's count in place of a name ("3 selected", "3 selected · 2 Instruments, 1 Emitter") */
.titlerow .name.multi { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* LANE IN-1: a PARAMETER ROW (`.phead`) greyed by its section -- the Filter's rows while it is off -- reads
   greyed too (the audit's NOTED: "Filter OFF leaves the Model row lit, not greyed"). The LOOK only: such a
   row keeps taking a hand, as it always did, so a cutoff can be set before the filter goes on. */
.phead.off .lab, .phead.off .val { color: var(--off); }
/* ...EVERY plate of such a row, not only the chosen one (the mockup's `.off .plate`): greying only the lit
   plate left its unchosen neighbours the brightest words in the row (Filter OFF's Model, READ shot) */
.phead.off .plate { color: var(--off); }
.phead.off .plate.on { background: var(--plate); color: var(--off); font-weight: 400; }

/* ── A PARAMETER ROW AND ITS RACK ───────────────────────────────────────────────────────────── */
/* THE OPENER IS GONE (LANE ROW-LOOK, V1). It was P60's triangle at the row's right edge (rico 2026-09-30: "use an
   inward-pointing triangle on the right side. it will be the opener and display for modulation"), then ROWLAW's MOD
   track between the name and the knob -- where rico 2026-10-04 found it "the worst thing I have seen EVER, and its
   moving into the knob as if its not wanting to exist". His pick, "V1: mod ring on the knob (Recommended)": the
   house's way, no glyph at all --
     * DISPLAY: a modulated knob draws the range its rack can reach as an arc on its own ring pad (forteria-app.js
       drawKnob / ringSpan; F2 OKIView.cpp:452-474, Tombola BlobLookAndFeel.h:918-922);
     * OPEN: a press on the row's NAME, or a right-click on the row (F2 SBUniClientEditor.cpp:11779-11787, Tombola
       ModulatableSlider.h:1269-1270) -- `.has-rack` marks a row that carries a rack, and its name takes the pointer
       and lights under the hand, the fold header's own "you may press here" (ASSUMED READING ROWLOOK-OPEN).
   A `.phead` is the row's control alone now; the class stays the parameter-row seat every reader names. */
.phead {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: center;
}
.phead > .ctl { grid-column: 1 / -1; grid-row: 1; }
.phead.has-rack > .ctl > .lab, .phead.has-rack > .ctl > .row > .lab { cursor: pointer; }
.phead.has-rack > .ctl > .lab:hover, .phead.has-rack > .ctl > .row > .lab:hover { color: var(--text); }
/* AN UNFOLDED CARD spans all three columns and stands on THE SAME COLUMNS as the rows above it:
   3 x 34u + 2 x 6u = 114u, the main grid exactly. There was NO INDENT (until lane P60's K-7, below).
   WHY THE INDENT WENT (measured in Segoe UI 12 px, after two real Chrome shots showed the wrap): a
   card column of 32u = 128 px is too narrow for two of the card's own rows, and both broke to two
   lines --
       Amount   knob 32 + gap 12 + "Amount" 42.8 + gap 4 + "+12.0 st"   42.7 = 133.5 px
       Scale    knob 32 + gap 12 + "Scale"  27.2 + gap 4 + "Minor pent" 59.2 = 134.4 px
   Both fit in 34u = 136 px and neither fits in 128. A label is NEVER truncated and never wrapped
   (F7), so the column gives way, not the word.
   BELONGING IS STILL SAID, without the indent: the card sits directly under its own row, it opens
   from that row's node dot, and it leads with its own "Card" plate. No box, no outline, no divider
   -- the gaps and the order say what belongs to what, which is the panel law one level smaller.

   ── LANE P60, K-7: THE RACK IS DRAWN AS ITS PARAMETER'S Y-EXTENSION ─────────────────────────────
   rico 2026-09-30: "still unclear: is the parameter-mod screen a popup or a further y-extension of the
   inspector? y-extension is acceptable, but then we must see it as such visually. it must show its
   connection to its root which is pitch in this case." A FIRST, MINIMAL LOOK (the think tank may
   refine it), in three moves:
     * IT OPENS DIRECTLY UNDER ITS OWN ROW, always (forteria-app.js, ASSUMED READING 6 of lane P60);
     * IT IS INDENTED BY ONE GROUP GAP (6u), and the indent is taken from the rack's COLUMN GAPS, never
       from its columns: 6u + 3 x 33u + 2 x 3u = 111u, the main grid exactly -- so every row keeps the
       33u column the census measured (the reason the old indent went) and none can wrap. The rack's
       third column stands on the main grid's third line; its first two sit 6u and 3u in.
     * A HAIRLINE BRACKET holds it to its root: from the row's opener (the triangle, pointing down
       while the rack is open) straight down into the row gap, left along that gap to the middle of the
       indent, and down the rack's whole left edge -- one ink at ONE rung, `--a-bracket` (tier 4, the
       editor plates' own rung). It is the ONE line this panel draws, and it is drawn because he asked
       for the connection to be SEEN; it is a bracket, never a box: no top rule over the rack, no side
       on the right, nothing under it.
   `--root-col` (0, 1, 2) is written on the rack by the page -- the column its parameter stands in --
   and `--opener-x` puts the drop under that row's triangle: its column's left line plus the column
   less 1u, the glyph's centre in its 3u cell. ASSUMED READING 7 (lane P60), one line each to flip:
   the indent (`padding-left`), the rung (`--a-bracket`). */
/* LANE IN-1: THE RACK SHRINKS WITH THE CARD. The main grid is 3 x 33u + 2 x 2u = 103u now (--gap-col), so
   the rack keeps P60's three 33u columns and its third column on the main grid's third line, and pays
   its indent and its gaps out of the 4u that are left: a 2u indent (--rack-in) and 1u gaps. Every rack
   row keeps the 33u column the census measured; only the air between them shrinks. */
.card-open {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(3, var(--w-col));
    column-gap: var(--u);
    row-gap: var(--gap-ctl);
    align-items: start;
    position: relative;
    --rack-in: calc(var(--u) * 2);
    padding-left: var(--rack-in);
    --root-col: 0;
    /* LANE ROW-LOOK: no opener -- the drop hangs from its KNOB's centre line (name track + half the knob track) */
    --opener-x: calc(var(--root-col) * (var(--w-col) + var(--gap-col)) + var(--w-lab) + var(--w-knob) / 2);
    --bracket: rgba(var(--ink), var(--a-bracket));
}
/* the arm and the left edge, one L: from the opener's x, along the middle of the row gap above the rack,
   to the middle of the indent, and down to the rack's last row */
.card-open::before {
    content: '';
    position: absolute;
    pointer-events: none;
    left: calc(var(--rack-in) / 2);
    top: calc(var(--gap-ctl) / -2);
    bottom: 0;
    width: calc(var(--opener-x) - var(--rack-in) / 2 + 1px);   /* +1 px: the arm runs under the drop's column */
    border-top: 1px solid var(--bracket);
    border-left: 1px solid var(--bracket);
}
/* the drop: LANE ROW-LOOK -- from the knob's FOOT (15 px under its centre: the ring's open bottom, so it touches no
   ink -- the ring's ends stand at 45 degrees, the cap ends at 7.5 px) down to the arm, which it meets end to end --
   never overlapping it, or two translucent lines would lay one brighter pixel */
.card-open::after {
    content: '';
    position: absolute;
    pointer-events: none;
    left: var(--opener-x);
    top: calc(var(--gap-ctl) * -1 - var(--h-row) / 2 + 15px);
    height: calc(var(--h-row) / 2 - 15px + var(--gap-ctl) / 2);
    width: 0;
    border-left: 1px solid var(--bracket);
}

/* ── ALL POINTS IS SPEED'S PROPERTY: THE SLAVE PLATE (lane P68, R-3) ───────────────────────── */
/* rico 2026-09-30: "ALL Points is a property of speed, but nothing in the gui tells us that. a connector
   line would do it, or a slave-plate..." The Breakpoints rows' `All` is a SMALL plate on the row UNDER the
   Speed knob, standing under Speed's own label (the knob's 8u + the 3u control gap), at the one plate size
   on the row's centre line. `.tied` (a knob exists above it) draws its CONNECTOR: one L in P60 K-7's
   bracket ink at the bracket's one rung (`--a-bracket`) -- from the knob row's bottom edge (3 px under the
   knob's circle) straight down the knob's centre (4u in) through the row gap to this row's centre line,
   then right along it to the plate's left edge, which it meets end to end (the width stops 1 px short so
   the translucent plate never lies over the line). One box's two borders: the corner is painted once.
   LANE ROWLAW (P97 D8-1): Speed's row reads name, mod, knob, value now, so what stands right of its knob is its
   VALUE: the plate hangs under Speed's value (the name + mod + knob tracks + 1u in), the connector drops from
   the knob's centre (name + mod + 4u in) and runs right to it.
   LANE ROW-LOOK: no mod track -- the plate under Speed's value (name + knob tracks + 1u), the drop down the knob's centre
   (name track + half the knob track), the run from there to the plate (half the knob track + 1u, 1 px short).
   LANE SMALL-UI -- ALL RULES SPEED AND STAY (rico 2026-10-05: "breakpoints: the ALL button is visually connected to travel
   speed, but it works for travel and rest."): the L from Speed's knob alone goes. ASSUMED READING SUI-ALL (one rule pair to
   flip back to the L): a BRACKET UNDER BOTH KNOBS, in the rack bracket's own law (`.card-open::before / ::after` above):
   a drop from each knob's foot (15 px under its centre: the ring's open bottom, no ink touched) to the middle of the row
   gap, the arm along that middle from Speed's knob centre to Stay's (one column on: --w-col + --gap-col), one box's three
   borders so every corner is painted once; then ONE STEM from just under the arm down into All's top at the S plate's
   centre, meeting both end to end (two translucent lines never overlap). Same ink, same rung, same 1 px. */
.slave {
    height: var(--h-row);
    display: flex;
    align-items: center;
    position: relative;
    padding-left: calc(var(--w-lab) + var(--w-knob) + var(--u));
}
.slave.tied::before {
    content: '';
    position: absolute;
    pointer-events: none;
    left: calc(var(--w-lab) + var(--w-knob) / 2);
    top: calc(var(--gap-ctl) * -1 - var(--h-row) / 2 + 15px);
    height: calc(var(--h-row) / 2 - 15px + var(--gap-ctl) / 2);
    width: calc(var(--w-col) + var(--gap-col) - 1px);
    border-left: 1px solid rgba(var(--ink), var(--a-bracket));
    border-right: 1px solid rgba(var(--ink), var(--a-bracket));
    border-bottom: 1px solid rgba(var(--ink), var(--a-bracket));
}
/* the stem: from the row under the arm down to All's top edge, at the S plate's centre (the plate stands on the row's centre line) */
.slave.tied::after {
    content: '';
    position: absolute;
    pointer-events: none;
    left: calc(var(--w-lab) + var(--w-knob) + var(--u) + var(--t-s) / 2);
    top: calc(var(--gap-ctl) / -2 + 1px);
    height: calc(var(--gap-ctl) / 2 - 1px + (var(--h-row) - var(--h-plate)) / 2);
    width: 0;
    border-left: 1px solid rgba(var(--ink), var(--a-bracket));
}

/* ── THE 8 x 8 STEP GRID (lane P3b) ─────────────────────────────────────────────────────────── */
/* rico 2026-09-21 23:45: "8x8 sequencers are 64 step sequencers with 8x8 cells."
   THE ARITHMETIC, stated rather than eyeballed: 8 cells of 3u with the ONE label gap (1u) between
   them is 8 x 12 + 7 x 4 = 124 px = 31u. ONE SIZE FOR THE ONE CONTROL, and now that a card stands on
   the main grid's own 34u columns it is the SAME column in both of its homes: flush on the left,
   3u over, which is the control gap. The cells themselves are painted in the canvas at the one
   radius and the table's alphas, like every plate; there is nothing to draw here but the box it
   stands in. */
.stepgrid {
    width: calc(var(--u) * 31);
    height: calc(var(--u) * 31);
    cursor: ns-resize;
}

/* A seq card's own sub-grid: the card's three columns, so the 8 x 8 grid and the three rows beside it
   stand on the same raster as every other row of the rack. It lives only inside a rack, so since lane
   P60 (K-7) its column gap is the rack's own 3u -- at 6u it would stand 24 px past the indented rack. */
.card-grid {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(3, var(--w-col));
    column-gap: var(--u);                  /* lane IN-1: the rack's own 1u (`.card-open`) */
    row-gap: var(--gap-ctl);
    align-items: start;
}
.stepgrid canvas { display: block; width: 100%; height: 100%; }

/* ── THE CV SEQ's BARS (lane P35) ───────────────────────────────────────────────────────────── */
/* The step grid's own box, to the pixel -- 31u square, column 1 over rows 1-4 -- so CV SEQ stands on
   STEP8X8's raster. The bars are painted in the canvas at the one radius and the table's alphas; the
   pointer is a crosshair because a stroke here DRAWS (a height), where the grid's cells are dragged. */
.seqbars {
    width: calc(var(--u) * 31);
    height: calc(var(--u) * 31);
    cursor: crosshair;
}
.seqbars canvas { display: block; width: 100%; height: 100%; }

/* ── THE PIANO ROLL'S EDITOR (lane ROLL-B, plan P96 + its AMENDMENT) ───────────────────────────────── */
/* Across the whole `.cols` width (3 x 39u + 2 x 2u = 121u = 484 px): a 9u key gutter, then the raster -- 112u, so
   4 bars = 28 px a beat = 7 px per 1/16. 48 rows (4 octaves) on Step8x8's own 4u pitch, the plate 3u: 47 x 4u + 3u =
   191u. Painted in the canvas at the one radius and the table's alphas (makeRollGrid); the box is only its seat.
   The lane under it (Vel | Ratchet) is 20u -- makeSeqBars' bars, on the raster's own x. */
/* lane ROLL-UX (rico 2026-10-08: "the piano roll is too high."): the raster shows ROLL_VIEW_ROWS = 24 rows of 4u (forteria-app.js),
   95u = 380 px -- the other rows are scrolled to (it was 48 rows, 191u = 764 px) */
.rollgrid { grid-column: 1 / -1; height: calc(var(--u) * 95); cursor: crosshair; }
.rollgrid canvas { display: block; width: 100%; height: 100%; }
.rolllane { grid-column: 1 / -1; height: calc(var(--u) * 20); cursor: ns-resize; }
.rolllane canvas { display: block; width: 100%; height: 100%; }
/* the tool row: plates of the house size on the row's own centre line, a 1u gap like every plate row */
.rolltools { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--u); height: var(--h-row); }
.rolltools > .sp { flex: 1 1 auto; }
/* lane PROGS: the Compose row's readout -- what was written last, dim, cut with an ellipsis (the full line is its title) */
.rollparts { flex-wrap: wrap; height: auto; min-height: var(--h-row); row-gap: var(--u); }   /* lane ROLL-UX: parts | chain | Copy Paste Others */
.rollparts > .rp-chain { flex: 0 1 var(--t-l); width: var(--t-l); min-width: calc(var(--u) * 12); }
.rolltools > .prog { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dim); }
/* lane HARMONY: the harmony row's "Follow" -- a label between plates, in the panel's own label ink */
.rollharm > .lab { color: var(--dim); padding-left: var(--u); }

/* ── THE BURST'S STRIP (lane P37) ───────────────────────────────────────────────────────────── */
/* The next burst's schedule, full width across the section's three columns and TWO ROWS of the row
   grid tall -- 8u + 3u + 8u = 19u, the timeline strip's own height -- so a train of 32 hits still
   reads as strokes. Painted in the canvas at the one radius and the table's alphas; it takes no press. */
.burststrip { height: calc(var(--u) * 19); }
.burststrip canvas { display: block; width: 100%; height: 100%; }

/* ── THE GLITCH'S STRIP (lane GLITCH) ───────────────────────────────────────────────────────────── */
/* The glitch effector's looper page: its recording over two bars, the slice it repeats, its repeat cells, the next
   glitch and the playhead -- full width under its knobs, the burst strip's own 19u, painted in the canvas. */
.glitchstrip { height: calc(var(--u) * 19); }
.glitchstrip canvas { display: block; width: 100%; height: 100%; }
/* LANE DRUMMER: the Drummer card's map pad and its 8 x 32 strip, the glitch strip's plate */
.drummap { height: calc(var(--u) * 24); }
.drumstrip { height: calc(var(--u) * 30); }
.drummap canvas, .drumstrip canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: default; }
/* LANE DRUM-MUTE: the kit's seven mute plates under the strip -- BAR plates (equal shares, no tier), lit = the drum plays */
.drummutes { grid-column: 1 / -1; display: flex; gap: var(--u); }
.drummutes > .plate { flex: 1 1 0; min-width: 0; padding: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── THE SAMPLER'S STRIP (lane SAMPLER) ─────────────────────────────────────────────────────────── */
/* The sampler's file, its Start / End handles (a press drags the nearer) and the live playhead -- full width under its
   choice rows, the burst strip's own 19u, painted in the canvas; the one strip here that takes a press. */
.smpstrip { height: calc(var(--u) * 19); margin: var(--u) 0; }
.smpstrip canvas { display: block; width: 100%; height: 100%; cursor: ew-resize; touch-action: none; }
/* LANE SAMPLER-2: the Time row's [?] (the card [?]'s inks) and its four lines across the card, the readout under the strip */
.smphelp { color: var(--dim); cursor: pointer; margin-left: var(--u); }
.smphelp.on, .smphelp:hover { color: var(--text); }
.smphelptext { grid-column: 1 / -1; color: var(--dim); white-space: normal; line-height: 1.4; }
.smphelptext[hidden] { display: none; }
.smphelptext > .on { color: var(--text); }
.smpinfo { color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0 0 var(--u); }

/* ── THE ARP'S STRIP (lane P36) ─────────────────────────────────────────────────────────────── */
/* Sixteen cells in two rows of eight, full width across the section's three columns and the burst strip's
   own 19u tall (two rows of the row grid) -- so a cell is 9u high, room for a gate's width and a ratchet's
   bars. Painted in the canvas at the one radius and the table's alphas; a click cycles, a drag sets. */
.arpstrip { height: calc(var(--u) * 19); cursor: ns-resize; }
.arpstrip canvas { display: block; width: 100%; height: 100%; }

/* (THE ANALOG ADSR'S CURVE, `.adsrcurve` (lanes P26 / V), stood here until lane IN-1 took both floating
   envelope drawings out of the panel -- rico 2026-10-02, "remove the envelope draw, it sits nowhere". The
   rack's ADSR input lane still draws the house diagram, in its own `.modscope` strip.) */

/* ── THE RHYTHM FLOW ROW (lane IN-1, I-5; the mockup's `.flow`) ─────────────────────────────────────
   Four plates of one width across the second and third columns, a dim › between each, on the knob row's
   own centre line beside `Clock`. A plate is its stage's ON-DOT plus its name; the lit one is whose
   editor stands below. */
.flow {
    display: grid;
    grid-template-columns: minmax(0, 1fr) calc(var(--u) * 2.5) minmax(0, 1fr) calc(var(--u) * 2.5) minmax(0, 1fr) calc(var(--u) * 2.5) minmax(0, 1fr);
    align-items: center;
    height: var(--h-row);
}
.flow .arrow { color: var(--dim); justify-self: center; }
.flow .plate { padding: 0 var(--u); gap: var(--u); min-width: 0; }
/* LANE ROW-LOOK: a stage plate's dot + name stand CENTRED on it as one block, like every caption (they started at its
   left inset: Burst 4.8 px, Arp 8.8 px off centre). The four stages keep their equal shares: at their words' tier (84)
   four stages and three arrows need 366 px where the two columns give 320 (ASSUMED READING ROWLOOK-FLOW). */
.flow .plate .nm { flex: 0 0 auto; }
/* MODE'S BAR WITH THE WAVEGUIDE'S SIGN (lane IN-1, I-3): Modal | Waveguide, a 3u gap, then + | − -- since LANE
   ROW-LOOK two tier GROUPS in the one flex bar (`.ctl.choice .pair > .gap`, above): the words share theirs, the signs theirs */
/* Take's trailing "last E3" takes no column while it says nothing */
.ctl.choice.tail > .val:empty { display: none; }
body.ctl-knobs .ctl.choice.tail:has(> .val:empty) { grid-template-columns: var(--w-lab) minmax(0, 1fr); }
/* a header's plate (CV seq's Randomize) stands in the 24 px head like any plate */
.foldhead > .plate { flex: 0 0 auto; }

/* ── C-4's SCOPE STRIP (lane MOD-C, P85 M-4): this modulator's own OUTPUT, then its kind-specific
   INPUT lane, directly under the Kind row -- full width of the card, like the burst/arp strips
   above, half their height each (ASSUMED: 8u, the smallest that still reads a trace + a tick). ── */
.card-scopes { grid-column: 1 / -1; display: flex; flex-direction: column; gap: calc(var(--u) / 2); }
.card-scopes canvas.modscope { display: block; width: 100%; height: calc(var(--u) * 8); }
.card-scopes.off { display: none; }    /* the modulator is off: nothing plays, nothing to show */

/* ── THE MODULATION RACK (lane P3c-3) ────────────────────────────────────────────────────────── */
/* One modulator is separated from the next by ONE ROW OF AIR and nothing else -- no box, no outline,
   no divider, which is the panel law one level smaller and the same law the card itself obeys under
   its parameter. It spans the card's three columns so the gap is the full width of the block it
   separates, and it is exactly a row high, so the raster never moves by half a step.

   LANE P5a, Y-4 (c): THIS IS WHAT THAT AIR IS, MEASURED RATHER THAN READ OFF A PICTURE. A shot of
   lane P10's panel showed "roughly 65 px" of unexplained space between the first modulator's
   Polarity row and the `Mod 2` header. `drive.js 1 <port> fold` measures it in the live panel:
       Polarity bottom -> Mod 2 header top = 56 px
         = one .rack-gap (--h-row, 32 px) + the two --gap-ctl row gaps around it (12 + 12)
   So NOTHING IS UNACCOUNTED FOR and nothing is closed: the 56 px IS the one row of air lane P3c-3
   ruled, plus the grid's own gap on each side of it. There is exactly ONE .rack-gap in a two-block
   rack -- below the LAST block there is none, so the air above the `Add modulator` plate measures
   12 px, one bare --gap-ctl, in the same run. 56 above, 12 below: the picture that read as "half"
   is a gap that exists and a gap that does not. */
.rack-gap {
    grid-column: 1 / -1;
    height: var(--h-row);
}

/* THE DIM 'RANGE' TAG on a migrated card: a value row like any other, at the table's disabled tone,
   whose value reads "convert". It is a readout that happens to take a press -- no new size, no
   outline, no second kind of object. */
.ctl.readout.tag { cursor: pointer; }
.ctl.readout.tag .lab, .ctl.readout.tag .val { color: var(--off); }
.ctl.readout.tag:hover .val { color: var(--text); }

/* ── THE POOL LIST, AND EVERY OTHER LIST OF NAMED ROWS ──────────────────────────────────────── */
/* `.rowlist` is the same shape under a name that does not say "samples": lane P9b's PROBES section
   is a list of a device's probes, drawn exactly like the pool because it IS the same thing -- a
   clickable named row with one dim value on its right. The rule is SHARED rather than copied, so
   the row height, the hover plate and the two text lanes can never drift apart between them. */
.pool, .rowlist { display: flex; flex-direction: column; gap: var(--gap-label); }
.pool .slot, .rowlist .slot {
    height: var(--h-plate);
    display: flex;
    align-items: center;
    gap: var(--gap-ctl);
    padding: 0 var(--gap-label);
    border-radius: var(--r);
    color: var(--dim);
    cursor: pointer;
}
.pool .slot:hover, .rowlist .slot:hover { background: var(--plate); }
.pool .slot.cur, .rowlist .slot.cur { color: var(--text); }
.pool .slot svg, .rowlist .slot svg { display: block; flex: 0 0 auto; }
.pool .slot .nm, .rowlist .slot .nm { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE FILE'S TEMPO, right-aligned on the row's own right edge: a value row is label-left and
   value-right, and a pool row is a value row whose label happens to be a file name. It stays DIM
   while the current slot's row goes to full ink, so it reads behind the name it belongs to. */
.pool .slot .bpm, .rowlist .slot .bpm { flex: 0 0 auto; text-align: right; color: var(--dim); }
/* LANE POOL: a browser row dragged over a slot -- the slot it would land in is lit like a hovered row */
.pool .slot.drop { background: var(--plate-hi); color: var(--text); }
/* ── THE SLOT STRIP (lane POOL-STRIP): the eight slots as TILES in one row, 2 x 4 on a phone ──────────────────────────
   rico 2026-10-08: "I am not sure if its cool to list the samples twice." A tile: its number and a mini waveform on top,
   the short name beneath (the part that gives way); an empty tile is its number alone, in the disabled tone. The
   current slot wears the lit plate; a slot a sounding grain reads right now flashes a hairline ring in full ink. */
/* LANE DRUMS-VOICE: THE KIT TABLE'S FOUR COLUMNS (forteria-app.js drmRows) -- the drum's Level row a whole column (39u), then
   Tune / Decay / Reverb as compact cells of 25u, named by their captions: no NAME track, the knob (9u), 1u, the value (15u).
   39 + 3 x 25 + 3 x 2u gaps = 120u, inside the three columns' 121u. */
.cols.drmkit { grid-template-columns: var(--w-col) repeat(3, calc(var(--u) * 25)); }
body.ctl-knobs .drmkit .drmcell .ctl { grid-template-columns: 0 var(--w-knob) var(--u) minmax(0, 1fr); }
body.ctl-knobs .drmkit .drmcell .ctl .lab { display: none; }
/* LANE SINGER: THE LYRICS EDITOR (forteria-app.js lyricsEditor) -- the browser's own multi-line editor in the house field's
   look (`.fld`: sunk, no border, the hairline ring on focus), full card width at every size (390 px phones included), five
   rows tall, vertical resize only, its own scrollbar for long text; under it the count (dim) and the POC's SYLLABLE LINE:
   each note onset · NUCLEUS (bold) · coda, the sung one lit (the lit plate), the next one dashed. */
.lyrics { display: flex; flex-direction: column; gap: var(--u); margin: var(--u) 0 calc(var(--u) * 2); min-width: 0; }
.lyrics-ta { display: block; width: 100%; box-sizing: border-box; min-height: calc(var(--u) * 16); max-height: 60vh;
             padding: calc(var(--u) * 1.5) calc(var(--u) * 2); border: none; outline: none; resize: vertical; overflow: auto;
             background: var(--bg); border-radius: var(--r); color: var(--text); font-family: inherit;
             font-size: var(--fs-label); line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 4;
             caret-color: var(--lit); }
.lyrics-ta::placeholder { color: var(--off); }
.lyrics-ta:focus { box-shadow: inset 0 0 0 1px var(--g-hair); }
.lyrics-meta { display: flex; justify-content: flex-end; color: var(--dim); font-size: var(--fs-label); }
.lyrics-line { display: flex; flex-wrap: wrap; gap: var(--u) calc(var(--u) * 2); font-size: var(--fs-label); color: var(--dim);
               min-width: 0; line-height: 1.6; }
.lyrics-line .word { display: inline-flex; gap: 2px; }
.lyrics-line .chip { display: inline-flex; gap: 2px; padding: 0 var(--u); border-radius: var(--r); border: 1px dashed transparent; }
.lyrics-line .chip .nuc { font-weight: 600; color: var(--text); }
.lyrics-line .chip span:empty { display: none; }
.lyrics-line .chip.lit { background: var(--lit); color: var(--bg); }
.lyrics-line .chip.lit .nuc { color: var(--bg); }
.lyrics-line .chip.next { border-color: var(--dim); }
.lyrics-empty { color: var(--off); }
.lyrics-line.stale { opacity: 0.5; }        /* the line waits for the editor to let go (forteria-app.js lyricsCount) */
.pool.slotstrip { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: var(--gap-label); }
.pool.slotstrip .slot {
    height: calc(var(--u) * 11);
    display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto;
    column-gap: var(--u); row-gap: 0; align-items: center;
    padding: var(--u); min-width: 0;
    font-size: var(--fs-label);
    box-shadow: inset 0 0 0 1px var(--plate);
}
.pool.slotstrip .slot .no { grid-column: 1; grid-row: 1; color: var(--dim); }
.pool.slotstrip .slot .st-wave { grid-column: 2; grid-row: 1; width: 100%; height: calc(var(--u) * 4); min-width: 0; display: block; }
.pool.slotstrip .slot .nm { grid-column: 1 / -1; grid-row: 2; min-width: 0; }
.pool.slotstrip .slot.empty .no { color: var(--off); }
.pool.slotstrip .slot.empty .st-wave { visibility: hidden; }
.pool.slotstrip .slot.cur { background: var(--plate-hi); color: var(--text); }
.pool.slotstrip .slot.cur .no { color: var(--text); }
.pool.slotstrip .slot.hit { box-shadow: inset 0 0 0 1px rgb(var(--ink)); }
html.phone .pool.slotstrip { grid-template-columns: repeat(4, minmax(0, 1fr)); }
html.phone .pool.slotstrip .slot { height: calc(52px * var(--px-screen, 1)); }
/* ── THE SAMPLE BROWSER (lane POOL, POOL-C): this browser's files under the device's eight slots ──────────────────────
   A bar of plates (+ Sample, + Folder, the die, the star filter, the sort) and the find field, which WRAPS on a narrow
   panel instead of scrolling sideways; a status line; then one row per file: star, prelisten, thumbnail, name (the one
   part that gives way), tempo, length, the slot plate, remove. The rows are the pool list's own shape (its height, its
   hover plate, the dim value lanes); a file the world names is in full ink, one it does not in the dim, a missing one
   in the disabled tone. */
.poolbrowser { display: flex; flex-direction: column; gap: var(--gap-label); margin-top: var(--gap-ctl); border-radius: var(--r); }
.poolbrowser.over { box-shadow: inset 0 0 0 1px var(--g-hair); }
.pb-bar { display: flex; flex-wrap: wrap; gap: var(--gap-label); align-items: center; }
.pb-bar .pb-find { flex: 1 1 calc(var(--u) * 20); }
.pb-status { color: var(--dim); font-size: var(--fs-label); min-height: var(--h-plate); display: flex; align-items: center;
             overflow-wrap: anywhere; }
.pb-list { display: flex; flex-direction: column; gap: var(--gap-label); }
.pb-empty { color: var(--off); font-size: var(--fs-label); padding: var(--gap-label); }
.pb-row { height: var(--h-plate); display: flex; align-items: center; gap: var(--gap-label); padding: 0 var(--gap-label);
          border-radius: var(--r); color: var(--dim); font-size: var(--fs-label); min-width: 0; }
.pb-row:hover { background: var(--plate); }
.pb-row.inw { color: var(--text); }
.pb-row.cur .pb-nm { text-decoration: underline; text-underline-offset: 3px; }
.pb-row.missing { color: var(--off); }
.pb-row .pbtn { flex: 0 0 auto; padding: 0; width: var(--h-plate); }
.pb-row .pb-use { width: auto; padding: 0 var(--gap-label); }
.pb-bar .pbtn { padding: 0 var(--gap-ctl); }
.pbtn.pb-fav { color: var(--off); }
.pbtn.pb-fav.on, .pbtn.pb-star.on { color: var(--text); background: var(--plate-hi); }
.pbtn.pb-fav.off { pointer-events: none; opacity: 0.4; }
.pbtn.pb-play.on { background: var(--plate-hi); }
.pb-thumb { flex: 0 0 auto; width: calc(var(--u) * 10); height: calc(var(--u) * 4); display: block; }
.pb-nm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pb-bpm, .pb-len { flex: 0 0 auto; color: var(--dim); text-align: right; }
/* lane POOL-STRIP: the slots a file fills on the selected device (`2 4 5`); a file in none of them reads dimmed; the row a
   slot tile's tap found wears the hover plate */
.pb-slots { flex: 0 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; color: var(--text); font-variant-numeric: tabular-nums; }
.pb-row.noslot { opacity: 0.6; }
.pb-row.sel { background: var(--plate); }
.pb-len { min-width: calc(var(--u) * 9); }
/* on a phone every press here is a 44 SCREEN px target (lane MOBILE-2's measure); the thumbnail gives way first */
html.phone .pb-row { height: calc(44px * var(--px-screen, 1)); }
html.phone .pb-row .pbtn, html.phone .pb-bar .pbtn { height: calc(44px * var(--px-screen, 1)); min-width: calc(44px * var(--px-screen, 1)); }
html.phone .pb-bar .pb-find { height: calc(44px * var(--px-screen, 1)); }
@media (max-width: 420px) { .pb-thumb, .pb-bpm { display: none; } }
/* LANE POOL-REC: the Pool's two tabs and the Record panel (forteria-app.js "THE POOL RECORDS") */
.pb-tabs .pbtn.on { background: var(--plate-hi); color: var(--text); }
.pr-box { display: flex; flex-direction: column; gap: var(--gap-label); }
.pr-box .fld { flex: 1 1 calc(var(--u) * 20); min-width: 0; max-width: 100%; }
.pr-hint { color: var(--dim); font-size: var(--fs-label); line-height: 1.35; }
.pr-meter { flex: 1 1 calc(var(--u) * 16); min-width: calc(var(--u) * 8); height: calc(var(--u) * 2); background: var(--plate);
            border-radius: var(--r); overflow: hidden; }
.pr-lvl { height: 100%; width: 0; background: var(--text); }
.pr-time { flex: 0 0 auto; color: var(--dim); font-variant-numeric: tabular-nums; }
.pbtn.pr-go.on, .pbtn.pr-trim.on, .pbtn.pr-norm.on { background: var(--plate-hi); color: var(--text); }
.pbtn.pr-go.off { pointer-events: none; opacity: 0.4; }
html.phone .pr-box .fld { height: calc(44px * var(--px-screen, 1)); }
/* ── THE DOOR'S READOUT (lane IN-2p): one line per ear in the MIDI card ─────────────────────────────
   The same clickable named row as the PROBES list (its height, its hover plate), laid on the KNOB ROW'S
   OWN COLUMNS like a choice row (lane IN-1's one label column): the live dot centred in the knob's 8u
   seat, the ear's NAME in the 14u label box (the text ink -- it is the link), what it delivers in the
   bar's place (dim), and the emitters that reach it on the right edge (dim).
   LANE ROWLAW (P97 D8-3): ON THE ROW TRACKS -- the ear's NAME in the name track, its live dot in the MOD track
   (where a parameter's opener stands: the thing that says "something arrives here"), what it delivers from the
   knob track on, the reach on the right edge. Placed by track, not by DOM order.
   LANE ROW-LOOK: the MOD track is gone -- the live dot stands centred in the KNOB track (where a knob's mod ring would
   say "something arrives here"), what it delivers from the VALUE track on (ASSUMED READING ROWLOOK-DOOR). */
.rowlist.door .slot.ear {
    display: grid;
    grid-template-columns: var(--w-lab) var(--w-knob) var(--u) minmax(0, 1fr) max-content;
    column-gap: 0;
    padding: 0;
}
.rowlist.door .slot.ear > .en { grid-column: 1; grid-row: 1; }
.rowlist.door .slot.ear > .ldot { grid-column: 2; grid-row: 1; justify-self: center; }
.rowlist.door .slot.ear > .tk { grid-column: 4; grid-row: 1; }
.rowlist.door .slot.ear > .bpm { grid-column: 5; grid-row: 1; }
.rowlist.door .slot.ear .en { color: var(--text); white-space: nowrap; }
.rowlist.door .slot.ear .tk { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rowlist.door .slot.ear .bpm { padding-right: var(--gap-label); }
/* the LIVE DOT: the header dot's 6 px, in its sounder's colour (lane LAYERS-RETIRE: set inline, `thingInk`), its
   opacity set per frame by the ear's own arrivals; a muted ear (a stored `none`) is a ring, the on-dot's "off" */
.ldot { justify-self: center; width: 6px; height: 6px; border-radius: 50%; background: var(--text); }
.ldot.mute { background: none; box-shadow: inset 0 0 0 1.25px var(--dim); }
/* a dim note on the label column: "no ear ...", the device's parked "keys + clock ticks: ..." */
.door-note { color: var(--dim); height: var(--h-plate); display: flex; align-items: center;
             padding-left: 0; white-space: nowrap; }       /* lane ROWLAW: the label column starts on the card's edge now */
/* WHY IS IT SILENT (lane IN-2p): one dim line directly under the device's name (the title row's own x),
   pulled up into the title row's air so it reads as the name's caption, not as a card */
#inspector > .why { flex: 0 0 auto; color: var(--dim); margin-top: calc(var(--u) * -2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── THE INTER SECTION ──────────────────────────────────────────────────────────────────────── */
.cards { display: grid; grid-template-columns: repeat(4, var(--w-card)); gap: var(--gap-group); }
.card { display: flex; flex-direction: column; gap: var(--gap-ctl); }

/* ── THE ALIAS ROW ──────────────────────────────────────────────────────────────────────────── */
/* rico: "we will need a row of avatar aliases, just to mute them. maybe solo too." The row (rows, since
   MIXER-FIT) on the raster between the timeline and the space. M and S are EXACTLY the one plate size -- square, no
   padding of their own -- so the header's plate and these are visibly the same object. */
/* LANE MIXER-FIT (rico 2026-10-05: "many devices bring a scroll bar which clips the mixer further. no scrollbar. rather stack
   the channels then."): THE ROW NEVER SCROLLS -- it is a GRID OF EQUAL CHANNEL CELLS that wraps into stacked rows (ASSUMED
   MIX-GRID: one cell width, so the columns line up row under row), each row 10u, and #main (flex 1, min-height 0) gives up
   the height the extra rows take -- the page never scrolls, the space canvas shrinks. The spare width of a row is spread
   BETWEEN the columns (space-between), so the last column ends on the page margin like the inspector does.
   ONE CELL, ONE PARTS LAW (F2; ASSUMED MIX-RET): crust 7u | name 10u | knob 8u | M | S, 1u between = 41u (164 px), so eight
   stand in a 1440 px row (ASSUMED MIX-CELL: the name track is the narrowest that holds "Reverb" whole -- 9u cut it). A RETURN
   strip is the same cell: its crust track stays empty (the separator's air), name | Return knob | M on the device's x, and
   the meter -- a 2u bar -- in the S track, beside M. (It was its name + knob + M + meter + 2 x 6u of air + a hairline:
   137 px plus a 24 px margin against a device's 105.)
   ASSUMED MIX-RETEND (SUPERSEDED by MIXER-PACK, below): the returns stood pinned in the LAST TWO COLUMNS (lines -3 / -2 / -1).
   LANE MIXER-PACK (rico 2026-10-09: "the mixer channels take up too much vertical space. they should bunch up. sometimes on the
   phone, a channel is in the second row, even though there was enough space in the first row. reverb channel has an awkward
   frame, remove it."):
   * BUNCHED: a row is the crust's own 7u (28 px, was 10u), no row gap -- a channel's tallest part IS the row; the Level knob
     7u (was 8u: ASSUMED MIX-KNOB7 -- flip: 6u, the plate's height, the cell 1u narrower), the return meter with it; the row
     stands 2u under the timeline (was 3u). M and S stay the one plate. The cell is 40u (160 px, was 41u), the columns at
     least 2u apart (was 3u; space-between spreads the rest, so a desk's gaps stay 18 .. 22 px at 1100 .. 1440 px).
   * THE PHONE'S PHANTOM SLOT: the phone's row is 496 px (528 - 2 x 16) and the old 164 px cell made auto-fill count TWO columns
     (three need 3 x 164 + 2 x 12 = 516 px) -- and space-between then spread the 168 px left over into ONE gap between them,
     wider than a channel: the row read as having room, and the third channel stood in the second row. 3 x 160 + 2 x 8 = 496 px:
     three columns now, exactly. And the returns pinned to the last two columns left a hole on the last device row whenever the device
     count fell short of it (on the phone's two columns: every odd count). The returns now FLOW right after the last device
     (forteria-app.js syncMixer keeps the pair together: a Reverb that would take a row's last cell starts the next row).
     ASSUMED MIX-FLOW: the returns follow the devices, on the same row when two cells are left -- flip: the old pin (lines -3 / -2 /
     -1), returns always at the right end.
   LANE MIXER-STRIP (rico 2026-10-17: "the mixer is very unintuitive, we cannot make out the channels. we have to bundle the
   channels better. I would put the name above the control-combo, but that would break our design language, unless we do that
   for every knob. but its the only way I see. G4 is naturally much shorter than Synth or Drums and will therefore visually
   belog to the previous channel. we love the little icons. they should not show their probes. just the bare icons."):
   A CHANNEL IS A STRIP -- ITS NAME ON TOP, ITS CONTROLS UNDER IT, ONE BLOCK.
   * THE HEAD: every strip's name -- instrument, return, Glue -- stands in a 4u HEAD track over its control combo, CENTRED over
     the span of its controls, cut with an ellipsis when it is wider than that span (the title says it whole). The name
     track in the row is GONE, so a short "G4" leaves no hole inside its strip and nothing of it reads as the tail of the
     strip before. Every part stands on its own TRACK (crust 1 | knob 2 | M 3 | S 4, the head over them),
     never by DOM order -- the row's DOM is the one test-presets L6-ALIAS compiles, unchanged.
     ASSUMED MS-HEAD: the name is a strip's HEAD -- the name of a group, not a knob's label -- so the inspector's label-left
     law (ROWLAW: name | knob | value) stays everywhere else. Flip: every knob in the app takes its label above it.
     ASSUMED MS-HEADCENTRE (rico 2026-10-17, on the shots: "the name should be center aligned."; it supersedes MS-HEADLEFT):
     the head is CENTRED over the controls the strip HAS -- an instrument's crust .. S, a return's knob .. meter (its crust
     track stays empty, MIX-AIR, so the head's box starts over the knob and ends at the meter's right edge), the Glue's knob ..
     meter -- so the name stands over the middle of what it names, not over the cell's empty air. Flip: left-aligned on the
     strip's first control (`text-align: start` on `.alias > .nm`; the head's box may stay as it is).
   * ONE BLOCK: inside a strip the parts stand 1u apart (the label gap) and the head sits ON its controls (no gap); strips stand
     at least 4u apart in a row and 2u row under row, so the least air between two strips is twice the most inside one.
     ASSUMED MS-AIR: 4u / 2u -- flip: a 3u row gap (every wrapped row 1u further down).
   * THE ARITHMETIC: crust 6u | knob 7u | M 6u | S 6u, 1u between = 28u = 112 px a strip (was 40u: the 10u name track, its gap
     and 1u of crust went); a row is the head 4u + the knob 7u = 11u = 44 px (was 7u), 2u between rows. THE PHONE: its row is 496 px
     (528 - 2 x 16) and 4 x 112 + 3 x 16 = 496 -- FOUR strips, exactly (was three), so space-between has nothing to spread into
     a phantom slot. THE DESK: a 1440 px page's row is 1408 px -- 11 strips (11 x 112 + 10 x 16 = 1392, the gaps 17.6 px); a
     1100 px page's 1068 -- 8 (1008, the gaps 24.6 px). With 16 instruments + Reverb, Delay and Glue (19 strips): 2 rows on
     the 1440 desk (96 px, was 3 rows = 84), 3 on the 1100 desk (148 px, was 4 = 112), 5 on the phone (252 px, was 7 = 196);
     with 5 instruments (8 strips): 1 row = 44 px on both desks (was 28 at 1440, 2 rows = 56 at 1100), 2 rows = 96 px on the
     phone (was 3 = 84).
   * THE CRUST BOX IS 6u AGAIN (ASSUMED MS-AV6, supersedes MIX-AV below): bare, the frame needs no room for stems, so the box
     goes back to the plate's 6u and the frame still GROWS -- 18.75 px corner to corner for every engine (the fit is the frame
     alone, forteria-app.js aliasUnit) against the ~16 px a Graveler drew at 7u beside its stems. Flip: 7u -- a strip of
     29u, and the phone's four then need a 2u column gap.
   * THE GLUE'S METER stands beside its knob (ASSUMED MS-GLUEMETER, supersedes MASTER-BUS's S-track seat): the Glue has no M,
     and its meter in the S track left a 7u hole INSIDE the strip, wider than the air between two strips. Flip: `grid-column:
     4` on `.alias.master .retmeter`. */
#aliases {
    --w-chcrust: calc(var(--u) * 6);                   /* the crust track (MIXER-STRIP: 6u, was 7u -- MS-AV6) */
    --w-chknob: calc(var(--u) * 7);                    /* the Level knob (MIXER-PACK: 7u, was 8u) */
    --h-chhead: calc(var(--u) * 4);                    /* MIXER-STRIP: the head track, the strip's name over its controls */
    --w-chmeter: calc(var(--u) * 2);                   /* a return's / the Glue's meter bar */
    --w-chan: calc(var(--w-chcrust) + var(--w-chknob) + var(--h-plate) * 2 + var(--gap-label) * 3);   /* = 28u */
    --h-chan: calc(var(--h-chhead) + var(--w-chknob)); /* = 11u */
    flex: 0 0 auto;
    min-height: var(--h-chan);                         /* one row even before the first frame fills it: the space never jumps */
    margin: calc(var(--u) * 2) var(--margin) 0 var(--margin);
    display: grid;
    grid-template-columns: repeat(auto-fill, var(--w-chan));
    grid-auto-rows: var(--h-chan);                     /* MIXER-STRIP: a row is one strip, its head over its controls */
    column-gap: calc(var(--u) * 4);                    /* MIXER-STRIP: the least air in a row (was 2u): the phone's 496 px holds four */
    row-gap: calc(var(--u) * 2);                       /* MIXER-STRIP: row under row (MIXER-PACK's rows touched) */
    justify-content: space-between;
}
.alias {
    display: grid;
    grid-template-columns: var(--w-chcrust) var(--w-chknob) var(--h-plate) var(--h-plate);   /* crust | knob | M | S */
    grid-template-rows: var(--h-chhead) var(--w-chknob);                                     /* the head / the controls */
    column-gap: var(--gap-label);
    align-items: center;
}
.alias > * { grid-row: 2; }                            /* MIXER-STRIP: every control in the controls row, each on its track */
.alias > .av { grid-column: 1; }
.alias > .mixknob { grid-column: 2; }
.alias > .plate { grid-column: 3; }                    /* M */
.alias > .plate + .plate { grid-column: 4; }           /* S */
.alias .av {
    width: var(--w-chcrust);
    height: var(--w-chcrust);
    display: block;
    cursor: pointer;
    /* the device's own CRUST at its far tier, painted into this canvas and FITTED to it (forteria-app.js aliasUnit: the
       frame, ALIAS_INSET clear of the edge -- BARE since MIXER-STRIP, its probes stay in the space). ASSUMED MIX-AV
       (SUPERSEDED by MS-AV6, above): 7u, up from the plate's 6u -- the frame had to share the box with its stems (a
       Graveler's ~16 px across at 7u, ~13 at 6u; it was ~22 with the stems cut off). It is already whitish line art on
       black, so there is nothing to invert. */
}
.alias > .nm { grid-row: 1; grid-column: 1 / -1; line-height: var(--h-chhead); text-align: center;   /* MIXER-STRIP: THE HEAD */
               color: var(--dim); cursor: pointer; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.alias.sel .nm { color: var(--text); }
.alias .plate { width: var(--h-plate); min-width: var(--h-plate); padding: 0; }
/* LANE SFX-3: the mixer's LEVEL knob (the inspector's own knob, 8u, no label rows) and the two RETURN strips at the row's end:
   name, Return knob, M, meter (no glyph: ASSUMED SFX3-NOGLYPH). The first strip WAS set off from the devices by a hairline on
   its left edge (an inset shadow, MIXER-FIT) -- rico 2026-10-09 "reverb channel has an awkward frame, remove it.": GONE
   (MIXER-PACK). ASSUMED MIX-AIR: "devices | sends" reads from SPACE alone -- a return's crust track stands empty, so its head
   and knob start a crust plus a gap further from the last device's S than the next device's crust would -- flip: the
   hairline back (`.alias.ret.first { box-shadow: inset 1px 0 0 var(--plate) }`). */
.alias .mixknob { width: var(--w-chknob); height: var(--w-chknob); display: block; cursor: ns-resize; }
/* a return's head spans its controls alone (MS-HEADCENTRE): from the knob (its crust track stays empty, MIX-AIR) to the
   meter's right edge -- the S track less the meter. The Glue's the same, its meter in the M track. */
.alias.ret > .nm { grid-column: 2 / -1; margin-right: calc(var(--h-plate) - var(--w-chmeter)); }
.alias.master > .nm { grid-column: 2 / 4; }
.alias.ret.sel .nm { color: var(--text); }
.alias .retmeter { grid-column: 4; width: var(--w-chmeter); height: var(--w-chknob); display: block; border-radius: 1px;
                   justify-self: start; }          /* a return's meter: the S track, beside M */
/* LANE MASTER-BUS: the Glue strip has no M -- its gain-reduction meter kept the returns' meter track (the S track) until
   MIXER-STRIP: it stands beside the knob now, in the M track (ASSUMED MS-GLUEMETER, above) */
.alias.master .retmeter { grid-column: 3; }
/* the device FX card's Open plates stand in the row of their knob, vertically centred in it, in the second column */
.fxopen { height: var(--h-row); display: flex; align-items: center; }

/* ── THE PRESET PANEL (lane P, PLAN P80's PR-1; lane PD; LANE P90 -- p90-dialog-world / -factory / -device) ── */
/* LANE P90: THE GRAPHITE LADDER (tone.md (a), lane IN-1's tokens) -- rico 2026-10-02: "its just all black in
   black." The panel (k 0) holds FOUR CARDS (k 1), each with one job: the strip (the document: what is loaded,
   Overwrite, Save as...), the rail (the collections), the matrix (the library), the selected preset (edited
   in place). Plates (k 2) stand on them, the ONE lit act is k 3; fields are SUNK one step (the ground's tone
   on the panel, the panel's inside a card). `#presetPanel` is the fixed backdrop; `.pp-card` the panel. */
#presetPanel {
    position: fixed;
    inset: 0;
    z-index: 15;
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--scrim);
    --text: var(--g-text); --dim: var(--g-lab); --off: var(--g-off);
    --plate: var(--g-ctl); --plate-hi: var(--g-ctl-hi); --lit: var(--g-act);
}
#presetPanel.open { display: flex; }
/* THE DEVICE DIALOG DOCKS BESIDE ITS INSPECTOR (RULED ~22:3x) -- placed by forteria-app.js `presetDock`; the
   inspector is lifted over the scrim so the device it loads into stays in view, lit. */
#presetPanel.dock { align-items: flex-start; justify-content: flex-start; }
#presetPanel.dock .pp-card { position: absolute; max-width: none; max-height: none; }
body.pp-docked #inspector { position: relative; z-index: 16; }
.pp-card {
    /* LANE PRESET-ROOM (rico 2026-10-12: "maybe the preset dialog should be bigger?"): 220 x 150u (880 x 600) -> 260 x 190u
       (1040 x 760), still never past 94vw / 90vh. ASSUMED READING PR-SIZE -- flip: the old numbers. */
    width: calc(var(--u) * 260);
    max-width: 94vw;
    height: calc(var(--u) * 190);
    max-height: 90vh;
    box-sizing: border-box;
    background: var(--g-panel);
    color: var(--text);
    border-radius: var(--card-r);
    padding: calc(var(--u) * 4);
    display: flex;
    flex-direction: column;
    gap: var(--gap-ctl);
    /* LANE PRESET-FIT: the card is the query container of its own narrow layout (the `@container ppcard` block under
       THE PHONE), and it scrolls when that layout stacks taller than it -- at the desk's three columns nothing in it
       overflows (the body shrinks, min-height 0), so the scroll never shows there */
    container: ppcard / inline-size;
    overflow-y: auto;
}
#presetPanel .plate.off, #sheet .plate.off { background: var(--g-hair); }   /* greyed = flat: not pressable */
.pp-head, .pp-foot { flex: 0 0 auto; height: var(--h-plate); display: flex; align-items: center; gap: var(--gap-ctl); }
.pp-title { flex: 1 1 auto; font-size: var(--fs-title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pp-title .lab { font-size: var(--fs-title); }
.pp-search { flex: 0 1 calc(var(--u) * 50); min-width: calc(var(--u) * 24); }   /* PRESET-FIT: it gives way before the title is cut */
.pp-box { background: var(--g-card); border-radius: var(--card-r); padding: var(--card-pad); box-sizing: border-box; min-width: 0; min-height: 0; }
.pp-box .fld { background: var(--g-panel); }
.pp-strip .pair, .pp-foot .pair { flex: 0 0 auto; display: flex; gap: var(--gap-label); }
.ccap { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); white-space: nowrap; }

/* THE STRIP (48 px): the caption, the name, where it lives, its state -- then Save as... and Overwrite */
.pp-strip { flex: 0 0 calc(var(--u) * 12); display: flex; align-items: center; gap: var(--gap-ctl); padding-top: 0; padding-bottom: 0; }
.pp-strip .ccap { flex: 0 0 auto; min-width: calc(var(--u) * 24); }
.pp-strip .nm { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.pp-strip .nm.untitled { color: var(--dim); font-weight: 400; }
.pp-strip .lab { white-space: nowrap; }
.pp-strip .st { display: flex; align-items: center; gap: calc(var(--u) * 1.5); white-space: nowrap; }
.pp-strip .sp, .pp-mhead .sp { flex: 1 1 auto; }

/* THE BODY: the rail (44u), the matrix, the selected preset's card (60u) -- 2u apart */
.pp-body { flex: 1 1 auto; min-height: 0; display: flex; gap: calc(var(--u) * 2); }
.pp-rail { flex: 0 0 calc(var(--u) * 44); overflow-y: auto; display: flex; flex-direction: column; gap: var(--gap-label); }
.pp-rail::-webkit-scrollbar, .pp-matrix::-webkit-scrollbar, .pp-list::-webkit-scrollbar { width: 3px; }
.pp-rail::-webkit-scrollbar-thumb, .pp-matrix::-webkit-scrollbar-thumb, .pp-list::-webkit-scrollbar-thumb { background: var(--g-hair); border-radius: 2px; }
/* a rail row is ONE PLATE high -- the NOTED fix (style.css :873 before this lane: no flex-basis, so the
   column squeezed every row to 16 px against the 24 px plate) */
.pp-rail .item {
    flex: 0 0 auto;
    height: var(--h-plate);
    padding: 0 calc(var(--u) * 2);
    display: flex;
    align-items: center;
    gap: calc(var(--u) * 2);
    border-radius: var(--r);
    cursor: pointer;
    white-space: nowrap;
}
.pp-rail .item:hover { background: var(--g-hair); }
/* D2-2 (lane TIERS): rail rows on ONE x -- rico: "there are menues that are indented (eingerückt).
   thats disturbing." A category sub-row keeps the head row's own 2u inset (style.css `.pp-rail
   .item` above); the head is told by its caps + weight, never by indenting deeper (flip: 5u). */
.pp-rail .item.sub { padding-left: calc(var(--u) * 2); }
.pp-rail .item.head { font-size: 11px; letter-spacing: 0.1em; font-weight: 600; color: var(--dim); }
.pp-rail .item.on { background: var(--lit); color: var(--bg); font-weight: 600; }
.pp-rail .item.tags { cursor: default; }
.pp-rail .item.tags:hover { background: transparent; }
.pp-rail .item .nm { flex: 1 1 auto; text-align: left; overflow: hidden; text-overflow: ellipsis; }
.pp-rail .item .n { color: var(--dim); font-variant-numeric: tabular-nums; }
.pp-rail .item.on .n { color: var(--bg); }
.pp-rail .rgap { flex: 0 0 var(--u); height: var(--u); }
.pp-cloud { display: flex; flex-wrap: wrap; gap: var(--gap-label); padding-left: calc(var(--u) * 2); }

/* THE MATRIX CARD (RULED: Factory 2's column-per-category matrix, KEPT): its head names the pick and holds
   the Sort switch; the columns wrap down, never sideways (lane P F7). */
.pp-mid { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: calc(var(--u) * 2); }
.pp-mhead { flex: 0 0 auto; height: var(--h-plate); display: flex; align-items: center; gap: var(--gap-label); }
.pp-mhead .ccap { padding-left: calc(var(--u) * 2); }
.pp-mhead .lab { margin-right: var(--u); }
/* LANE PRESET-FIT (rico 2026-10-10: "buttons are being cut off" -- his shot: COMMUNITY's Favs | New | Name ran under the
   selected preset's card; the docked dialog's library card is 98u wide, the caption + Sort + three L plates ~105u): the
   head WRAPS -- "Sort" and its plates are one group (`.pp-sort`, forteria-app.js) that drops under the caption whole, at
   the card's left edge. ASSUMED READING PF-SORT: wrap the group, keep the L tier -- flip: shrink the plates (`flex: 0 1
   var(--t-l)`) and cut "Complexity". */
.pp-mhead { height: auto; min-height: var(--h-plate); flex-wrap: wrap; row-gap: var(--gap-label); }
.pp-sort { flex: 0 0 auto; display: flex; align-items: center; gap: var(--gap-label); }
/* LANE PRESET-SORT (rico 2026-10-12: "the sort buttons are a bit rough/big"): the switch is WORDS on the card, not filled
   L plates -- the graphite ladder's label ink (--g-lab) for a word at rest, the value ink (--g-text) under the hand and for
   the ACTIVE word, which is semibold and carries its direction arrow (`.dir`, forteria-app.js). No plate, no tier: each
   word is as wide as its letters, 5u high (a plate is 6u). The phone keeps the full plate height for a finger (below).
   ASSUMED READING PS-LOOK: words in the label ink, MENU-LOOK's "current = weight" law -- flip: a hairline underline. */
.pp-sort .plate.sk { width: auto; height: calc(var(--u) * 5); min-width: 0; padding: 0 var(--u); background: transparent; color: var(--g-lab); font-weight: 400; }
.pp-sort .plate.sk:hover { background: transparent; color: var(--g-text); }
.pp-sort .plate.sk.on { background: transparent; color: var(--g-text); font-weight: 600; }
html.phone .pp-sort .plate.sk { height: var(--h-plate); }
.pp-matrix { flex: 1 1 auto; min-height: 0; overflow-x: hidden; overflow-y: auto; display: grid;
             grid-template-columns: repeat(auto-fill, minmax(calc(var(--u) * 40), 1fr));
             column-gap: calc(var(--u) * 4); row-gap: calc(var(--u) * 4); align-items: start; align-content: start; }
.pp-col { min-width: 0; display: flex; flex-direction: column; gap: var(--gap-label); }
.pp-col .cap { height: calc(var(--u) * 4); display: flex; align-items: center; padding-left: calc(var(--u) * 2);
               font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); }
.pp-matrix .pp-row .cat, .pp-matrix .pp-row .tag { display: none; }
.pp-list { flex: 1 1 auto; min-width: 0; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--gap-label); }
/* LANE PRESET-TABS (rico 2026-10-13: "factory, user, community should be tabs, so we dont have to scroll so much, we must
   expect thousands of presets here, so it must be übersichtlich."): THE TABS over the body -- the house's plate switch
   (the open tab lit, k3), each its word and its count; on the phone the three share the width at a finger's height. */
.pp-tabs { flex: 0 0 auto; display: flex; gap: var(--gap-label); }
.pp-tab { flex: 0 0 calc(var(--u) * 34); min-width: 0; justify-content: space-between; padding: 0 calc(var(--u) * 2); }
.pp-tab .nm { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
.pp-tab .n { flex: 0 0 auto; color: var(--dim); font-weight: 400; font-variant-numeric: tabular-nums; }
.pp-tab.on .n { color: var(--bg); }
/* THE RAIL is three boxes now: the tab's sections and categories (they scroll on their own), the Favorites filter, the
   TAGS (a box of their own, at most ~ten chip lines, then it scrolls) -- the rail itself never scrolls */
.pp-rail { overflow: hidden; }
.pp-cats { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--gap-label);
            border-bottom: 1px solid var(--g-hair); padding-bottom: var(--gap-label); }
.pp-tagbox { flex: 0 1 auto; min-height: 0; max-height: calc(var(--u) * 52); overflow-y: auto; display: flex; flex-direction: column;
             gap: var(--gap-label); border-top: 1px solid var(--g-hair); padding-top: var(--gap-label); }
.pp-cats::-webkit-scrollbar, .pp-tagbox::-webkit-scrollbar { width: 3px; }
.pp-cats::-webkit-scrollbar-thumb, .pp-tagbox::-webkit-scrollbar-thumb { background: var(--g-hair); border-radius: 2px; }
.pp-rail .item.pp-favf { flex: 0 0 auto; }
/* THE LIST DRAWS ONLY WHAT IS SEEN (forteria-app.js `presetVList`): one pitch for every entry (a plate and the label gap,
   read off `.pp-vprobe`), the entries in view stand absolutely in a pad as tall as all of them. A caption row is the old
   matrix column's caption; under captions the row needs no category (the search's list shows it). */
.pp-vlist { display: block; position: relative; }
.pp-vprobe { height: calc(var(--h-plate) + var(--gap-label)); }
.pp-vpad { position: relative; width: 100%; }
.pp-vpad > .pp-row, .pp-vpad > .cap { position: absolute; left: 0; right: 0; box-sizing: border-box; }
.pp-vpad > .cap { height: var(--h-plate); display: flex; align-items: flex-end; padding: 0 0 var(--u) calc(var(--u) * 2);
                  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); white-space: nowrap; overflow: hidden; }
.pp-vlist:not(.found) .pp-row .cat { display: none; }

/* A ROW: the loaded / sounding mark, the complexity glyph, the name (category + tags in the search list),
   the favourite's star -- one plate high, no plate of its own; the selected row is the lit tone. */
.pp-row {
    flex: 0 0 auto;
    height: var(--h-plate);
    padding: 0 calc(var(--u) * 2);
    display: flex;
    align-items: center;
    gap: calc(var(--u) * 2);
    border-radius: var(--r);
    cursor: pointer;
    white-space: nowrap;
}
.pp-row:hover { background: var(--g-hair); }
.pp-row.sel { background: var(--lit); color: var(--bg); font-weight: 600; }
.pp-row.flash { background: var(--text); color: var(--bg); }              /* a save just wrote it: 2 x 120 ms */
.pp-row .mk, .pp-row .fav { flex: 0 0 calc(var(--u) * 3); width: calc(var(--u) * 3); text-align: center; color: var(--dim); font-weight: 400; }
.pp-row.sel .mk, .pp-row.sel .fav, .pp-row.flash .mk, .pp-row.flash .fav { color: var(--bg); }
/* LANE COMMUNITY: a shared row's heart carries its count (wider than the star's seat), lit when I faved it; the
   Driver ID box under the rail's COMMUNITY rows */
.pp-row .fav.cm { flex: 0 0 auto; width: auto; min-width: calc(var(--u) * 3); padding: 0 var(--u); cursor: pointer; font-variant-numeric: tabular-nums; }
.pp-row .fav.cm.on { color: var(--g-act); }
.pp-row.sel .fav.cm.on { color: var(--bg); }
.pp-comm { display: flex; flex-direction: column; gap: var(--gap-label); padding: var(--u) 0 0 calc(var(--u) * 2); }
.pp-comm .who { display: flex; align-items: center; gap: var(--gap-label); }
.pp-comm .who .nm { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
.pp-comm .fld { width: 100%; box-sizing: border-box; }
/* LANE PRESET-PRIVACY: my shared preset's mark in a row (a globe = public, a lock = private, the row's own ink), and
   the Public | Private switch on the save sheet and on my preset's card: one group of two L plates, icon before word */
.pp-row .vis { flex: 0 0 calc(var(--u) * 3); width: calc(var(--u) * 3); display: flex; align-items: center; justify-content: center; color: var(--dim); }
.pp-row .vis.priv { color: var(--text); }
.pp-row.sel .vis, .pp-row.flash .vis { color: var(--bg); }
.vis-sw { display: flex; gap: var(--gap-label); }
.vis-sw .plate { gap: var(--u); }
.vis-sw .plate .vis-ic { flex: 0 0 auto; }
/* LANE PRESET-FIT: on the selected preset's card the value column is 38u -- two L plates and their gap are 43u, so Private
   ran 5u past the card (rico's shot). There the two SHARE the column (equal halves, 18.5u each: icon + "Private" is
   ~15u), never wider than their L tier; the save sheet's 86u column keeps its L tiers. ASSUMED READING PF-VIS -- flip:
   the switch on a row of its own under the label, full card width. */
.pp-form .vis-sw .plate.t-l { flex: 1 1 0; width: auto; min-width: 0; max-width: var(--t-l); }   /* never wider than L (the phone's wide column) */
/* LANE MARKET-1: a Marketplace row's price (or Owned) before its heart, in the dim ink like the heart's count; the card's
   Sell column (the price field over its XL plate, never wider than the card's 38u value column) and a bought preset's note */
.pp-row .price { flex: 0 0 auto; color: var(--dim); font-variant-numeric: tabular-nums; font-weight: 400; }
.pp-row.sel .price { color: var(--bg); }
.pp-sell .plate.t-xl { width: 100%; max-width: var(--t-xl); }
.pp-sell .ro { height: var(--h-plate); line-height: var(--h-plate); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-form > .pp-bought, .pp-sell .sh-hint { line-height: 1.4; padding-top: calc((var(--h-plate) - 1.4em) / 2); }
/* the upgrade sheet (forteria-app.js `upgradeBody`): each row a line of what it gives, its act at the row's end */
.up-row { display: flex; align-items: center; gap: var(--gap-label); min-width: 0; }
.up-row .sh-line { flex: 1 1 auto; }
.up-row .fld { flex: 1 1 auto; min-width: 0; }
.up-row .plate { flex: 0 0 auto; }
.pp-row .nm { flex: 1 1 auto; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; }
.pp-row .cat { flex: 0 0 calc(var(--u) * 18); color: var(--dim); overflow: hidden; text-overflow: ellipsis; }
.pp-row .tag { flex: 0 0 calc(var(--u) * 28); color: var(--dim); overflow: hidden; text-overflow: ellipsis; }
.pp-row.sel .cat, .pp-row.sel .tag { color: var(--bg); font-weight: 400; }
/* LANE P90: THE COMPLEXITY GLYPH -- N lines through one centre, a capital letter high (0.72 em), in the
   row's own ink; it stands BEFORE the name with the label gap (forteria-app.js `complexityGlyph`). */
svg.cx { flex: 0 0 auto; width: 0.72em; height: 0.72em; display: block; overflow: visible; }
svg.cx line { stroke: currentColor; stroke-width: 1.2; stroke-linecap: round; }
.pp-row svg.cx { margin-right: calc(var(--gap-label) - var(--u) * 2); }   /* the house gap: 1u to the name */

/* THE SELECTED PRESET'S CARD: the form (labels on one x, controls on one x), a hairline, the facts, then
   Favorite + Delete at its foot. A USER preset's fields are live; a FACTORY one's are text (read-only). */
.pp-detail { flex: 0 0 calc(var(--u) * 80); display: flex; flex-direction: column; gap: var(--gap-ctl); overflow: hidden; min-height: 0; }
/* LANE PRESET-ROOM: THREE ZONES -- the form (bounded: its tags box holds three lines and scrolls inside), the thread (takes
   what is left, scrolls), the acts (pinned at the foot, never shrunk). The card's column 60u -> 80u (240 -> 320 px). */
.pp-detail > .pp-form, .pp-detail > .pp-hair, .pp-detail > .pp-meta, .pp-detail > .pp-acts { flex: 0 0 auto; }
.pp-form .pp-tagsrow { max-height: calc(var(--h-plate) * 3 + var(--gap-label) * 2); overflow-y: auto; align-content: flex-start; }
.pp-form { display: grid; grid-template-columns: calc(var(--u) * 14) minmax(0, 1fr); column-gap: calc(var(--u) * 2); row-gap: calc(var(--u) * 2); align-items: start; }
.pp-form > .lab { height: var(--h-plate); display: flex; align-items: center; }
.pp-form > .ro { height: var(--h-plate); line-height: var(--h-plate); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-form .fld, .pp-form > .plate { width: 100%; }
.pp-hair { flex: 0 0 1px; height: 1px; background: var(--g-hair); }
.pp-meta { color: var(--dim); line-height: calc(var(--u) * 4.5); white-space: nowrap; }
.pp-meta b { color: var(--text); font-weight: 400; }
.pp-acts { margin-top: auto; flex: 0 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-label); }
/* lane PRESET-COMMENTS: the card's thread between its facts and its acts -- it takes the card's free height and scrolls */
.pp-talk { flex: 1 1 0; min-height: calc(var(--u) * 12); overflow-y: auto; display: flex; flex-direction: column; gap: calc(var(--u) * 2); }
.pp-cm { display: flex; flex-direction: column; gap: calc(var(--u) * 0.5); }
.pp-cm-head { display: flex; align-items: baseline; gap: calc(var(--u) * 1.5); color: var(--dim); white-space: nowrap; min-width: 0; }
.pp-cm-head b { flex: 0 1 auto; min-width: 0; color: var(--text); font-weight: 400; overflow: hidden; text-overflow: ellipsis; }
.pp-cm-at { flex: 0 0 auto; }
.pp-cm-acts { display: flex; gap: calc(var(--u) * 2); }
.pp-cm-body { color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere; }
.pp-cm-act, .pp-talk-what .pp-cm-act { color: var(--dim); cursor: pointer; }
.pp-cm-act:hover, .pp-cm-act.on { color: var(--text); }
.pp-talk-what { display: flex; gap: calc(var(--u) * 1.5); color: var(--dim); }
.pp-talk-bar { display: flex; gap: var(--gap-label); align-items: flex-end; }
.pp-talk-in { flex: 1 1 auto; height: auto; min-height: var(--h-plate); padding: calc(var(--u) * 1.5) calc(var(--u) * 2); resize: vertical; line-height: 1.4; }
/* LANE PRESET-PICTURE (rico 2026-10-18: "here is enough space to draw a little preview of the patch."): the picture takes the
   card's FREE height, between the thread and the acts. The thread keeps its content's height (it stood at flex 1 1 0, the free
   height, with its content at its top -- so no row moves) and still shrinks and scrolls when it outgrows the card; the picture
   grows from 0 into what is left, so it gives way first when the comments grow -- the one gap it adds to the column is taken
   back by its margin and stands inside it, over its canvas, so a picture squeezed to nothing costs the thread not one pixel.
   It stands on the space's own ground (PP-GROUND); with nothing to draw (PP-CLOSED, PP-PACK) the box is the card, a quiet
   line at its middle. */
.pp-detail > .pp-talk { flex: 0 1 auto; }
.pp-pic { flex: 1 1 0; min-height: 0; margin-top: calc(-1 * var(--gap-ctl));
          position: relative; display: flex; align-items: center; justify-content: center; }
.pp-pic > canvas { position: absolute; left: 0; top: var(--gap-ctl); width: 100%; height: calc(100% - var(--gap-ctl)); display: block;
                   background: var(--bg); border-radius: var(--r); }
.pp-pic-note { text-align: center; padding: 0 calc(var(--u) * 4); }

/* TAG CHIPS (PD-3's, now in place on the selected preset and in the sheet): one plate high; x removes, a
   click renames; a PRINTED chip (a factory preset's) is one step flatter, with no x and no click; a lit
   rail chip is a live filter. */
.pp-tagsrow { display: flex; align-items: center; gap: var(--gap-label); flex-wrap: wrap; min-width: 0; }
.pp-tag-chip {
    height: var(--h-plate);
    padding: 0 calc(var(--u) * 2);
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: calc(var(--u) * 1.5);
    background: var(--plate);
    border-radius: var(--r);
    cursor: pointer;
    white-space: nowrap;
}
.pp-tag-chip:hover { background: var(--plate-hi); }
.pp-tag-chip.print { background: var(--g-hair); cursor: default; }
.pp-tag-chip.on { background: var(--lit); color: var(--bg); font-weight: 600; }
.pp-tag-x { color: var(--dim); display: inline-flex; align-items: center; }
.pp-tag-x:hover { color: var(--text); }
/* RT-2: the SAME drawn X as the close plates (`closeXIcon`), measured the same off-centre as theirs --
   sized for a TEXT ROW, not a plate (`complexityGlyph`'s own `svg.cx` token, style.css above). */
.pp-tag-x .x-ic { width: 0.72em; height: 0.72em; display: block; }
.pp-tag-edit { width: calc(var(--u) * 20); }
.pp-tag-add { width: calc(var(--u) * 16); }

/* THE FOOTER: Import... Export... | the status line (every save answers here, in words) | Prelisten | Load */
.pp-status { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: calc(var(--u) * 1.5); white-space: nowrap; overflow: hidden; color: var(--text); }
.pp-status .tx { overflow: hidden; text-overflow: ellipsis; }
.pp-status.bad { color: var(--dim); }

/* ── A LIST POPUP (a control that opens a list draws the ▾; this is the list) ───────────────── */
#popup {
    position: fixed;
    z-index: 10;
    display: none;
    flex-direction: column;
    gap: var(--gap-label);
    padding: var(--gap-label);
    background: #000;
    border-radius: var(--r);
}
#popup.open { display: flex; }
/* D2-1 (lane TIERS): a row's padding = the OPENING plate's own word inset (`--row-pad`, measured per open
   in forteria-app.js `placeOverPlate`), so a list's first word lands on its plate's word -- was a fixed
   3u (`--gap-ctl`); menus nobody measured for (the add menu) keep 2u; flip: back to --gap-ctl. */
#popup .item {
    height: var(--h-plate);
    padding: 0 var(--row-pad, calc(var(--u) * 2));
    display: flex;
    align-items: center;
    background: var(--plate);
    border-radius: var(--r);
    cursor: pointer;
    white-space: nowrap;
}
#popup .item:hover { background: var(--plate-hi); }
#popup .item.on { background: var(--lit); color: var(--bg); font-weight: 600; }
/* a row that cannot be picked yet (the add menu's coming entities, lane P45): `.plate.off`'s look */
#popup .item.off { color: var(--off); pointer-events: none; }
/* LANE INT-CAP: the header's [?] beside Interactions wears the card [?]'s ink; its help opens in the popup as one dim
   paragraph, wrapped to a readable measure */
#header .help { color: var(--dim); cursor: pointer; white-space: nowrap; }
#header .help:hover { color: var(--text); }
#popup.helppop { max-width: calc(var(--u) * 70); }
#popup.helppop .item.off { height: auto; padding: var(--u) calc(var(--u) * 2); color: var(--dim); white-space: normal; line-height: 1.4; }
/* LANE P90: a menu's separator, its shortcut words, and the popup LIFTED over a dialog or the sheet (the
   category menu) -- there it wears the graphite ladder: the panel surface under control plates. */
#popup .msep { height: 1px; flex: 0 0 1px; background: var(--plate); margin: calc(var(--u) / 2) calc(var(--u) * 2); }
#popup .item .k { margin-left: auto; padding-left: calc(var(--u) * 4); color: var(--dim); }
/* LANE MENU-CAPS: THE ADD MENU'S SUBMENU -- a second house plate inside the one popup, beside its row (forteria-app.js
   `addOpenSub` places it); a row that opens one carries a dim `›` at its end, the shortcut-words seat */
#popup .sub { position: fixed; display: none; flex-direction: column; gap: var(--gap-label); padding: var(--gap-label);
              background: #000; border-radius: var(--r); }
#popup .sub.open { display: flex; }
#popup .item.hassub .k { padding-left: calc(var(--u) * 3); }
#popup .item.hassub.on .k { color: var(--bg); }
#popup.over { z-index: 30; background: var(--g-panel);
              --text: var(--g-text); --dim: var(--g-lab); --off: var(--g-off); --plate: var(--g-ctl); --plate-hi: var(--g-ctl-hi); --lit: var(--g-act); }
#popup.over .msep { background: var(--g-hair); }

/* ── LANE MENU-LOOK: THE ADD MENU IS A MENU (rico 2026-10-10: "the right click menu must be bigger though. its crammed and
   not very beautiful, looks like a stack of buttons, not like a menu.") -- forteria-app.js `openAddMenu` puts `.addmenu` on
   the one popup. ONE PANEL, the graphite ladder's CARD (k 1) standing on the black space, held by a hairline (tt3) and a soft
   ground-tone shadow; its ROWS have no plate of their own -- glyph | name | `›` -- and the highlight
   runs the whole row: under the hand (and while its submenu is open) the CONTROL rung (k 2). The remembered row wears no
   ground of its own -- its name semibold and its glyph lit -- so the hand's row is the one lit row (it opens under the hand). A greyed row dims all three (disabled, tt5). The submenu is the same panel.
   ASSUMED READING MLK-LIT: the remembered row is marked by weight and glyph alone; flip: the hairline rung (--g-hair) behind it.
   ASSUMED READING MLK-SHADOW: the look law's "no shadow" gives way here -- a popup floats over the space and the panel, and
   the shadow is the ground's own tone (no new colour); flip: drop the box-shadow's second term.
   ASSUMED READING MLK-TYPE: the name at the ladder's title size (16 px), no new type size; flip: the name at 12 px semibold.
   LANE UI-NAMES (rico 2026-10-17: "entferne die sub claims in den menüs."): the dim description line under each name is
   gone, in every menu (the add menu, the thing menu, the source menu and the mod dialog's rows); the tooltip stays. */
#popup.addmenu, #popup.addmenu .sub {
    --text: var(--g-text); --dim: var(--g-lab); --off: var(--g-off);
    background: var(--g-card);
    border-radius: var(--card-r);
    padding: calc(var(--u) * 1.5);
    gap: 0;
    min-width: calc(var(--u) * 60);
    box-shadow: 0 0 0 1px var(--g-hair), 0 calc(var(--u) * 3) calc(var(--u) * 8) color-mix(in srgb, var(--bg) 70%, transparent);
}
#popup.addmenu .sub { min-width: calc(var(--u) * 56); }
/* LANE THING-MENU: the thing menu (Clone | Delete) is the same panel, narrower -- two rows need no add menu's width */
#popup.addmenu.thingmenu { min-width: calc(var(--u) * 52); }
#popup.addmenu .item {
    height: auto;
    min-height: calc(var(--u) * 12);        /* 48 px: MENU-LOOK's roomy row, the name alone on its centre (UN-ROW) */
    padding: calc(var(--u) * 1.5) calc(var(--u) * 3);
    gap: calc(var(--u) * 3);
    background: transparent;
    color: var(--g-text);
    font-weight: 400;
}
#popup.addmenu .item:hover, #popup.addmenu .item.subopen { background: var(--g-ctl); }
#popup.addmenu .item .tx { display: flex; flex-direction: column; min-width: 0; text-align: left; }
#popup.addmenu .item .nm { font-size: var(--fs-title); line-height: 1.25; }
#popup.addmenu .item.on .nm { font-weight: 600; }
#popup.addmenu .item .mg { flex: 0 0 auto; width: calc(var(--u) * 5); height: calc(var(--u) * 5); fill: none; stroke: currentColor;
                           stroke-width: 1.2; stroke-linejoin: round; color: var(--g-lab); }
#popup.addmenu .item .mg .fill { fill: currentColor; stroke: none; }
#popup.addmenu .item .mg .later { stroke-dasharray: 1.6 2.4; }
#popup.addmenu .item:hover .mg, #popup.addmenu .item.on .mg, #popup.addmenu .item.subopen .mg { color: var(--g-text); }
#popup.addmenu .item.hassub .k, #popup.addmenu .item.hassub.on .k { margin-left: auto; padding-left: calc(var(--u) * 4);
                           font-size: var(--fs-title); color: var(--g-lab); }
#popup.addmenu .item.hassub:hover .k, #popup.addmenu .item.subopen .k { color: var(--g-text); }
#popup.addmenu .item.off, #popup.addmenu .item.off .mg { color: var(--g-off); }
#popup.addmenu .item.off .nm, #popup.addmenu .item.off .mg { opacity: 0.7; }
/* LANE DEVICES-16: a row at the instrument cap says why at its end ("max 16", where the `›` stood), in the caret's own ink */
#popup.addmenu .item.off .k { color: var(--g-lab); }
#popup.addmenu .msep { background: var(--g-hair); margin: calc(var(--u) * 1.5) 0; }

/* ── LANE DROP-LOOK (rico 2026-10-12: "we have to clean up all dropdown menus, so they are as beautiful as the world menu."):
   EVERY DROPDOWN WEARS THE WORLD MENU'S PANEL -- forteria-app.js `openList` (every ▾ list: Engine, Type, Variant, Root, Scale,
   Quant, Interactions ...), `openCategoryMenu` and the part menu put `.dropmenu` on the one popup. MENU-LOOK's card (k 1),
   hairline and ground-tone shadow, 6 px inset; rows with NO plate, the name at the title size (16 px), 36 px (9u) high, the
   whole row lighting at the control rung (k 2) under the hand. The CURRENT value is its name semibold with a check at the
   row's end (MENU-LOOK marks its remembered row by weight; a value list also says which one it holds). A divider row (the
   Type menu's "Tuned") is a GROUP CAPTION: the cards' small caps over a hairline. A list taller than the window scrolls.
   ASSUMED READING DL-ROW: 36 px rows, names only -- the add menu keeps MENU-LOOK's roomier 48 px (its description line
   left with lane UI-NAMES) (flip: 48 px). ASSUMED READING DL-CHECK: the current row's check at its end (flip: weight
   alone, MENU-LOOK's MLK-LIT). */
#popup.dropmenu {
    --text: var(--g-text); --dim: var(--g-lab); --off: var(--g-off);
    background: var(--g-card);
    border-radius: var(--card-r);
    padding: calc(var(--u) * 1.5);
    gap: 0;
    max-height: calc(100vh - var(--u) * 2);
    overflow-y: auto;
    box-shadow: 0 0 0 1px var(--g-hair), 0 calc(var(--u) * 3) calc(var(--u) * 8) color-mix(in srgb, var(--bg) 70%, transparent);
}
#popup.dropmenu .item {
    flex: 0 0 auto;
    height: calc(var(--u) * 9);
    padding: 0 var(--row-pad, calc(var(--u) * 3));
    gap: calc(var(--u) * 3);
    background: transparent;
    color: var(--g-text);
    font-size: var(--fs-title);
    font-weight: 400;
}
#popup.dropmenu .item:hover { background: var(--g-ctl); }
#popup.dropmenu .item.on { background: transparent; color: var(--g-text); font-weight: 600; }
#popup.dropmenu .item.on:hover { background: var(--g-ctl); }
#popup.dropmenu .item.on::after { content: '\2713'; margin-left: auto; padding-left: calc(var(--u) * 4); font-weight: 400; color: var(--g-text); }
#popup.dropmenu .item .k { color: var(--g-lab); }
#popup.dropmenu .item.off { height: auto; margin-top: calc(var(--u) * 1.5); padding: calc(var(--u) * 2.5) var(--row-pad, calc(var(--u) * 3)) calc(var(--u) * 1);
                            border-top: 1px solid var(--g-hair); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--g-lab); }
#popup.dropmenu .msep { background: var(--g-hair); margin: calc(var(--u) * 1.5) 0; }

/* ── LANE P90: THE SHEET (the first save; Save / Discard / Cancel) -- p90-save-first ─────────────────── */
/* A small modal over everything: the GRAPHITE ladder (tone.md (a)) -- panel, then one card holding the form,
   plates on it, fields SUNK one step (the panel's tone inside the card). Its scrim is 60 %, never the
   dialog's 97 %: the sheet does not mute or hide the world (mockup ASSUMED READING 1). */
#sheet { position: fixed; inset: 0; z-index: 25; display: none; align-items: center; justify-content: center;
         background: color-mix(in srgb, var(--bg) 60%, transparent);
         --text: var(--g-text); --dim: var(--g-lab); --off: var(--g-off); --plate: var(--g-ctl); --plate-hi: var(--g-ctl-hi); --lit: var(--g-act); }
#sheet.open { display: flex; }
.sh-card { width: calc(var(--u) * 100); background: var(--g-panel); border-radius: var(--card-r); padding: calc(var(--u) * 4);
           display: flex; flex-direction: column; gap: var(--gap-ctl); color: var(--text); }
.sh-head, .sh-foot { flex: 0 0 auto; height: var(--h-plate); display: flex; align-items: center; gap: var(--gap-label); }
.sh-title { flex: 1 1 auto; font-size: var(--fs-title); white-space: nowrap; }
.sh-foot { justify-content: flex-end; }
.sh-x, .pp-close { flex: 0 0 var(--h-plate); width: var(--h-plate); padding: 0; }
/* RT-2: the close plates' X is DRAWN now (forteria-app.js `closeXIcon`), the transport icons' own way --
   `display: block` for the same reason theirs carries it (an inline svg's own baseline gap would pull it
   off the flex-centred middle `.plate` already gives it for free). */
.sh-x .x-ic, .pp-close .x-ic { display: block; }
.sh-body { background: var(--g-card); border-radius: var(--card-r); padding: var(--card-pad); display: flex; flex-direction: column; gap: calc(var(--u) * 2); }
.sh-form { display: grid; grid-template-columns: calc(var(--u) * 14) minmax(0, 1fr); column-gap: calc(var(--u) * 2); row-gap: calc(var(--u) * 2); align-items: start; }
.sh-form > .lab { height: var(--h-plate); display: flex; align-items: center; }
.sh-col { display: flex; flex-direction: column; gap: var(--u); min-width: 0; }
.sh-warn:empty { display: none; }
.sh-warn, .sh-text { color: var(--text); }
.sh-hint { color: var(--dim); }
/* LANE EXPORT-B: THE EXPORT SHEET (forteria-app.js `exportBody`). Its readouts (Range / Contains / Format) are
   one plate-high LINE each -- so the first line of a row stands on its label's own centre line, the save sheet's
   form grid unchanged -- cut with an ellipsis (the full text is the line's title), never wrapped. Under the form, the
   PROGRESS line: its words, and while rendering a 2 px bar on the control tone, filled with the active tone (the
   graphite ladder: no new colour). A failure wears the house's refusal tone (dim, `.pp-status.bad`); the clipped
   count is dim at 0 and in the text tone above 0 (the colour law has no warn hue). */
.sh-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.sh-lines { gap: 0; }
.sh-line { display: block; height: var(--h-plate); line-height: var(--h-plate); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* LANE EXPORT-PICK: the Contains row's bar -- the house plates (t-m, the foot's own size), 1 u apart, standing where every value
   of the form stands; the `.sh-line` it wears keeps the row's one centre line (the label sits on it). */
/* LANE TL-RETIRE: the export's Length bar (`.sh-lens`) is the same bar of plates. */
.sh-picks, .sh-lens { display: flex; align-items: center; gap: var(--u); overflow: visible; line-height: normal; }
.sh-prog { display: flex; flex-direction: column; gap: var(--u); }
.sh-prog:empty { display: none; }          /* idle: no line, no hole (the save sheet's `.sh-warn:empty` law) */
.sh-prog.bad .sh-line { color: var(--dim); }
.sh-bar { height: calc(var(--u) / 2); background: var(--plate); border-radius: var(--r); overflow: hidden; }
.sh-fill { height: 100%; background: var(--lit); }
.sh-clip { color: var(--dim); }
.sh-clip.warn { color: var(--text); }
/* THE HOUSE FIELD (the NOTED Tags input fix, style.css :932 before this lane: an unstyled white field): one
   plate high, the plate's inset, no border, SUNK -- the ground's tone on a panel, the panel's on a card. */
.fld { height: var(--h-plate); padding: 0 calc(var(--u) * 2); box-sizing: border-box; border: none; outline: none;
       background: var(--bg); border-radius: var(--r); color: var(--text); font-family: inherit; font-size: var(--fs-label); min-width: 0; }
.fld::placeholder { color: var(--off); }
.fld:focus { box-shadow: inset 0 0 0 1px var(--g-hair); }
.sh-body .fld, .pp-box .fld { background: var(--g-panel); }
.plate.menu { justify-content: space-between; }
.plate.menu .nm { overflow: hidden; text-overflow: ellipsis; }

/* ── LANE MOD-ALL: THE SOURCE MENU (it replaces C-3's tile grid, lane MOD-C) ───────────────────────────────────────
   rico 2026-10-12: "the mod dialog is still UX design HELL." The source picker is a DROP-LOOK menu now (`.dropmenu` on
   the one popup, the world menu's card, hairline and shadow; rows with no plate, the current one semibold with its
   check) and its rows are MENU-LOOK's: the kind's glyph | its name | its LIVE PREVIEW (MOD-C's tile
   trace, kept) | the check. 48 px (12u) rows, the add menu's measure. */
#popup.modsrc { min-width: calc(var(--u) * 76); }
#popup.modsrc .item { height: auto; min-height: calc(var(--u) * 12); padding: calc(var(--u) * 1.5) calc(var(--u) * 3); gap: calc(var(--u) * 3); }
#popup.modsrc .item:focus { outline: none; background: var(--g-ctl); }
#popup.modsrc .item .md-glyph { flex: 0 0 auto; width: calc(var(--u) * 5); height: calc(var(--u) * 4); display: block; }
#popup.modsrc .item .tx { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
#popup.modsrc .item .nm { font-size: var(--fs-title); line-height: 1.25; }
#popup.modsrc .item .pv { flex: 0 0 auto; width: calc(var(--u) * 16); height: calc(var(--u) * 6); display: block; }
#popup.modsrc .item::after { content: ''; flex: 0 0 calc(var(--u) * 3); margin-left: 0; padding-left: 0; }
#popup.modsrc .item.on::after { content: '\2713'; margin-left: 0; padding-left: 0; }

/* ── THE DEV STRIP: the one place allowed to look like a debug tool ─────────────────────────── */
/* LANE DEV-HIDE (rico 2026-10-16: "dev/load samples on the botto of the page should be removed."): the strip is NOT shown,
   on the desk or the phone -- the space takes its height. It stays in the DOM, hidden: `#devInfo` is the line every drive
   reads the audio mode from, and samples still arrive by a drop (and the hidden `#fileInput` the drives set).
   ASSUMED READING DEV-HIDE: hidden, not deleted -- flip: delete the markup and move the drives to `__sondeEngine`. */
#dev {
    display: none !important;
    flex: 0 0 auto;
    height: calc(var(--u) * 8);
    margin: var(--gap-group) var(--margin) var(--margin) var(--margin);
    display: flex;
    align-items: center;
    gap: var(--gap-ctl);
    color: var(--dim);
}
#dev .tag { letter-spacing: 0.1em; }
#dev .spacer { flex: 1 1 auto; }
#dev .plate { height: calc(var(--u) * 5); }

#drop {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.8);
    color: var(--text);
    font-size: var(--fs-title);
}
#drop.on { display: flex; }

input[type=file] { display: none; }

/* ── LANE MOBILE-1: WHERE A FINGER DRAGS AND WHERE IT SCROLLS ─────────────────────────────────────────────
   rico 2026-10-06: "the world or any parameter, nothing gets the mouse on mobile." The touch adapter
   (forteria-app.js, "THE TOUCH ADAPTER") reads `touch-action` to tell the two apart, so this block IS the
   map: `none` = the page takes the finger from its first pixel (the browser neither scrolls nor zooms
   there) -- the space, every canvas (the knob dials, bars, lanes, the roll, the strip), every knob cell,
   every dragged number plate, the header's whole band. `pan-y` = the panels that scroll keep scrolling
   under a finger that starts on a label, a plate or a gap; a tap and a hold there still reach the page.
   ASSUMED READING TOUCH-4: a KNOB CELL takes the finger (a vertical drag turns it) and the inspector
   scrolls from everything else -- flip: drop `.ctl:has(> canvas)` below, and a knob turns only from its
   dial. A desk mouse never reads any of this. */
html, body { -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; }
#header, #space, canvas, .ctl:has(> canvas), .plate.num { touch-action: none; }
#inspector, .pp-rail, .pp-matrix, .pp-list, .pp-cats, .pp-tagbox { touch-action: pan-y; }

/* ── THE PHONE (lane MOBILE-2) ──────────────────────────────────────────────────────────────────
   rico 2026-10-06, on an iPhone: "maybe the inspector and the world must always be fullscreen and we just need a switch.
   because the inspector is tiny on the phone. might be different on ipad." index.html marks a phone (`html.phone`) and lays
   it out at the inspector's own 528 px; here the world and the inspector each take the whole middle, ONE shown at a time
   (`body.ph-insp` = the inspector, forteria-app.js `setPhoneView`), and the switch under them flips. The header WRAPS
   (it is 1108 px of plates on one line at a desk). `--px-screen` (index.html) is layout px per screen px: every phone
   target below is 44 SCREEN px, Apple's HIG minimum. Nothing here matches without `html.phone`: a tablet and a desk are
   the page they were. */
#phoneSwitch { display: none; }
html.phone #header { flex-wrap: wrap; height: auto; row-gap: var(--gap-ctl); column-gap: var(--gap-group);
                     padding-top: var(--gap-ctl); padding-bottom: var(--gap-ctl); }
/* both stand in the middle, one over the other, and the one not shown is only INVISIBLE: never `display: none`, so every
   box keeps its size and a panel built while the world is shown measures its knobs, menus and lanes as it will show them
   (a display-none build drew 0-px knobs -- the arc threw, and the frame loop died with it) */
html.phone #main { padding: 0; gap: 0; margin-top: var(--gap-ctl); }
html.phone #main > #space { position: absolute; inset: 0 var(--margin); }
html.phone #main > #inspector { position: absolute; inset: 0; width: auto; }
html.phone body.ph-insp #main > #space,
html.phone body:not(.ph-insp) #main > #inspector { visibility: hidden; pointer-events: none; }
html.phone #inspTab { display: none; }                 /* the switch IS the phone's hide plate */
html.phone #phoneSwitch { display: flex; flex: 0 0 auto; gap: var(--gap-label); margin: var(--gap-ctl) var(--margin) 0 var(--margin); }
html.phone #phoneSwitch .plate { flex: 1 1 0; justify-content: center; height: calc(44px * var(--px-screen, 1)); font-size: var(--fs-title); }
/* LANE UNDO (UN-SEAT): the phone's header row has no room for Undo | Redo, so they open the switch row -- two finger-square
   plates at its left, World | Inspector sharing the rest; the header's own pair is the desk's */
html.phone #phoneSwitch .plate.ph-hist { flex: 0 0 calc(44px * var(--px-screen, 1)); }
html.phone #undoPlate, html.phone #redoPlate { display: none; }
/* rico 2026-10-08 (iPhone): "you must remove the dev row on the bottom." -- the phone gives the DEV strip no room at all
   (load samples lives in the POOL fold now; the build stamp stays on the desk). Desk and tablet keep it. */
html.phone #dev { display: none; }
/* LANE KEY-SHOW (rico 2026-10-09, iPhone): "on phone: no 'sonde' row in the header. no timeline. this will buy us 2 rows of
   real estate." The brand block goes, and so does the timeline strip (ruler + loop bar): both rows' height is the space's.
   The song position -- the brand row's one other tenant -- moves to the END of the key row (`order`), where it fits at
   528 px; the desk keeps both rows. The header key's list rows are finger-sized (44 SCREEN px), as every phone target. */
html.phone #brand, html.phone #timeline { display: none; }
html.phone #header > .grp:has(> #posBars) { order: 1; }      /* ASSUMED READING KS-5 -- flip: hide it with the brand */
/* LANE PHONE-HEAD (rico 2026-10-12, iPhone: "the iphone has 3 rows on top while wasting real estate. it could be 2 rows."):
   measured at 528 px the wrap made [Presets name][transport] / [BPM key][Quantize] / [Interactions .......... position] --
   the third row for two short groups, each row half empty. TWO ROWS, by `order` and one forced break (the header's ::after,
   a zero-height full-width item; the row gap is 0 so its empty line adds nothing, and row 2 keeps the gap as its margin):
     row 1  Presets + name | transport | song position (at the end)
     row 2  tempo + key | Quantize | Interactions
   Row 2 measured 544 of the 496 px between the margins, so on the phone the groups stand --gap-ctl apart (24 -> 12 px) and
   the tempo plate drops its "BPM" word (the plate keeps it as its title, and the number reads as the tempo beside the key):
   490 px with this machine's fallback font (wider than the iPhone's own). The save note ("Saved 20:41:07", `docSay`) would
   need a row of its own when it shows: on the phone it floats under the header's right end instead.
   ASSUMED READING PH-BPM: the "BPM" word goes, not the "Interactions" one (a bare "Max" would not say what it caps) --
   flip: hide `.grp:has(> #intCap) > .lab` instead. */
html.phone #header { position: relative; column-gap: var(--gap-ctl); row-gap: 0; }
html.phone #header > .grp:has(> #presetsPlate) { order: 1; }
html.phone #header > .grp:has(> #play) { order: 2; }
html.phone #header > .grp:has(> #posBars) { order: 3; margin-left: auto; }
html.phone #header::after { content: ''; order: 4; flex-basis: 100%; height: 0; }
html.phone #header > .grp:has(> #bpm), html.phone #header > .grp:has(> #quantDiv),
html.phone #header > .grp:has(> #intCap) { order: 5; margin-top: var(--gap-ctl); }
html.phone #header > .grp:has(> #bpm) > .lab { display: none; }
html.phone #docStatus { position: absolute; right: var(--margin); top: 100%; order: 6; pointer-events: none; }
html.phone #docStatus:empty { display: none; }
/* LANE HEADER-USER, THE PHONE: the account plate FLOATS at row 1's right end -- top right, outside the two rows' flow (PH1's
   rows stay as PHONE-HEAD laid them) -- and the song position there makes its room: it keeps bars.beats (1.1.1) and drops its
   minutes (0:00.0), 39 px for the plate's 28 + a gap. The popup is a sheet from margin to margin, its rows a finger high.
   ASSUMED READING HU-PHONE: the plate ends row 1 and the minutes go -- flip: it ends row 2 and Interactions' word goes. */
html.phone #acctPlate { position: absolute; top: var(--gap-ctl); right: var(--margin); }
html.phone #header > .grp:has(> #posBars) { margin-right: calc(var(--u) * 7 + var(--gap-ctl)); }
html.phone #posTime { display: none; }
html.phone #acctPop { left: var(--margin) !important; right: var(--margin) !important; width: auto; }
html.phone #acctPop .fld, html.phone #acctPop .plate, html.phone #acctPop .item { height: calc(44px * var(--px-screen, 1)); font-size: var(--fs-title); }
html.phone #popup.fing .item { height: calc(44px * var(--px-screen, 1)); font-size: var(--fs-title); }
/* LANE MENU-LOOK, THE PHONE (MOBILE-2): the add menu is a SHEET from margin to margin (forteria-app.js `addPlaceSheet` puts it
   on the half away from the long-press), its rows 56 SCREEN px with type at a phone's reading size, and a submenu opens
   INLINE under its row -- sunk one step (the panel's tone inside the card, the sheet's own field law), indented under the
   name, its caret turned down. */
html.phone #popup.addmenu { right: var(--margin); min-width: 0; padding: calc(8px * var(--px-screen, 1));
                            max-height: calc(100vh - var(--margin) * 2); overflow-y: auto; }
html.phone #popup.addmenu .item { height: auto; min-height: calc(56px * var(--px-screen, 1)); font-size: inherit;
                                  padding: calc(8px * var(--px-screen, 1)) calc(16px * var(--px-screen, 1)); gap: calc(14px * var(--px-screen, 1)); }
html.phone #popup.addmenu .item .nm { font-size: calc(17px * var(--px-screen, 1)); }
html.phone #popup.addmenu .item .mg { width: calc(26px * var(--px-screen, 1)); height: calc(26px * var(--px-screen, 1)); }
html.phone #popup.addmenu .item.hassub .k { font-size: calc(22px * var(--px-screen, 1)); transition: transform 0.12s; }
html.phone #popup.addmenu .item.subopen .k { transform: rotate(90deg); }
html.phone #popup.addmenu .sub { position: static; min-width: 0; padding: calc(4px * var(--px-screen, 1)); box-shadow: none;
                                 background: var(--g-panel); margin: calc(2px * var(--px-screen, 1)) 0 calc(4px * var(--px-screen, 1))
                                 calc(40px * var(--px-screen, 1)); }
/* the preset dialog and the sheet are the phone's whole screen too: the card fills it (the dialog never docks on a phone:
   forteria-app.js `presetDock`) */
html.phone #presetPanel .pp-card { width: 100%; max-width: none; height: 100%; max-height: none; border-radius: 0; }
/* ...and the dialog's desk row (rail | matrix | card, 880 px) STACKS on a phone's 528: the strip and the foot wrap, the
   rail, the matrix and the selected preset's card stand under each other, and the whole card scrolls (pan-y) */
html.phone #presetPanel .pp-card { overflow-y: auto; touch-action: pan-y; }
html.phone .pp-strip { flex: 0 0 auto; flex-wrap: wrap; padding-top: var(--card-pad); padding-bottom: var(--card-pad); row-gap: var(--gap-ctl); }
html.phone .pp-strip .sp { flex-basis: 100%; height: 0; }
html.phone .pp-body { flex: 0 0 auto; flex-direction: column; }
html.phone .pp-rail { flex: 0 0 auto; max-height: calc(var(--u) * 60); }
html.phone .pp-mid { flex: 0 0 auto; height: calc(var(--u) * 70); }
html.phone .pp-detail { flex: 0 0 auto; min-height: calc(var(--u) * 50); }
/* LANE PRESET-PICTURE: the stacked card has no free height -- the picture stands under the acts at the column's foot, a height
   of its own, and the thread keeps the stacked card's own law (flex 1 1 0), so no row above it moves. ASSUMED READING PP-PHONE
   -- flip: between the facts and the thread. */
html.phone .pp-detail > .pp-talk { flex: 1 1 0; }
html.phone .pp-detail > .pp-pic { order: 1; flex: 0 0 auto; height: calc(var(--u) * 60); }
html.phone .pp-foot { height: auto; flex-wrap: wrap; row-gap: var(--gap-ctl); }
/* LANE PRESET-LOAD-TOP: Load stands in the head (title | search | Load | close on the desk). A phone's 528 cannot hold all
   four in one row, so the head wraps -- title | close, then search | Load -- and stays PINNED to the card's top while the
   card scrolls, so Load is in reach from any row of the matrix without a scroll back (ASSUMED READING -- flip: a head
   that scrolls away with the card). Opaque (the card's own ground) so the rows pass under it; it reaches up over the card's
   4-unit padding and down over one control gap (paid back by the negative margins, so the resting layout is unmoved), or
   the rows would show through above and touch it below. */
html.phone .pp-head { height: auto; flex-wrap: wrap; row-gap: var(--gap-ctl); position: sticky; top: calc(var(--u) * -4); z-index: 1;
                      background: var(--g-panel); padding: calc(var(--u) * 4) 0 var(--gap-ctl);
                      margin: calc(var(--u) * -4) 0 calc(-1 * var(--gap-ctl)); }
html.phone .pp-title { flex: 1 1 calc(100% - var(--h-plate) - var(--gap-ctl)); min-width: 0; }
html.phone .pp-close { order: 1; }
html.phone .pp-search { order: 2; flex: 1 1 0; min-width: 0; }
html.phone .pp-head .pp-load { order: 3; }
html.phone .pp-tab { flex: 1 1 0; height: calc(44px * var(--px-screen, 1)); }   /* LANE PRESET-TABS: three in the width, a finger's height */
/* LANE PRESET-TABS: stacked, the rail's three boxes each get their own height (the categories never squeezed by the tags) */
html.phone .pp-rail { max-height: none; }
html.phone .pp-cats { flex: 0 1 auto; max-height: calc(var(--u) * 36); }
html.phone .pp-tagbox { max-height: calc(var(--u) * 26); }
/* LANE PRESET-FIT: A NARROW CARD STACKS AS THE PHONE'S DOES. The three desk columns need 200u of card (rail 44u + library
   ~84u for its Sort group + selected preset 60u + gaps and insets); the DOCKED dialog gets what the inspector leaves --
   174u at 1280 px, 102u at 1024, 104u on a tablet's 1120 desk -- and there the library card was crushed to nothing,
   its head and the selected preset's lines running out of the card. Below 200u the card takes the phone's stacked
   layout above (strip and foot wrap, rail / library / selected preset under each other, the head pinned with Load in it,
   the card scrolls). The same rules as the `html.phone` block, keyed on the card's width instead of the device: a phone
   in landscape keeps its own block. `800px` = 200u (a container condition takes no var()). ASSUMED READING PF-STACK:
   stack in place, docked -- flip: a narrow dock centres the dialog full-width over the inspector instead. */
@container ppcard (width < 800px) {
    .pp-strip { flex: 0 0 auto; flex-wrap: wrap; padding-top: var(--card-pad); padding-bottom: var(--card-pad); row-gap: var(--gap-ctl); }
    .pp-strip .sp { flex-basis: 100%; height: 0; }
    .pp-body { flex: 0 0 auto; flex-direction: column; }
    .pp-rail { flex: 0 0 auto; max-height: calc(var(--u) * 60); }
    .pp-mid { flex: 0 0 auto; height: calc(var(--u) * 70); }
    .pp-detail { flex: 0 0 auto; min-height: calc(var(--u) * 50); }
    .pp-detail > .pp-talk { flex: 1 1 0; }                                             /* LANE PRESET-PICTURE: PP-PHONE */
    .pp-detail > .pp-pic { order: 1; flex: 0 0 auto; height: calc(var(--u) * 60); }
    .pp-foot { height: auto; flex-wrap: wrap; row-gap: var(--gap-ctl); }
    .pp-head { height: auto; flex-wrap: wrap; row-gap: var(--gap-ctl); position: sticky; top: calc(var(--u) * -4); z-index: 1;
               background: var(--g-panel); padding: calc(var(--u) * 4) 0 var(--gap-ctl);
               margin: calc(var(--u) * -4) 0 calc(-1 * var(--gap-ctl)); }
    .pp-title { flex: 1 1 calc(100% - var(--h-plate) - var(--gap-ctl)); min-width: 0; }
    .pp-close { order: 1; }
    .pp-search { order: 2; flex: 1 1 0; min-width: 0; }
    .pp-head .pp-load { order: 3; }
    .pp-tab { flex: 1 1 0; }                            /* LANE PRESET-TABS: a narrow card shares its width among the three */
    .pp-rail { max-height: none; }
    .pp-cats { flex: 0 1 auto; max-height: calc(var(--u) * 36); }
    .pp-tagbox { max-height: calc(var(--u) * 26); }
}
/* THE PIANO ROLL, FULL-SCREEN (lane MOBILE-2; forteria-app.js `rollBody` / ROLL_FULL_K): the card body lifted over the whole
   phone screen, a head row (the emitter's name, Done) above it; the raster is as tall as the screen leaves it (its rows
   2 x the desk's), and the rest of the body scrolls under a finger outside the canvases */
.roll-full { position: fixed; inset: 0; z-index: 14; background: var(--g-panel); overflow-y: auto; touch-action: pan-y;
             padding: var(--card-pad) var(--margin); display: flex; flex-direction: column; gap: var(--gap-ctl); visibility: visible; }
.roll-full .rf-head { flex: 0 0 auto; display: flex; align-items: center; gap: var(--gap-ctl); }
.roll-full .rf-title { flex: 1 1 auto; font-size: var(--fs-title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.roll-full .rf-done { height: calc(44px * var(--px-screen, 1)); min-width: calc(88px * var(--px-screen, 1)); justify-content: center; font-size: var(--fs-title); }
.roll-full .rollgrid { height: calc(100vh - var(--u) * 100); min-height: calc(var(--u) * 64); max-height: calc(var(--u) * 191); }   /* lane ROLL-UX: never more than ROLL_VIEW_ROWS x 2 rows */
/* LANE PHONE-LAND (rico 2026-10-12: "when I put the phone in landscape, it only fills half the screen. tested with pianoroll in
   fullscreen mode."): MEASURED in landscape (iPhone 13, the page 844 px wide): the full-screen editor covered the screen but
   its raster stood 16..500 of 750 px -- the card's three FIXED 39u columns (.cols, 484 px) -- and its rows were cut to three
   by the height. In the full-screen editor the columns now STRETCH to the screen (never under their desk width), and a
   screen wider than it is tall gives the raster the screen's height under the head row (the tools scroll above it).
   ASSUMED READING PL-ROWS: the raster's height = the screen less the head row (20u) in landscape -- flip: the portrait rule. */
.roll-full .cols, html.phone #inspector .cols:not(.drmkit) { grid-template-columns: repeat(3, minmax(var(--w-col), 1fr)); }   /* ...and every card on a phone: a landscape screen is all panel (fitInspector) */
@media (min-aspect-ratio: 1/1) { .roll-full .rollgrid { height: calc(100vh - var(--u) * 20); min-height: calc(var(--u) * 40); } }
/* THE GROWN ROLL (lane ROLL-EXPAND; forteria-app.js `rollBody`'s band, `rollGrowSync`). rico 2026-10-18: "why not just extend
   the piano roll to the left [...] when the roll expands to the left, we need some trickery, because we are breaking out of the
   inspector, while still being a part of it. the inspector is grey, the world is black." It REPLACES ROLL-WIDE's `.roll-wide`,
   a second roll laid over the space beside the card's own. Grown (`#main.rx`), the inspector stands as tall as its rows -- the
   title row and the roll's card alone -- and the space beside it as tall (`--rx-top`, measured), a black band; the card's own
   sheet, its Vel | Ratchet row and its lane hang under both in the card's BAND, positioned against #main from the window's left
   margin to the inspector's right edge and down to the window's foot. The trickery is two nested L's, one tone each: THE
   PANEL'S (k 0) -- the column and the band's rim meeting edge to edge, the panel's 2u inset on the left, its foot 2u -- and
   inside it THE CARD'S (k 1) -- the column card's foot and lower corners go, so its rows run straight down into the band's
   card, whose top-right corner is square where they join. Each L's one concave corner is filled by a fillet of the card
   radius (a quarter disc cut out of a square of the L's tone), so neither reads as two boxes put side by side.
   ASSUMED READING RX-SHAPE: two nested L's (the panel's rim, the card inside), the convex corners the house's (the panel
   square, the card 6 px), each concave corner a 6 px fillet -- flip: the band one flat card, no rim, no fillets.
   ASSUMED READING RX-EDGE: no hairline and no shadow where the grey meets the black -- the blob law (a surface is its tone
   step, never an outline) and a shadow on the black ground cannot show -- flip: a 1 px tt3 hairline along the black. */
#main.rx > #space { align-self: flex-start; height: var(--rx-top); }
/* ASSUMED READING RX-TOP: the column is as tall as its rows, never over 45 % of the middle -- the sheet keeps 55 % at the least
   and a short window scrolls the column instead (at 1440 x 900 the rows fit: 216 of 760 px) -- flip: no cap. */
#main.rx > #inspector { align-self: flex-start; max-height: 45%; padding-bottom: 0; }
/* RX-ALONE: the other cards step out of the column's flow, unseen and untouchable -- never `display: none`, under which their
   knobs measure 0 px and the arc throws, killing the frame loop (lane MOBILE-2's own finding) */
#main.rx > #inspector > :not(.titlerow):not(.rx-card) { position: absolute; visibility: hidden; pointer-events: none; }
#main.rx .rx-card { padding-bottom: 0; border-radius: var(--card-r) var(--card-r) 0 0; }
/* ASSUMED READING RX-DOCK: grown, a docked preset dialog leaves the inspector under its scrim with the band -- the lift
   (`body.pp-docked #inspector`, relative + z 16) would make the panel the band's box (it vanished, clipped) or, kept static,
   lay the band OVER the docked dialog -- flip: lift the column alone. */
body.pp-docked #main.rx > #inspector { position: static; z-index: auto; }
.rx-band { position: absolute; z-index: 1; left: var(--margin); right: var(--margin); top: calc(var(--gap-group) + var(--rx-top)); bottom: 0;
           background: var(--g-panel); padding: 0 var(--rx-gut) var(--w-margin-l) var(--w-margin-l); }
.rx-sheet { position: relative; height: 100%; background: var(--g-card); border-radius: var(--card-r) 0 var(--card-r) var(--card-r);
            padding: var(--card-pad); display: grid; grid-template-columns: minmax(0, 1fr);
            grid-template-rows: minmax(0, 1fr) auto auto; row-gap: var(--gap-ctl); align-items: center; }
.rx-band::before, .rx-sheet::before { content: ''; position: absolute; top: calc(-1 * var(--card-r)); width: var(--card-r); height: var(--card-r);
                                      background: radial-gradient(circle at 0 0, transparent var(--card-r), var(--fill) calc(var(--card-r) + 0.5px)); }
.rx-band::before { right: var(--rx-iw); --fill: var(--g-panel); }        /* the panel's concave corner, over the black */
.rx-sheet::before { right: var(--rx-cw); --fill: var(--g-card); }        /* the card's, over the panel's rim */
.rx-sheet > .rollgrid { grid-column: 1 / -1; height: auto; min-height: 0; align-self: stretch; }
.rx-sheet > .rolllane { grid-column: 1 / -1; }
/* THE BAND'S LOWER ROW (follow-up): Vel | Ratchet, Notes, Length | Grid | Draw (a column's width each, as in the card), the
   Harmony row taking what is left (its readout cut, whole in its tooltip), the zoom plates at the end -- one line at a desk,
   wrapping on a narrower window */
.rx-row { grid-row: 2; display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--gap-col); row-gap: var(--gap-ctl); min-width: 0; }
.rx-row > .ctl:not(.choice.two) { flex: 0 0 var(--w-col); width: var(--w-col); }
.rx-row > .rollharm { flex: 1 1 calc(var(--u) * 72); min-width: 0; }
.rx-row > .rw-zoom { margin-left: auto; }
/* THE EXPAND PLATE under a finger: a 44 SCREEN px box round the same 24 px plate (the hide plate's MOBILE2-TAB law), its
   extra height laid over the air round the head row (the [?]'s negative margins), so no card head grows */
@media (pointer: coarse) {
    .foldhead > .rw-open { width: max(var(--t-sq), calc(44px * var(--px-screen, 1))); height: max(var(--h-plate), calc(44px * var(--px-screen, 1)));
                           margin: calc((var(--h-plate) - 44px * var(--px-screen, 1)) / 2) 0; background: transparent; position: relative; isolation: isolate; }
    .foldhead > .rw-open::before { content: ''; position: absolute; left: 50%; top: 50%; width: var(--t-sq); height: var(--h-plate);
                                   transform: translate(-50%, -50%); background: var(--plate); border-radius: var(--r); z-index: -1; }
}
/* THE ZOOM PLATES (ROLL-WIDE round 2): Time - + | Pitch - +, on the grown roll's Vel | Ratchet row (its right end) and on a line
   of their own under the phone's full-screen head (title | Done); under a coarse pointer (a tablet's, a phone's finger) each
   is a 44 SCREEN px box, the hide plate's own law */
.rw-zoom { flex: 0 0 auto; display: flex; align-items: center; gap: var(--u); }
.rw-zoom > .lab { color: var(--dim); margin: 0 var(--u) 0 var(--gap-ctl); }
.rw-zoom > .plate { justify-content: center; }
.roll-full .rf-head { flex-wrap: wrap; row-gap: var(--gap-ctl); }
.roll-full .rf-head > .rw-zoom { order: 1; flex-basis: 100%; }
.roll-full .rf-head > .rw-zoom > .lab:first-child { margin-left: 0; }
@media (pointer: coarse) {
    .rw-zoom > .plate { width: max(var(--t-sq), calc(44px * var(--px-screen, 1))); height: max(var(--h-plate), calc(44px * var(--px-screen, 1))); }
}
/* THE HIDE PLATE UNDER A FINGER (lane MOBILE-2, rico 2026-10-06: "expend inspector button is too small on mobile."): its
   24 px square shown at a tablet's 0.73 is 17.5 screen px; on a touch screen (pointer: coarse) its BOX is 44 SCREEN px square
   (Apple's HIG) and the plate it shows is the same 24 px square, drawn by ::before in the plate's own ink and radius, in its
   middle. The box stands IN the head row (the row grows to it) -- a pad overlapping its neighbours would steal the Presets
   plate's presses, and the panel's own scroll box clips anything past its edge. A mouse (pointer: fine) never matches: the
   desk's plate is the one it always was. ASSUMED READING MOBILE2-TAB: the box grows, the plate does not -- flip: drop
   ::before and the transparent ground, and the plate itself is 44 px. */
@media (pointer: coarse) {
    #inspTab { width: max(var(--t-sq), calc(44px * var(--px-screen, 1))); height: max(var(--h-plate), calc(44px * var(--px-screen, 1)));
               background: transparent; position: relative; isolation: isolate; }
    #main > #inspTab { position: absolute; }
    .titlerow:has(> #inspTab) { height: auto; }        /* the head row grows round the box, its centre line kept */
    #inspTab::before { content: ''; position: absolute; left: 50%; top: 50%; width: var(--t-sq); height: var(--h-plate);
                       transform: translate(-50%, -50%); background: var(--plate); border-radius: var(--r); z-index: -1; }
}

/* ── LANE MOD-ALL: THE MOD DIALOG (rico 2026-10-12: "the mod dialog is still UX design HELL. horrible. it can be much
   more beautiful.") -- forteria-app.js `buildModDialog`. ONE PANEL in MENU-LOOK's vocabulary: the graphite card (k 1),
   a tt3 hairline and the ground-tone shadow (MLK-SHADOW), the card's 6 px corner. Its HEAD: a small-caps caption over
   the parameter's name at the title size, the value now in the label ink, a close cross. Per modulator: a small-caps
   `MOD n` caption and its one Remove (a word that lights under the hand, never a plate); the SOURCE as a menu row (glyph
   | name | caret, 48 px, the whole row lit under the hand); the drawn shape; the rows on the inspector's
   own raster -- TWO of its columns, 2u apart -- where a choice's options are WORDS (lane PRESET-SORT's switch: the label
   ink at rest, the value ink under the hand, the current one semibold); the AMOUNT as a bipolar slider over a centre
   tick with its value, the direction in words. Hairlines (MENU-LOOK's group rule) between the blocks and above `Add
   modulator`. Plates stay only on acts that are acts of a row of their own (Randomize, Shape). Every text starts on the
   menus' 3u inset (the panel's 1.5u + the row's 1.5u). A dialog taller than the window scrolls its body.
   ASSUMED READING MODALL-COLS: two of the inspector's columns, 2 x 39u (flip: three, the rack's old width).
   LANE MOD-LOOK (rico 2026-10-17: "the mod popup could have better distinction between background and controls."): every
   control stood ON the card's own tone -- the source row, the words, Remove, the cross and the drawn shapes were bare on k 1,
   the Amount a 2 px hairline -- so nothing but the type said where a hand goes. THE CONTRAST LAW: on the popup's ground (k 1,
   the world menu's card, unchanged) whatever a hand moves stands ONE SURFACE STEP UP, k 2 (--g-ctl; under the hand tt5,
   --g-ctl-hi): the source row and Add modulator, the menu chips, Remove, the close cross, the knob's cap, the CURRENT option
   of a choice; whatever a control is read or chosen IN is SUNK ONE STEP, k 0 (--g-panel, the house's field law: the panel's
   tone inside a card): the drawn shapes (the scope strips, the source menu's previews), the Amount's groove, the bar a
   choice's words stand in. One step is the ladder's doubling, so a fill's ink share is 2x or 0.5x the ground's -- the
   minimum tools/check-modlook.js ML2 measures on the pixels. No new tone: --g-panel / --g-card / --g-ctl / --g-ctl-hi / --g-act.
   ASSUMED READING ML-SEG: a choice is a SEGMENTED bar (its words in one sunk bar, the current one raised on k 2, semibold) --
   flip: every option its own k 2 plate, the current one lit k 3 (the inspector's choice row). */
#moddlg {
    --text: var(--g-text); --dim: var(--g-lab); --off: var(--g-off); --plate: var(--g-ctl); --plate-hi: var(--g-ctl-hi); --lit: var(--g-act);
    position: fixed;
    z-index: 9;                            /* under the popup (10): the source menu opens over it */
    display: none;
    flex-direction: column;
    width: calc(var(--w-col) * 2 + var(--gap-col) + var(--u) * 6);
    max-height: calc(100vh - var(--u) * 2);
    background: var(--g-card);
    color: var(--g-text);
    border-radius: var(--card-r);
    box-shadow: 0 0 0 1px var(--g-hair), 0 calc(var(--u) * 3) calc(var(--u) * 8) color-mix(in srgb, var(--bg) 70%, transparent);
    font-size: var(--fs-label);
}
#moddlg.open { display: flex; }
#moddlg [tabindex]:focus { outline: none; }
#moddlg [tabindex]:focus-visible { outline: 1px solid var(--g-act); outline-offset: -1px; border-radius: var(--r); }
#moddlg .md-head { flex: 0 0 auto; display: flex; align-items: center; gap: calc(var(--u) * 3);
                   padding: calc(var(--u) * 3) calc(var(--u) * 1.5) calc(var(--u) * 1.5) calc(var(--u) * 3); }
#moddlg .md-title { display: flex; flex-direction: column; gap: calc(var(--u) / 2); min-width: 0; }
#moddlg .md-cap { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--g-lab); white-space: nowrap; }
#moddlg .md-name { font-size: var(--fs-title); font-weight: 600; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#moddlg .md-now { margin-left: auto; color: var(--g-lab); white-space: nowrap; font-variant-numeric: tabular-nums; }
#moddlg .md-x { flex: 0 0 auto; width: calc(var(--u) * 7); height: calc(var(--u) * 7); display: flex; align-items: center; justify-content: center;
                font-size: 20px; line-height: 1; color: var(--g-text); background: var(--g-ctl); border-radius: var(--r); cursor: pointer; }
#moddlg .md-x:hover { background: var(--g-ctl-hi); }
#moddlg .md-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 calc(var(--u) * 1.5) calc(var(--u) * 1.5); touch-action: pan-y; }
#moddlg .md-sep { height: 1px; background: var(--g-hair); margin: calc(var(--u) * 1.5) 0; }
#moddlg .md-block { display: flex; flex-direction: column; gap: var(--u); }
#moddlg .md-bhead { display: flex; align-items: center; height: calc(var(--u) * 6); padding-left: calc(var(--u) * 1.5); }
#moddlg .md-bhead .sp { flex: 1 1 auto; }
#moddlg .md-remove { height: calc(var(--u) * 6); display: flex; align-items: center; padding: 0 calc(var(--u) * 1.5);
                     color: var(--g-text); background: var(--g-ctl); border-radius: var(--r); cursor: pointer; white-space: nowrap; }
#moddlg .md-remove:hover { background: var(--g-ctl-hi); }
/* the SOURCE row and the ADD row: MENU-LOOK's row, glyph | name | caret -- a control, so a k 2 plate (lane MOD-LOOK) */
#moddlg .md-row { display: flex; align-items: center; gap: calc(var(--u) * 3); min-height: calc(var(--u) * 12);
                  padding: calc(var(--u) * 1.5); border-radius: var(--r); cursor: pointer; background: var(--g-ctl); }
#moddlg .md-row:hover, #moddlg .md-row:focus-visible { background: var(--g-ctl-hi); }
#moddlg .md-row .md-glyph { flex: 0 0 auto; width: calc(var(--u) * 5); height: calc(var(--u) * 4); display: block; }
#moddlg .md-row .tx { display: flex; flex-direction: column; min-width: 0; }
#moddlg .md-row .nm { font-size: var(--fs-title); line-height: 1.25; }
#moddlg .md-row .k { margin-left: auto; padding-left: calc(var(--u) * 2); color: var(--g-lab); }
#moddlg .md-row:hover .k { color: var(--g-text); }
#moddlg .md-src[data-kind='off'] .nm { color: var(--g-lab); }
#moddlg .md-plus { flex: 0 0 auto; width: calc(var(--u) * 5); text-align: center; font-size: 20px; line-height: 1; color: var(--g-lab); }
#moddlg .md-act:hover .md-plus { color: var(--g-text); }
/* the drawn shape: the output over what the kind hears, on the text inset -- each strip a SUNK field (lane MOD-LOOK) */
#moddlg .card-scopes { padding: 0 calc(var(--u) * 1.5); }
#moddlg .card-scopes canvas.modscope, #popup.modsrc .item .pv { background: var(--g-panel); border-radius: var(--r); }
/* the rows: two inspector columns; a choice row spans them, its options are words */
#moddlg .md-grid { display: grid; grid-template-columns: repeat(2, var(--w-col)); column-gap: var(--gap-col); row-gap: var(--gap-ctl);
                   align-items: start; padding: var(--u) calc(var(--u) * 1.5); }
#moddlg .md-grid > .ctl.choice:not(.list), #moddlg .md-grid > .card-grid { grid-column: 1 / -1; }
#moddlg .md-grid .card-grid { grid-template-columns: repeat(2, var(--w-col)); column-gap: var(--gap-col); }
#moddlg .ctl.choice .pair { flex-wrap: wrap; row-gap: var(--u); }
/* lane MOD-LOOK (ML-SEG): the words stand in ONE sunk bar on the column's control x, the current one raised on k 2 */
#moddlg .ctl.choice .pair:not(:has(> .plate.menu)), #moddlg .md-words {
    justify-self: start; margin-left: var(--x-ctl); padding: calc(var(--u) / 2); gap: calc(var(--u) / 2); row-gap: calc(var(--u) / 2);
    background: var(--g-panel); border-radius: var(--r); }
#moddlg .ctl.choice .plate { background: transparent; color: var(--g-lab); width: auto; min-width: 0; padding: 0 calc(var(--u) * 1.5); font-weight: 400; }
#moddlg .ctl.choice .plate:hover { color: var(--g-text); }
#moddlg .ctl.choice .plate.on { background: var(--g-ctl); color: var(--g-text); font-weight: 600; }
#moddlg .ctl.choice .plate.on:hover { background: var(--g-ctl-hi); }
#moddlg .ctl.choice .plate.menu, #moddlg .ctl.choice .plate.menu:hover { background: var(--g-ctl); color: var(--g-text); padding: 0 calc(var(--u) * 2); }   /* a menu chip stays a chip: it opens a list */
#moddlg .ctl.choice .plate.menu:hover { background: var(--g-ctl-hi); }
/* the AMOUNT: name | the bipolar slider | its value, on the knob rows' own tracks; the direction's words under it */
#moddlg .md-amt { display: flex; flex-direction: column; gap: var(--u); padding: var(--u) calc(var(--u) * 1.5); }
#moddlg .md-amt-row { display: grid; grid-template-columns: var(--w-lab) minmax(0, 1fr) var(--w-val); align-items: center; height: var(--h-row); }
#moddlg .md-amt-row > .lab { color: var(--g-lab); white-space: nowrap; }
#moddlg .md-amt-row > .val { padding-left: calc(var(--u) * 3); white-space: nowrap; font-variant-numeric: tabular-nums; cursor: text; }
#moddlg .md-slider { position: relative; height: var(--h-row); margin: 0 calc(var(--u) * 1.5); cursor: ew-resize; touch-action: none; }
#moddlg .md-slider .trk { position: absolute; left: 0; right: 0; top: calc(50% - 3px); height: 6px; border-radius: 3px; background: var(--g-panel); }   /* a sunk groove (lane MOD-LOOK) */
#moddlg .md-slider .mid { position: absolute; left: 50%; top: calc(50% - var(--u) * 1.5); height: calc(var(--u) * 3); width: 1px; margin-left: -0.5px; background: var(--g-lab); }
#moddlg .md-slider .fill { position: absolute; top: calc(50% - 2px); height: 4px; border-radius: 2px; background: var(--g-act); }
#moddlg .md-slider .hd { position: absolute; top: 50%; width: calc(var(--u) * 3); height: calc(var(--u) * 3); margin: calc(var(--u) * -1.5) 0 0 calc(var(--u) * -1.5);
                         border-radius: 50%; background: var(--g-text); }
#moddlg .md-slider:hover .hd, #moddlg .md-slider:focus-visible .hd { box-shadow: 0 0 0 var(--u) color-mix(in srgb, var(--g-text) 20%, transparent); }
#moddlg .md-words { display: flex; align-items: center; }      /* the bar itself: ML-SEG, above */
#moddlg .md-word { height: calc(var(--u) * 6); display: flex; align-items: center; padding: 0 calc(var(--u) * 1.5); border-radius: var(--r);
                   color: var(--g-lab); cursor: pointer; white-space: nowrap; }
#moddlg .md-word:hover { color: var(--g-text); }
#moddlg .md-word.on { background: var(--g-ctl); color: var(--g-text); font-weight: 600; }
#moddlg .md-word.on:hover { background: var(--g-ctl-hi); }
/* THE ROW THE DIALOG BELONGS TO says so: its name in the value ink, semibold (the rack's bracket's job, now the name's) */
.phead.has-rack.open > .ctl > .lab, .phead.has-rack.open > .ctl > .row > .lab { color: var(--text); font-weight: 600; }
/* THE TITLE ROW'S LEVEL is a door (lane MOD-ALL): it takes the pointer, it lights when its dialog is open, and while a rack
   moves Level it wears a dot after its name -- the title row has no knob for the mod ring */
.titlerow .lab.door { cursor: pointer; }
.titlerow .lab.door:hover, .titlerow .lab.door.open { color: var(--text); }
.titlerow .lab.door.open { font-weight: 600; }
.titlerow .lab.door.modded::after { content: ''; display: inline-block; width: 6px; height: 6px; margin-left: var(--u); border-radius: 50%;
                                    background: currentColor; vertical-align: middle; }
/* THE PHONE (MOBILE-2): a SHEET over the inspector's foot, margin to margin, finger-sized rows (44 SCREEN px); it stands
   only while the inspector is shown */
html.phone #moddlg { left: var(--margin); right: var(--margin); top: auto; width: auto; max-height: 62vh;
                     bottom: calc(var(--margin) + 44px * var(--px-screen, 1) + var(--gap-ctl)); }   /* over the World | Inspector switch, never on it */
/* only INVISIBLE while the world is shown, never display: none -- MOBILE-2's law: a box built hidden keeps its size, so its
   knobs measure what they will show (a 0-px knob throws in its arc, and the frame loop dies with it) */
html.phone body:not(.ph-insp) #moddlg { visibility: hidden; pointer-events: none; }
/* (lane MOD-LOOK) the cross, Remove and a word keep their PLATE's size -- on k 2 now, a 44 px plate stood past its row, over the
   rule above it -- and take the finger through a transparent 44-screen-px press box laid over them (HELP-FINGER's law: the box
   grows, the glyph does not). ASSUMED READING ML-FINGER: the press box over the plate -- flip: 44 px plates, rows grown to hold them */
html.phone #moddlg .md-x, html.phone #moddlg .md-remove, html.phone #moddlg .md-word { position: relative; }
html.phone #moddlg .md-x::after, html.phone #moddlg .md-remove::after, html.phone #moddlg .md-word::after {
    content: ''; position: absolute; left: 50%; top: 50%; min-width: 100%; width: calc(44px * var(--px-screen, 1));
    height: calc(44px * var(--px-screen, 1)); transform: translate(-50%, -50%); }
html.phone #moddlg .md-row { min-height: calc(56px * var(--px-screen, 1)); }
html.phone #popup.modsrc .item { min-height: calc(56px * var(--px-screen, 1)); }
