/* VIS-1 — dark pro-audio console theme foundation (rq5 palette).
   Every color below flows from the :root token set adopted verbatim from
   docs/ultron/research/rq5-palette.md (WCAG-AA verified) — introduce no raw
   hex values; extend tokens, not literals. Component redesign (faders,
   status strip, card anatomy) is VIS-2..6's scope, deliberately not
   attempted here. */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* ===========================================================================
   VIS-1 token set — rq5-palette.md verbatim (SOURCE OF TRUTH).
   Warm charcoal neutrals (bg-body chassis ground / bg-panel faceplate inset /
   bg-card module cards); hairline = decorative seams ONLY (sub-3:1 by
   design); line-strong = meaningful outlines/bezels (3:1 vs all grounds);
   amber accent set with dark on-accent ink; split-role safety red (red-edge
   for rings/halos, red-fill for solid fills with white text — never
   swapped); amber-safe focus tokens; 6 family edges; status + meter stops.
   Tokens not yet referenced below (family edges, meter stops, status-error,
   accent-hover/active, focus-ring-on-accent, --font-readout) are the
   pre-verified vocabulary VIS-2..VIS-6 code against. --font-readout exists
   because the plan rules out webfonts (system mono stack only).
   =========================================================================== */

:root {
  /* Native surfaces (form-control popups, scrollbars) follow the dark
     scheme — a light <select> dropdown would break the dark render. */
  color-scheme: dark;

  /* Neutrals — warm charcoal brushed steel */
  --bg-body: #1B1917;
  --bg-panel: #292623;
  --bg-card: #322E29;
  --hairline: #4C463E;
  --line-strong: #857C6E;

  /* Text */
  --text-primary: #EDE8DE;
  --text-muted: #A79F92;

  /* Amber signal accent */
  --accent: #F0A83C;
  --accent-hover: #FFC06B;
  --accent-active: #D18A20;
  --on-accent: #241A08;

  /* Safety red — split role (never swap edge/fill) */
  --red-edge: #E5484D;
  --red-fill: #C93A32;

  /* Focus */
  --focus-ring: #FFB640;
  --focus-ring-on-accent: #241A08;

  /* Family edges */
  --family-gain: #D9C37A;
  --family-compressor: #8CC079;
  --family-eq: #82A9DE;
  --family-delay: #B18FDE;
  --family-reverb: #6FC2C8;
  --family-limiter: #DE8FB0;
  /* UI-2 (cycle 3) — the four shelved effects, chosen to rq5's family
     rules (S 36–58 enamel band, L within the existing 61–72 family band,
     on-accent ink 6.74–10.22 ≥ the shipped set's own 6.40 floor, ≥3:1 vs
     every ground with the 1.7 headroom, hues at the centers of the four
     widest gaps in the existing family wheel — nearest existing family
     ≥25° away, muted S36 vs the neighbors' S54–58 for extra separation):
     Distortion mustard, Gate spring green, Chorus periwinkle, Autotune
     orchid. Verified numbers in docs/ultron/production-log.md (UI-2). */
  --family-distortion: #C0CE97;
  --family-chorus: #9E9ED1;
  --family-gate: #9AD5B2;
  --family-autotune: #D19ED1;

  /* Status + meter stops */
  --status-live: #7BD389;
  --status-error: #FF806E;
  --meter-low: #5CC06E;
  --meter-mid: #F0A83C;
  --meter-clip: #F05A45;

  /* Mono readout stack for VIS-2+ status/level numerals (no webfonts) */
  --font-readout: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ===========================================================================
   PATTERN MACHINE register (redesign item 1 — direction "Pattern Machine",
   structure "Single Face Chassis"; docs/ultron/redesign.md + the locked
   decision card). The canvas panel's own world, adopted verbatim from the
   locked card's palette [#131316, #232330, #9EA4B8, #FF6B35, #FFD75E]:

     --pm-chassis    matte near-black instrument chassis (ONE continuous
                     faceplate ground top to bottom)
     --pm-face-edge  the panel's machined bezel edge (a cut face, not a
                     floating border — used as the panel chrome line)
     --pm-groove-cut / --pm-groove-lip  the machined groove between
                     sections: a dark slot with a light lower lip (cut-face
                     geometry, no ambient shadow)
     --pm-print      silkscreen panel print (labels)  7.47 on chassis
     --pm-print-hi   lifted print (values, live labels)  11.78 on chassis
     --pm-print-dim  receding print (flow marks)  5.26 on chassis
     --pm-accent     SIGNAL ORANGE — the one committed accent; reserved for
                     SYSTEM STATES only (display-register marks, focus
                     rings, the live-control blink, pressed pads, the
                     chosen-drag groove, Start). 6.54 on chassis (non-text
                     ≥3 ✓, small text ≥4.5 ✓). NOT the knob arcs — item 1's
                     adjustment round moved those to the section's family
                     color (see --knob-arc at the data-family rules below).
     --pm-display    dot-matrix display amber (machine values on the
                     register)  14.09 on --pm-register-bg
     --pm-register-bg the inset display slot ground
     --pm-key        pad/trim key ground (a physical key on the panel)
     --pm-ring-track unlit knob ring scale — DECORATIVE by design (the
                     accent arc carries the value; same sub-3 seam
                     precedent as --hairline)
     --pm-family-*   the ten family codes as DESATURATED silkscreen inks
                     (panel-print marking, never filled chips) — all
                     8.66–10.76 on the chassis, so even their small-print
                     use clears AA. The knob ARCS do not use these: they
                     take the rq5 saturated --family-* tokens (the palette
                     flank's own vocabulary) via --knob-arc — 6.93–11.07
                     on the chassis as non-text arcs, measured in the
                     item-1 adjustment record.
     --pm-vu-*       the VU corner's lamp paint (src/meters.js reads these
                     first; rq5 meter tokens remain the fallback)

   ITEM 1b (Two-Deck Stack) extends the register to the whole page —
   the system deck, the deck seam, the page bench, and the two accent
   tints the one orange key answers through on hover/press:
     --pm-system-deck  the UPPER slab's cast (a hair lighter than the
                       chassis — two castings, one instrument)
     --pm-seam-cut / --pm-seam-lip  the machined deck seam: a deep dark
                       cut under a bright light lip — DEEPER (4px vs 1px)
                       and brighter-lipped (0.30 vs 0.14 alpha) than the
                       section grooves, so the joint reads as the
                       deepest cut on the page, never a gap
     --pm-bench        the ground AROUND the instrument (the page)
     --pm-accent-hi / --pm-accent-lo  hover/press tints of the ONE
                       signal orange (ink 7.72 / 5.22 on them)

   Scoped by USAGE, not by selector (defined at :root so canvas-drawn
   meters can resolve them off documentElement). Since item 1b the
   --pm-* register IS the page's system: the instrument chassis, both
   decks, and all three voice-deck zones consume it. The remaining rq5
   consumers are the floating agent toasts, the ?dev harness overlay,
   and the watchdog Restore key's active state — surfaces outside the
   chassis that keep the rq5 vocabulary (recorded residual).
   =========================================================================== */
:root {
  --pm-chassis: #131316;
  --pm-face-edge: #232330;
  --pm-groove-cut: #05060a;
  --pm-groove-lip: rgba(158, 164, 184, 0.14);
  --pm-print: #9ea4b8;
  --pm-print-hi: #c9cedc;
  --pm-print-dim: #767c90;
  --pm-accent: #ff6b35;
  --pm-accent-hi: #ff8558;
  --pm-accent-lo: #e85a20;
  --pm-display: #ffd75e;
  --pm-register-bg: #0b0c10;
  --pm-key: #1d1e26;
  --pm-key-edge: #343746;
  --pm-ring-track: #454a5a;
  --pm-cap: #1e2027;
  --pm-ink: #131316;
  --pm-system-deck: #17181d;
  --pm-seam-cut: #010208;
  --pm-seam-lip: rgba(201, 206, 220, 0.3);
  --pm-bench: #0a0b0f;

  /* OQ-9 (QA-5 element round) — THE SECTION SLAB. The board's sections
     are weighted slabs with a real face step above the chassis (DESIGN.md
     promised "weighted-slab sections with cut faces, never floating
     panels"; this delivers it): one step lighter than the chassis ground
     they sit IN, a sawn dark cut edge all round, and a machined top lip
     one rank brighter than a section groove but below the deck seam —
     elevation stays TONAL + edge, never shadow (Rest-Flat holds: no drop
     shadow at rest anywhere on the slab). */
  --pm-slab: #181a21;
  --pm-slab-lip: rgba(201, 206, 220, 0.22);

  --pm-family-gain: #cdc5ad;
  --pm-family-compressor: #b0c6a9;
  --pm-family-eq: #aabacf;
  --pm-family-delay: #baaecb;
  --pm-family-reverb: #a6c6c9;
  --pm-family-limiter: #d0b3bf;
  --pm-family-distortion: #bfc7a8;
  --pm-family-chorus: #afafca;
  --pm-family-gate: #aecbbd;
  --pm-family-autotune: #cfb4cf;

  /* VU lamp paint: desaturated matte lamps — green stays green, the mid
     zone IS the display amber (one lamp language with the register), clip
     stays safety red. Contrast on chassis in the build record. */
  --pm-vu-low: #4ea96b;
  --pm-vu-mid: #ffd75e;
  --pm-vu-clip: #e4574a;
  --pm-vu-unlit: #262933;
  --pm-vu-tick: #c9cedc;
  --pm-vu-label: #9ea4b8;
}

body {
  margin: 0;
  padding: 1rem;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  color: var(--text-primary);
  /* Item 1b: the page is the BENCH the instrument rests on — one step
     darker than the chassis, so the full-bleed frame reads as a slab on
     a surface, not a page background color. */
  background: var(--pm-bench);
}

/* Browser surfaces — cheap signal (VIS-1, re-tokened to the Pattern
   Machine register by item 1b): selected text reads as the signal
   orange with chassis ink, and one global focus ring — the SAME
   --pm-accent every system state uses — makes every focusable control
     visible against all the instrument's grounds (6.25–7.97). */
::selection {
  background: var(--pm-accent);
  color: var(--pm-ink);
}

:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 1px;
}

/* ===========================================================================
   ITEM 1b — THE INSTRUMENT: chassis frame, SYSTEM DECK, deck seam.
   The whole viewport is two slabs bolted into one chassis (locked card
   `two-deck-stack`): .instrument is the outer machined shell (full-bleed,
   one frame — not three panels); the SYSTEM DECK below is the upper slab
   carrying everything read at arm's length; .deck-seam is the machined
   joint; the VOICE DECK (the .layout block further down) is the lower
   slab, one faceplate split into printed zones by the same grooves the
   canvas sections use. All tokens from the --pm-* register.
   =========================================================================== */

/* The outer shell: bleeds out of body's 1rem padding so the instrument
   spans the full viewport width — ONE frame around both decks. No
   overflow clipping (the system deck is position:sticky; a clipped
   ancestor would break the pin), no radius (a machined slab, squared). */
.instrument {
  margin: -1rem;
  border: 1px solid var(--pm-face-edge);
  background: var(--pm-chassis);
}

/* FIXED ONE-PAGE CONSOLE (2026-08-31 round, user direction): on the
   desktop event laptop the instrument IS the page — one viewport-sized
   chassis, no page scroll, every zone scrolls INSIDE. The frame becomes
   a flex column (system deck · seam · voice deck); the voice deck takes
   the remaining height and its three zones clamp (min-height: 0) so the
   palette/presets flanks scroll their own content and the chain face
   pans the board. Below 901px the zones stack and the page keeps its
   scroll — the fixed console is the wide-screen reading. */
@media (min-width: 901px) {
  html,
  body {
    height: 100%;
    overflow: hidden;
  }

  .instrument {
    height: 100vh;
    display: flex;
    flex-direction: column;
  }

  .topbar,
  .deck-seam {
    flex: none;
  }

  .layout {
    flex: 1 1 auto;
    min-height: 0;
  }

  /* The grid-blowout guard (the container round's real defect): without
     min-width: 0 the 1fr face track grows to the board's content width
     (the chain-list carries the row extent as its min-width) and pushes
     the presets zone and the chassis frame off screen — the outer
     container must bound the board, the face scrolls inside. */
  .layout .palette,
  .layout .canvas-panel,
  .layout .presets {
    min-height: 0;
    min-width: 0;
  }

  .layout .canvas-panel .canvas {
    min-width: 0;
  }

  .layout .palette,
  .layout .presets {
    overflow-y: auto;
  }
}

/* THE DECK SEAM — the joint that makes two slabs read as one chassis
   (the locked card's honest risk, solved in-design): a 1px bright
   machined lip over a 4px deep dark cut. It is deliberately DEEPER and
   WIDER than the section grooves (1px cut + 1px lip) and its lip is
   brighter (0.30 vs 0.14 alpha): the deepest cut on the page, a joint
   under load — never a gap that would read as two instruments. Pure
   geometry: no shadow, no glow. */
.deck-seam {
  height: 4px;
  border-top: 1px solid var(--pm-seam-lip);
  background: var(--pm-seam-cut);
}

/* ===========================================================================
   SYSTEM DECK (VIS-2's status strip re-cast by item 1b as the upper
   slab): one persistent full-width row — identity legend · the DOT-MATRIX
   ETCH (engine lamp + status sentence + RATE/LAT/NODES, wrapped in
   .system-etch) · Start/device controls · BYPASS as the red-ringed end
   key. All UI-1 ids/classes survive verbatim — src/main.js and
   src/agent-ui.js (chip inserts before #bypass-toggle-button inside
   .topbar) are untouched; the .topbar-section/.readout/.system-etch
   wrappers are pure additions.
   =========================================================================== */

.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 0.9rem;
  row-gap: 0.5rem;
  /* One-row vertical rhythm: 1rem total padding + the 3rem (48px) Bypass
     target = a 4rem (64px) deck; taller content grows the deck in size,
     never overflows it. */
  min-height: 4rem;
  padding: 0.5rem 1.25rem;
  /* The upper slab's own cast: a hair lighter than the voice deck's
     chassis — two castings joined by the seam, one instrument. */
  background: var(--pm-system-deck);
  position: sticky;
  top: 0;
  /* Agent toasts (z-index 20) and the dev harness (15) stay above the
     sticky deck. */
  z-index: 10;
}

.topbar-section {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}

/* Identity absorbs the deck's slack: the legend ellipsizes first, so
   the controls and BYPASS keep their place on the row as long as the
   viewport allows (see the <900px rule below for the narrow fallback).
   Refinement entry 3 ($impeccable harden): the basis drops from `auto`
   to 0 to make that declared yield actually happen — with basis:auto
   the legend's content width forces flex-wrap to break the row before
   the legend ever gets a chance to ellipsize (wrapping is decided on
   base sizes, and only then does shrinking engage). basis:0 + grow
   reproduces today's rendered width exactly whenever the row has slack
   (the legend grows back to its content width), so every non-error
   state renders unchanged; it differs only when the row is over-full —
   exactly the failure states whose error copy must not wrap the deck
   (see .start-hint / .topbar-status caps below). */
.topbar-identity {
  flex: 1 1 0;
}

/* App title as the ENGRAVED NAMEPLATE: the same silkscreen micro-style
   as the zone legends (uppercase, tracked) in lifted print (11.26 on the
   deck). A nameplate, not a headline: nothing here may out-shout the
   Bypass key. */
.app-title {
  margin: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pm-print-hi);
}

/* THE DOT-MATRIX ETCH (item 1b): one etched slot on the system deck
   carrying the engine lamp + status sentence and the RATE/LAT/NODES
   readouts — the deck's machine register. It borrows the display
   register's inset geometry (register ground + machined lip) but stays
   SUBORDINATE to the chain face's register by design (the locked
   card's second risk): neutral bright print, never the register's
   amber/orange machine voice, and the value tier one step down
   (12px vs the register's 13.6px main line) — the machine reports
   from ONE mouth, and it is the canvas register's. */
.system-etch {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  max-width: 100%;
  min-width: 0;
  padding: 0.35rem 0.9rem;
  background: var(--pm-register-bg);
  box-shadow: inset 0 1px 0 var(--pm-groove-lip);
}

.topbar-status {
  /* Shrinkable (not rigid): a transient long error label ellipsizes like
     a real etch instead of pumping the deck's width. Entry 3: the
     hard max-width also caps the flex BASE size — with flex-wrap on the
     deck, an uncapped base would wrap the row (wrapping beats
     shrinking) instead of ellipsizing. 20rem fits every WHAT-HAPPENED
     sentence in full; the longer device-switch lines (sentence + next
     action, front-loaded) and footnotes ellipsize by design. */
  flex: 0 1 auto;
  min-width: 0;
  max-width: 20rem;
}

/* Engine-state lamp: silkscreen label + dot, one vocabulary with the
   readout micro-labels. */
.status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  color: var(--pm-print);
  min-width: 0;
}

/* Error strings ("Failed to start (…)") truncate, never reflow the deck. */
#status-text {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Stopped lamp: dimmed print, still ≥3:1 vs the etch ground (4.71) as a
   state indicator. */
.dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--pm-print-dim);
  flex: none;
  transition: background-color 150ms ease;
}

/* LIVE belongs to the signal orange — the one accent, reserved for
     system states; the label lifts to bright print while the lamp
     carries the orange (6.25 non-text on the deck, ~7 on the etch). */
.status.live .dot {
  background: var(--pm-accent);
}

.status.live {
  color: var(--pm-print-hi);
}

/* Refinement entry 3 ($impeccable harden, 2026-08-28): the ERROR
   register. A failed Start / device switch raises the sentence to Error
   Coral (rq5's status-error token on its etch pairing, 7.97) while the
   DOT above keeps telling the engine truth (dim = stopped, orange =
   still live: a failed switch never stops the show). Copy + color
   redundancy, same pattern as .live. */
.status.error {
  color: var(--status-error);
}

/* The demoted technical footnote ("NotAllowedError: Permission denied")
   appended by src/main.js after an operator sentence — on the status
   line for device-switch failures, and in the .start-hint after the
   recovery action for failed Starts: muted mono, sentence case,
   untracked — the machine footnote register the agent toasts already
   use. Same 0.75rem as the host line (12px, above the 11px floor), one
   rank below the operator copy it follows. */
.status-detail {
  font-family: var(--font-readout);
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  color: var(--pm-print);
}

/* Machine readout group — the etch's readout half (VIS-2, wired by
   FEW-2): silkscreen micro-label over a mono value, slots separated by
   thin groove cuts, the cluster bounded from the status sentence by the
   same cut. Fixed value slots: mono + tabular numerals + a per-slot
   min-width, so '—' becoming '48.0 kHz' changes glyphs, never the
   deck's geometry. */
.readout-group {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding-left: 0.9rem;
  border-left: 1px solid var(--pm-groove-cut);
}

.readout {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.readout + .readout {
  padding-left: 0.9rem;
  border-left: 1px solid var(--pm-groove-cut);
}

.readout-label {
  /* 0.7rem silkscreen floor (11.2px) — these are the registers read from
     across a dark venue; 0.625rem was below the declared 11px floor.
     Dimmed print on the etch ground: 4.71, above the 4.5 text floor,
     one rank below the values they name. */
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  color: var(--pm-print-dim);
}

/* The etch's value tier — 12px mono tabular (the ladder's per-control
   value tier): one step BELOW the chain register's 13.6px main line, so
   the system deck reports in a smaller register than the instrument it
   serves (12.42 on the etch ground). */
.readout-value {
  font-family: var(--font-readout);
  font-size: 0.75rem;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--pm-print-hi);
}

#readout-sample-rate {
  min-width: 8ch;
}

#readout-latency {
  min-width: 7ch;
}

#readout-node-count {
  min-width: 3ch;
}

/* Controls pinned to the deck's right end; Bypass remains the last
   item after them (and the agent chip lands between the two). */
.topbar-controls {
  flex: none;
  margin-left: auto;
}

/* Long device labels (Chrome's "Default - … (Built-in)") must not pump
   the deck's width — cap the select, it ellipsizes natively. */
#input-device-select {
  max-width: 15rem;
}

/* Refinement entry 2 ($impeccable clarify): the deck's one visible
   pre-Start instruction — "Press Start to power on" — as muted sentence
   copy beside Start (0.75rem = 12px, above the 11px floor; same register
   as the unsaved-dot flag, NOT a silkscreen legend: it is an instruction,
   not machine state). Visibility keys off the button's OWN disabled state:
   src/main.js disables Start synchronously for the mic request and leaves
   it disabled once live (re-enabling only on failure), so the
   following-sibling rule below tracks the real engine state with zero JS
   and can never drift from it. */
.start-hint {
  flex: none;
  /* Entry 3: recovery copy (e.g. the Chrome mic-permission path, plus a
     demoted technical footnote) runs far longer than "Press Start to
     power on" — the cap bounds the flex BASE size so the block can
     never widen the deck into wrapping a second row (with flex-wrap,
     wrapping is decided on base sizes before any shrinking engages);
     the excess ellipsizes, the copy is front-loaded so the operative
     instruction survives the cut, and the full pair rides the
     element's title attribute (set by src/main.js). */
  max-width: 21rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--pm-print);
  font-size: 0.75rem;
  white-space: nowrap;
}

#start-button:disabled ~ .start-hint {
  display: none;
}

/* ===========================================================================
   SHARED CONTROL KEYS (item 1b restyle of the VIS-3 control vocabulary):
   every button/select on the instrument — Start, the device select, the
   presets zone's Save As…/select/Load/Delete, the naming row — renders
   as a physical KEY on the panel: --pm-key ground, cut-edge bezel,
   bright print. Hover leans the bezel to full print; the pressed state
   is the ONE orange fill with chassis ink (the accent-as-brightness
   economy).
   =========================================================================== */

button.control,
select.control {
  padding: 0.5rem 1rem;
  border-radius: 4px;
  border: 1px solid var(--pm-key-edge);
  background: var(--pm-key);
  font-size: 0.9rem;
  color: var(--pm-print-hi);
}

button.control {
  cursor: pointer;
}

button.control:not(:disabled):hover,
select.control:not(:disabled):hover {
  border-color: var(--pm-print);
}

/* Pressed: the one orange fill with chassis ink (6.54) — brightness is
   saturation on matte, never glow. While orange-filled, the chassis-ink
   focus-ring variant keeps the global ring visible (orange-on-orange is
   unusable — the ink ring exists for exactly this). */
button.control:not(:disabled):active {
  background: var(--pm-accent);
  border-color: var(--pm-accent);
  color: var(--pm-ink);
}

button.control:not(:disabled):active:focus-visible {
  outline-color: var(--pm-ink);
  /* The ink ring only scores on the orange — pull it INSIDE the fill
     (global offset 1px would paint it on the deck ground at ~1.2:1). */
  outline-offset: -2px;
}

/* Disabled — the shared RECEDE half of the one disabled grammar (the
   hatch half lives at zone scale, in the disabled-texture block): the
   key sinks to its cut-edge seam, print recedes, pointer refuses. */
button.control:disabled,
select.control:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  border-color: var(--pm-key-edge);
  color: var(--pm-print);
}

select.control {
  padding: 0.5rem;
}

/* START — the deck's ONE ORANGE KEY (VIS-2 primary, re-cast by item 1b):
   signal-orange fill with chassis ink (6.54; hover 7.72, press 5.22 —
   ink-on-fill at every step, per the incumbent pressed-state guidance),
   the ink focus ring while filled. Once the engine starts, src/main.js
   disables the button and it recedes to the shared disabled treatment —
   post-engine the deck belongs to the state lamp and BYPASS. Id
   specificity beats every shared .control rule above without touching
   them. */
#start-button:not(:disabled) {
  background: var(--pm-accent);
  border-color: var(--pm-accent);
  color: var(--pm-ink);
  font-weight: 700;
  transition:
    background-color 150ms ease,
    border-color 150ms ease,
    color 150ms ease;
}

#start-button:not(:disabled):hover {
  background: var(--pm-accent-hi);
  border-color: var(--pm-accent-hi);
  color: var(--pm-ink);
}

#start-button:not(:disabled):active {
  background: var(--pm-accent-lo);
  border-color: var(--pm-accent-lo);
  color: var(--pm-ink);
}

#start-button:not(:disabled):focus-visible,
#start-button:not(:disabled):active:focus-visible {
  outline-color: var(--pm-ink);
  /* Chassis-ink ring must sit on the orange fill (6.54), not the deck
     ground outside it — negative offset draws it inside. */
  outline-offset: -2px;
}

/* ===========================================================================
   BYPASS — the deck's RED-RINGED END KEY (UI-2 + VIS-2, re-cast by item
   1b): the single most safety-critical control in the app, and the
   LOUDEST element on the deck — largest target (3rem / 48px min-height),
   heaviest type on the deck (1.05rem / 700 / tracked; the nameplate is
   0.75rem silkscreen and etch values 12px mono, so nothing exceeds this
   weight), and the only red-ringed thing on the page's system surface.
   Split-role safety red (rq5 tokens, re-used verbatim), applied in its
   verified pairing:
     OFF  — key ground + bright print + 2px red-edge ring (4.53 vs the
            deck around it as a non-text state ring; red-edge is an EDGE
            here, never a text or fill color);
     ON   — red-fill ground + #FFFFFF text (5.08 PASS) + red-edge ring.
   Never white text on red-edge (fails); never red-fill as an edge
   (fails). Below 900px the order-wrap rules keep it above the wrapped
   etch row, and the deck is sticky — the emergency key never leaves
   the visible top. */
.bypass-btn {
  flex: none;
  min-height: 3rem;
  padding: 0.65rem 1.6rem;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border-radius: 4px;
  border: 2px solid var(--red-edge);
  background: var(--pm-key);
  color: var(--pm-print-hi);
  cursor: pointer;
  transition:
    background-color 150ms ease,
    border-color 150ms ease,
    color 150ms ease;
}

.bypass-btn.engaged {
  background: var(--red-fill);
  border-color: var(--red-edge);
  color: #FFFFFF;
}

.bypass-btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

/* Narrow deck (<900px, item 1b): the deck re-packs so BYPASS NEVER
   leaves the visible top — row 1 keeps the identity nameplate, the
   agent chip, and BYPASS (its red ring at the row's end); the Start/
   hint/device group takes the second row as a full-width block that
   wraps internally (Start + hint over the device select — nothing may
   overflow the deck); the DOT-MATRIX ETCH (lamp + status + readouts,
   now ONE wrapper) wraps LAST. The deck is sticky, so every row stays
   pinned. DOM order (and with it tab order and the chip's insertion
   point) is untouched — presentation-only reordering via order. */
@media (max-width: 900px) {
  .topbar-identity {
    order: 1;
  }

  .agent-chip {
    order: 2;
  }

  .bypass-btn {
    order: 3;
  }

  .topbar-controls {
    order: 4;
    /* A full-width block so its own internal wrap owns the deck's width
       — the select can never push past the frame (the packing defect
       the first narrow-deck render exposed: controls as an unbreakable
       flex:none group overflowed the viewport). */
    flex-basis: 100%;
    flex-wrap: wrap;
    row-gap: 0.5rem;
    margin-left: 0;
  }

  .system-etch {
    order: 9;
    flex-basis: 100%;
    justify-content: flex-start;
  }

  /* Keep the first rows honest on a narrow deck: the device select and
     the hint cap tighter so they wrap inside the controls block. */
  #input-device-select {
    max-width: 11rem;
  }

  .start-hint {
    max-width: 9rem;
  }
}

/* ===========================================================================
   PATTERN MACHINE CONTROL LAYER (redesign item 1) — the encoder field
   src/param-controls.js renders inside each section. Three shapes, one
   chassis vocabulary (all tokens from the --pm-* register):

   KNOB (.knob-row) — a 54px rotary encoder. The native range input is the
   semantic engine, clipped to the knob's own box (opacity 0, hit-through)
   and still the focusable control; the KNOB draws the keyboard focus ring
   when the engine has :focus-visible (sibling selector below). Ring: a
   conic-gradient arc from min to value in the SECTION'S FAMILY COLOR over
   the unlit ring-track scale (--knob-pos is written by JS per value; the
   arc color is --knob-arc, derived from the section's data-family at the
   .node-card rules — the same rq5 --family-* tokens the palette flank
   uses, so a section's knobs answer in its menu color). ONLY the arc
   carries family color: pointer line, track, detent tick, labels and
   values stay neutral print; signal orange stays reserved for system
   states (focus ring below, the live-control pointer lift, the register,
   pressed pads, Start). Cap: matte knob cap with a print pointer line
   (--knob-rot). Bipolar params print a detent tick at 12 o'clock (unity).
   Label UNDER the knob in silkscreen print; the mono value under that,
   tabular numerals.

   PADS (.pad-row) — discrete params as real buttons in a radio group:
   keys on the panel (--pm-key ground, print text), the pressed pad LIT in
   the accent fill with chassis ink (6.54) — the accent-as-brightness
   economy: the selected value is the one bright thing in the group.

   TRIM (.trim-row) — the wide linear ranges as a short horizontal
   instrument trimmer: label + mono value line over a hairline milled slot
   with a bright print cap.

   Motion: the touched control's value writes the display register (the
   one blink lives there — see .register-blink). Nothing on the controls
   themselves animates except the 150ms hover/focus color answers, all
   inside the reduced-motion guard per house discipline.
   =========================================================================== */

/* The encoder field: rows flow and wrap as clusters (condensed, fluid —
   the direction contract's refusal of stacked full-width fader rows). */
.node-params-inner {
  display: flex;
  flex-flow: row wrap;
  align-items: flex-start;
  gap: 0.3rem 0.7rem;
}

.param-row {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  padding: 0.15rem 0 0.2rem;
  /* Every control row shares the section's lift/recede behavior through
     the section rules below (no per-control brightness). */
}

/* --- Silkscreen label + mono value registers (shared) ----------------
   VALUE-DISPLAY LADDER (item 1 adjustment round — the one documented
   grammar for every value/state readout on the canvas surface; one mono
   size per tier, tabular numerals everywhere, min-widths so digits
   changing never pump layout):

     per-control value   0.75rem (12px) / 400 / mono / tabular /
                         min-width 6ch — .param-value (knob + trim),
                         .pad legends, .meter-readout (dB)
                         (flow-state print), .register-help (the
                         register's subordinate line)
     register line       0.85rem (13.6px) / 400 + 700 segments / mono /
                         tabular — .register-main (module · param ·
                         value): the PANEL-LEVEL INSTRUMENT's etched
                         scale, one tier up from per-control values by
                         direction; its own segments share the one size
                         (weight/color differentiate, never size)
     identity print     NOT value display: .section-code (family code),
                         sans silkscreen labels (.param-label 0.7rem),
                         the EXP badge
                         (0.6875rem floor) — unchanged by this round

   Rationale for 0.75rem: at operator distance 12px mono holds the value
   as the brightest read line under each control while the 11.2px sans
   label recedes to silkscreen; everything that DISPLAYS A VALUE on this
   surface now renders at exactly that one size. */

.param-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.25;
  color: var(--pm-print);
}

.param-value {
  font-family: var(--font-readout);
  font-size: 0.75rem;
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  /* The ladder's stable slot: 6ch holds every common format ("8 dB",
     "-6 dB", "25%", "4:1", "300 ms") at fixed width so 9 -> 10 digit
     changes re-center inside the box, never reflow the row. Longer
     readouts ("-23.5 dB") simply render past it — nowrap, no clip. */
  min-width: 6ch;
  color: var(--pm-print-hi);
}

/* --- KNOB ------------------------------------------------------------ */

.knob-row {
  position: relative; /* anchor for the clipped engine input */
}

/* The clipped semantic engine: the knob unit's dial band (top of the
   row, the unit's full width), invisible but focusable (opacity, not
   display/visibility — it must stay in the tab order and keep
   announcing), hit-through so the knob visual owns the pointer. The
   global :focus-visible ring is REPLACED by the accent ring drawn on
   the knob itself (below). */
.knob-input {
  position: absolute;
  left: 0;
  top: 0.15rem;
  width: 40px;
  height: 40px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

/* Compactness round (2026-08-31): the knob unit reads SIDE-LABEL — the
   dial left, label over value to its right (a grid with named areas, so
   the DOM order — knob, value, label — and every sibling selector that
   rides it (the live-value lift below, the focus ring) stay untouched).
   The row height is now the dial alone (40px), not dial+label+value
   stacked (~90px). */
.knob-unit {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas:
    'knob label'
    'knob value';
  align-items: center;
  column-gap: 0.5rem;
  row-gap: 0;
}

.knob {
  position: relative;
  grid-area: knob;
  width: 40px;
  height: 40px;
  cursor: ns-resize;
  touch-action: none; /* the vertical drag is ours, not the page's scroll */
  user-select: none;
}

/* Position ring: the FAMILY-COLORED arc from min to value over the unlit
   scale, starting at 225deg (down-left) sweeping 270deg. --knob-pos (0..1)
   is written by src/param-controls.js on every value change — native
   keyboard, drag tick, wheel notch, and agent fast-path writes all land
   in the same place. The COLOR comes from --knob-arc, set per family on
   the .node-card (one derivation, no per-control class); the fallback is
   neutral bright print so an as-yet-unmapped future family still gets a
   readable arc without borrowing the system accent. */
.knob-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(
    from 225deg,
    var(--knob-arc, var(--pm-print-hi)) 0deg,
    var(--knob-arc, var(--pm-print-hi)) calc(var(--knob-pos, 0) * 270deg),
    var(--pm-ring-track) calc(var(--knob-pos, 0) * 270deg),
    var(--pm-ring-track) 270deg,
    transparent 270deg
  );
}

/* Matte knob cap — the rotating body the pointer line rides on. The
   1px top inset is the cap's own machined lip (bevel geometry, not an
   ambient shadow). */
.knob-cap {
  position: absolute;
  inset: 20%;
  border-radius: 50%;
  background: var(--pm-cap);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* Pointer line: from the cap's center to its rim, rotated by --knob-rot
   (-135deg at min .. +135deg at max, 0 = 12 o'clock). */
.knob-pointer {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 2px;
  height: 11px;
  background: var(--pm-print-hi);
  transform: translate(-50%, -100%) rotate(var(--knob-rot, 0deg));
  transform-origin: 50% 100%;
}

/* Detent tick — printed at 12 o'clock OUTSIDE the ring only where the
   param has a unity center (bipolar ranges; data-detent written by JS).
   NEUTRAL by recorded choice (item 1 adjustment): family color lives on
   the arc alone — the one colored instrument per section; marking the
   detent in family color too would multiply the colored pixels and read
   as decoration, not identity. */
.knob[data-detent='true']::after {
  content: '';
  position: absolute;
  top: -6px;
  left: calc(50% - 1px);
  width: 2px;
  height: 4px;
  background: var(--pm-print);
}

/* The engine's keyboard focus, drawn on the KNOB (the visible control):
   an accent ring at the dial's edge — the global :focus-visible rule is
   suppressed on the invisible input itself. */
.knob-input:focus {
  outline: none;
}

.knob-input:focus-visible {
  outline: none;
}

.knob-input:focus-visible ~ .knob-unit .knob {
  outline: 2px solid var(--pm-accent);
  outline-offset: 3px;
  border-radius: 50%;
}

/* While a pointer drag holds the knob, the value arc holds full
   saturation and the cap's pointer lifts to bright — the held encoder
   reads as the live control. Static color state, no animation. */
.knob[data-live='true'] .knob-pointer {
  background: var(--pm-display);
}

.knob-unit .param-value {
  grid-area: value;
  text-align: left;
}

.knob-unit .param-label {
  grid-area: label;
  text-align: left;
}

/* --- PADS ------------------------------------------------------------ */

.pad-row {
  flex-direction: column;
  gap: 0.35rem;
}

.pad-group {
  display: flex;
  flex-flow: row wrap;
  gap: 4px;
  max-width: 19rem; /* the 12-key field folds 6 per row */
}

/* A pad key: physical key ground + print legend. Min 34px hit floor in
   both axes (the 24px floor is for the rail's small buttons; pad fields
   carry the primary interaction). Legend at the ladder's per-control
   value size (0.75rem mono tabular — the pressed pad IS the value
   display; the 12-key field still folds 6 per row at this size). */
.pad {
  min-width: 44px;
  min-height: 34px;
  padding: 0.25rem 0.4rem;
  border: 1px solid var(--pm-key-edge);
  border-radius: 4px;
  background: var(--pm-key);
  color: var(--pm-print);
  font-family: var(--font-readout);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  cursor: pointer;
}

.pad:not([aria-checked='true']):hover {
  border-color: var(--pm-print);
  color: var(--pm-print-hi);
}

/* The pressed pad is LIT: accent fill, chassis ink legend (6.54) — the
   one bright element of the group, i.e. the selected value. */
.pad[aria-checked='true'] {
  background: var(--pm-accent);
  border-color: var(--pm-accent);
  color: var(--pm-ink);
  font-weight: 700;
}

/* Keyboard focus rides the visible pad (it is the focusable control). */
.pad:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}

/* --- TRIM (mini-slider) ---------------------------------------------- */

.trim-row {
  width: 10rem;
}

.trim-unit {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

/* The value rides the label line's right end — a readout line over the
   throw, like the printed scale on a trimmer. The ladder's 6ch min-width
   anchors the slot's RIGHT edge: digits changing grow leftward into the
   throw, never pushing the label or the slot. */
.trim-unit .param-label {
  align-self: flex-start;
}

.trim-unit .param-value {
  margin-left: auto;
  text-align: right;
}

.trim-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 1.25rem;
  margin: 0;
  background: transparent;
  cursor: ew-resize;
}

/* Milled slot: hairline channel + step ticks every 10% (the measuring
   scale; decorative like every hairline) + a center detent notch where
   the throw is bipolar. */
.trim-slider::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background:
    linear-gradient(90deg,
      transparent calc(50% - 1px), var(--pm-print-dim) calc(50% - 1px),
      var(--pm-print-dim) calc(50% + 1px), transparent calc(50% + 1px)),
    repeating-linear-gradient(90deg,
      var(--pm-key-edge) 0, var(--pm-key-edge) 1px,
      transparent 1px, transparent 11.5px),
    linear-gradient(90deg, var(--pm-key-edge), var(--pm-key-edge));
}

/* Bright print cap: the moving indicator (non-text 11.78 on chassis —
   the state-carrying part of the trimmer). NEUTRAL by recorded choice
   (same decision as the knob detent tick): the trimmer cap is a pointer,
   not an arc — family color stays on the rotary arcs only. */
.trim-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 10px;
  height: 18px;
  margin-top: -6px;
  border-radius: 2px;
  border: 1px solid var(--pm-chassis);
  background: var(--pm-print-hi);
}

.trim-slider:hover::-webkit-slider-thumb {
  background: var(--pm-display);
}

.trim-slider:focus-visible {
  outline: 2px solid var(--pm-accent);
  outline-offset: 2px;
}

/* Item 2 bake (2026-08-30 pick: A ENCODER) — the picked variant's ONE
   refinement, gate dropped: while a pointer drag holds the knob, the
   mono value under it lifts to display amber (the register's
   machine-value color, 13.36 on chassis) — the readout the operator
   watches answers the hand, not just the arc. Static state, no
   animation. The unpicked B DIAL / C VFD layers were deleted with the
   round's switcher (FEW-0). */
.knob[data-live='true'] ~ .param-value {
  color: var(--pm-display);
}

/* ===========================================================================
   VOICE DECK (UI-3 re-cast by item 1b) — the LOWER slab of the Two-Deck
   Stack: one continuous faceplate (the .instrument chassis ground) whose
   three columns read as PRINTED ZONES, not separate panels. The palette
   zone and presets zone are print on the same face as the chain face;
   the zone boundaries are MACHINED GROOVES in the same grammar the
   canvas sections use between themselves (a 1px dark cut beside a 1px
   light lip — cut first in reading order, lip after). Grid columns keep
   the committed 200px | 1fr | 220px; below 900px the zones stack
   palette → face → presets and the grooves turn horizontal.
   =========================================================================== */

.layout {
  display: grid;
  grid-template-columns: 200px 1fr 220px;
  gap: 0;
  min-height: 60vh;
  /* The voice deck IS the lower slab of the one chassis — the
     .instrument frame provides the shell; the face carries no border,
     radius, or overflow of its own (the system deck above must stay
     position:sticky, so nothing in the frame may clip). */
  background: var(--pm-chassis);
}

/* Gated until AudioEngine.start() succeeds (mirrors the Bypass button /
   old test button's disabled-until-started pattern) — dragging/adding
   nodes before the engine is running has nothing to build against.
   src/canvas.js's onSort/remove handlers ALSO guard on
   window.AudioEngine.isStarted directly, so this is a visual/UX affordance
   on top of a real functional guard, not the only thing preventing a
   pre-Start drop from throwing. Includes .presets (PS-3) too.
   Item 1b: all three zones now share the ONE Pattern Machine disabled
   grammar (hatch + recede — see the disabled-texture block); the
   legacy flank dim is retired. */

.panel {
  padding: 0.85rem 0.9rem 1rem;
}

/* PALETTE ZONE — print zone on the voice deck. The zone's right edge
   is the CUT half of the palette|face groove (the face's left border is
   the lip half, at .canvas-panel below): sequence reads cut-then-lip,
   the vertical twin of the sections' top groove. */
.palette {
  border-right: 1px solid var(--pm-groove-cut);
  background: transparent;
}

/* Zone header — silkscreened legend (VIS-3): uppercase tracked print
   label over a groove-cut rule, with more space above (the zone's
   padding) than below (0.6rem), per the craft floor. Same treatment on
   the Presets header keeps the two zones one vocabulary. */
.palette h2 {
  margin: 0 0 0.6rem;
  padding-bottom: 0.45rem;
  border-bottom: 1px solid var(--pm-groove-cut);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pm-print);
}

/* Refinement entry 3 (critique P2-3) — palette group legends. The ten
   chips chunk into three operator groups under micro-legend headers in
   the SAME register the surface already uses for grouped options (the
   preset/device selects' optgroup labels — the in-house precedent):
   uppercase, tracked 0.08em, print, 0.7rem = 11.2px above the 11px
   floor. Real <h3>s (h1 title → h2 Palette → h3 groups) so screen
   readers can navigate the chunking; non-interactive by construction —
   no border, no pointer, proximity does the grouping (headers bind
   tight to the chips below them, groups separate through the generous
   space above each subsequent header; the first header needs no extra
   top margin — the zone h2's rule already separates it). */
.palette-group-label {
  margin: 0.9rem 0 0.35rem;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pm-print);
}

.palette-group-label:first-child {
  margin-top: 0;
}

/* Narrow viewport (<900px, item 1b — the locked card's viewport law):
   the voice deck STACKS palette → chain face → presets, and the zone
   grooves turn horizontal (same cut-then-lip reading order, now top to
   bottom). This also relieves the old sub-680px three-column squeeze:
   every zone takes the full width. */
@media (max-width: 900px) {
  .layout {
    grid-template-columns: 1fr;
  }

  .palette {
    border-right: none;
    border-bottom: 1px solid var(--pm-groove-cut);
  }

  .canvas-panel {
    border-left: none;
    border-right: none;
    border-top: 1px solid var(--pm-groove-lip);
    border-bottom: 1px solid var(--pm-groove-cut);
  }

  .presets {
    border-left: none;
    border-top: 1px solid var(--pm-groove-lip);
  }
}

/* ITEM 1b — palette chips as KEYS ON THE VOICE DECK, the flank twin of
   the canvas key vocabulary (--pm-key ground, cut-edge bezel). Anatomy:
   a 20px family legend square (the family's IDENTITY MARK — the one
   fill, carrying the same saturated rq5 --family-* token the section's
   knob arcs answer in) beside the module's name as a silkscreened
   legend label (12px uppercase tracked, print at rest 6.68 on key,
   bright on hover). Rest seam is the key's cut edge; hover raises it
   to full print; active/drag-origin goes signal orange — the module is
   being pulled into the signal path. Sizes stay close to the pre-item-1b
   chips so SortableJS's drag ghost geometry is unaffected. Keyboard
   focus: the global orange :focus-visible ring covers these items
   exactly as before. */
.node-chip {
  /* R2-2 button-semantics normalization (visual no-op): the chip is now a
     real <button>, so the three UA button defaults the old <div> never
     had are reset to what the div rendered — full-width block (buttons
     shrink to content), the page font (buttons fall back to the UA face),
     and left-aligned flex content (buttons center text). cursor: grab
     below survives the conversion; the global :focus-visible ring covers
     keyboard focus, and the real `disabled` attribute (pre-Start gating,
     src/canvas.js) removes the chip from the tab order honestly. */
  width: 100%;
  font-family: inherit;
  text-align: left;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.5rem;
  border: 1px solid var(--pm-key-edge);
  border-radius: 4px;
  background: var(--pm-key);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pm-print);
  cursor: grab;
  user-select: none;
  transition:
    border-color 150ms ease,
    color 150ms ease,
    box-shadow 150ms ease,
    transform 150ms ease;
}

/* Pre-Start the whole zone is pointer-locked by the gate; the chip's
   own disabled attribute also withdraws it from the tab order (the
   honest functional gate) — the key merely refuses the pointer. */
.node-chip:disabled {
  cursor: not-allowed;
}

/* The family legend square: the FAMILY IDENTITY MARK. Family token fill
   (mapped below from the data-family attribute src/canvas.js writes —
   the same saturated rq5 tokens the knob arcs carry) with the 2-letter
   silkscreen initials from data-initials in chassis ink (6.93–11.07 on
   every family fill — the redundant encoding, so color is never the
   only signal). An as-yet-unmapped future type falls back to the
   ring-track grey so the module never loses its chip while awaiting a
   family token. */
.node-chip::before {
  content: attr(data-initials);
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 3px;
  background: var(--chip-family, var(--pm-ring-track));
  color: var(--pm-ink);
  /* 0.6875rem — the declared initials floor (matches the section badge). */
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
}

.node-chip[data-family='gain'] {
  --chip-family: var(--family-gain);
}

.node-chip[data-family='compressor'] {
  --chip-family: var(--family-compressor);
}

.node-chip[data-family='eq'] {
  --chip-family: var(--family-eq);
}

.node-chip[data-family='delay'] {
  --chip-family: var(--family-delay);
}

.node-chip[data-family='reverb'] {
  --chip-family: var(--family-reverb);
}

.node-chip[data-family='limiter'] {
  --chip-family: var(--family-limiter);
}

/* UI-2 (cycle 3): the four shelved effects map onto their new family
   tokens — same one-line mapping as the six above, so chips and cards
   share one vocabulary. */
.node-chip[data-family='distortion'] {
  --chip-family: var(--family-distortion);
}

.node-chip[data-family='chorus'] {
  --chip-family: var(--family-chorus);
}

.node-chip[data-family='gate'] {
  --chip-family: var(--family-gate);
}

.node-chip[data-family='autotune'] {
  --chip-family: var(--family-autotune);
}

/* Hover: the module answers — bezel to full print, label to bright, a
   1px lift, and the family color appears as a 2px top EDGE (inset
   shadow: no layout shift, no glow — edge marking, not edge light). */
.node-chip:hover {
  border-color: var(--pm-print);
  color: var(--pm-print-hi);
  box-shadow: inset 0 2px 0 var(--chip-family, var(--pm-print));
}

/* QA-4 V3: the 1px hover lift is motion — guarded per the reduced-motion
   discipline (the bezel/color answer above still applies under reduce). */
@media (prefers-reduced-motion: no-preference) {
  .node-chip:hover {
    transform: translateY(-1px);
  }
}

/* Active / drag-origin (SortableJS applies chosenClass for the whole
   drag): signal-orange bezel — the module is being pulled into the
   signal path. The 2px family top edge rides along; the label stays
   bright. */
.node-chip:active,
.node-chip.sortable-chosen {
  border-color: var(--pm-accent);
  color: var(--pm-print-hi);
  box-shadow: inset 0 2px 0 var(--chip-family, var(--pm-accent));
  cursor: grabbing;
}

/* SortableJS drag states (forceFallback), mirroring the node-card
   treatment below: the live clone lifts; the dashed translucent ghost
   marks the insertion slot in the chain list. */
.node-chip.sortable-drag {
  opacity: 0.85;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
}

.node-chip.sortable-ghost {
  opacity: 0.35;
  border-style: dashed;
  transform: none;
}

/* ===========================================================================
   CANVAS PANEL — the SINGLE FACE CHASSIS (redesign item 1): the CHAIN
   FACE, the middle zone of the voice deck. One continuous matte
   near-black faceplate:

     top edge    DISPLAY REGISTER (etched, accent-marked, JS-built by
                 src/canvas.js as the panel's first child)
     face        MIC IN jack print (top-left) → panel-print SECTIONS
                 split by machined grooves → OUT jack print
     base edge   the pinned OUT footer mirror (the VU corner,
                 bottom-right — src/meters.js) + the flow-direction
                 switch (bottom-left)

   The old floating-card vocabulary (border-radius islands, drop shadows
   at rest) is gone: sections are print zones of the one face, the only
   real shadow in the world is the drag LIFT (SortableJS's live clone),
   and brightness is saturation on matte, never glow.

   Item 1b: the panel's side chrome is now the two halves of the ZONE
   GROOVES it shares with the flanking zones — its left border is the
   LIP under the palette zone's cut, its right border the CUT above the
   presets zone's lip (same grammar as the sections' top grooves, read
   left to right: cut, then lip). Everything inside the panel is the
   item-1 build, untouched.
   =========================================================================== */

.canvas-panel {
  padding: 0;
  /* The chassis ground: the SAME faceplate as the whole voice deck —
     the chain face is a zone of the one slab, not an inset panel. */
  background: var(--pm-chassis);
  /* The zone grooves (see the block comment above). */
  border-left: 1px solid var(--pm-groove-lip);
  border-right: 1px solid var(--pm-groove-cut);
  /* A bounded flex column so the face owns its scrolling — the page must
     never grow unbounded with a long chain. Budget re-based by item 1b
     to the Two-Deck Stack's chrome: body padding (2rem) + the 4rem
     system deck + the 4px deck seam + slack — slightly MORE room for
     the face than the old strip+margin budget. */
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 6.75rem);
  min-height: 0;
}

/* --- The display register (top edge) ---------------------------------
   One etched slot: inset dark register ground, machined lip, an accent
   mark at the left edge. TWO fixed-height lines (machine value line +
   help line) that clip overflow — the register must never pump layout.
   Dot-matrix amber on the register ground (14.09), the module segment
   in signal orange (6.89) as the accent mark. aria-hidden: the controls
   carry the semantics; this is the redundant display.

   INTERNAL SCALE (item 1 adjustment round): the register is the
   panel-level instrument and keeps its larger etched tier, but its own
   segments are internally consistent — ONE face (mono --font-readout)
   and ONE size per line: the value line at the register tier (0.85rem,
   its module/param/value segments differentiated by color and weight,
   never by size), the help line at the surface's shared per-control
   value size (0.75rem, the ladder's bottom tier — see the VALUE-DISPLAY
   LADDER at the control layer) in the same mono face with tabular
   numerals, so the instrument speaks one voice top to bottom. */
.display-register {
  flex: none;
  position: relative;
  /* The MIC IN header unit is absolutely pinned into this strip's left
     end (see .display-register .anchor below), so the text block clears
     it. */
  /* FIXED geometry: two register lines, height pinned — the register
     answers every control event without ever pumping layout (values and
     help lines clip, never reflow). */
  height: 3rem;
  padding: 0.35rem 0.9rem 0.35rem 1.4rem;
  background: var(--pm-register-bg);
  box-shadow: inset 0 1px 0 var(--pm-groove-lip);
  border-bottom: 1px solid var(--pm-groove-cut);
  overflow: hidden;
}

.display-register::before {
  content: '';
  position: absolute;
  left: 0.6rem;
  top: 0.55rem;
  bottom: 0.55rem;
  width: 3px;
  background: var(--pm-accent);
}

.register-main {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  font-family: var(--font-readout);
  font-size: 0.85rem;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
}

.register-module {
  color: var(--pm-accent);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.register-module::after {
  content: '\00B7';
  margin-left: 0.45rem;
  color: var(--pm-print-dim);
}

.register-param {
  color: var(--pm-print);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.register-param::after {
  content: '\00B7';
  margin-left: 0.45rem;
  color: var(--pm-print-dim);
}

.register-param:empty::after {
  content: none;
}

.register-value {
  color: var(--pm-display);
  font-weight: 700;
}

.register-help {
  /* The register's subordinate line — same mono face + tabular numerals
     as the value line above it (internal consistency), at the ladder's
     shared per-control value size. */
  font-family: var(--font-readout);
  font-size: 0.75rem;
  line-height: 1.3;
  font-variant-numeric: tabular-nums;
  color: var(--pm-print);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ONE blink marks the live control: the register's value segment blinks
   once per update while the operator works (retriggered from JS by a
   class re-add across a reflow). Pure signal — and fully guarded: under
   prefers-reduced-motion the keyframe never exists, the value text
   change itself carries the information. */
@media (prefers-reduced-motion: no-preference) {
  .register-value.register-blink {
    animation: register-value-blink 220ms ease-out;
  }

  @keyframes register-value-blink {
    0% {
      color: var(--pm-ink);
      background: var(--pm-display);
    }

    100% {
      color: var(--pm-display);
      background: transparent;
    }
  }
}


/* THE MIC IN HEADER UNIT (2026-08-31, user direction): the input print
   row lives ON the register strip — the fixed top header of the chain
   face — in the same vocabulary as the base plate's OUT corner at the
   bottom-right (jack ring + silkscreen legend + the 96x26 lamp bar +
   mono dB readout, one row). Absolutely pinned: it never scrolls with
   the board. The register's 3px accent mark leads it. */
.display-register .anchor {
  position: absolute;
  left: 1.4rem;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.75rem 0 0;
  margin: 0;
  border-right: 1px solid var(--pm-groove-cut);
  white-space: nowrap;
}

/* Compact meter in the strip: the readout rides BESIDE the bar (the
   3rem strip cannot stack them). */
.display-register .anchor .meter-unit {
  flex-direction: row;
  align-items: center;
  gap: 0.4rem;
  width: auto;
  margin: 0;
}

/* The register's text (touched-control readouts — the state line is
   retired) clears the pinned unit. */
.display-register .register-main,
.display-register .register-help {
  padding-left: 15rem;
}

/* The leading flow chevron retired with the in-face MIC anchor: the
   board's flow now starts at the header unit, not at a print row. */
.canvas-panel .canvas > .arrow:first-of-type {
  display: none;
}

/* --- The face (scrolling flow) ---------------------------------------- */

.canvas {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.75rem 0.9rem 1rem;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* FEW-3: the positioning context for the cord layer — the SVG and the
     .chain-list then share ONE coordinate space (the list's live
     offsetLeft/offsetTop map 1:1 onto the layer's origin), and the
     layer scrolls WITH the board instead of floating over the panel. */
  position: relative;
}

/* Canvas anchors (MIC IN / OUT) — silkscreen JACK PRINT, not cards: the
   drawn jack ring + the label in print, the VIS-5 meter unit at the
   print row's right end. No border-radius island, no dashed target. */
.anchor {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.35rem 0.15rem;
  background: transparent;
  border: none;
  color: var(--pm-print);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

/* The drawn jack: a threaded ring with a dark socket — authored SVG-free
   geometry (border ring + radial socket), never a text glyph. */
.anchor::before {
  content: '';
  flex: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 2px solid var(--pm-print);
  background: radial-gradient(circle, var(--pm-register-bg) 0 2.5px, transparent 2.5px);
  margin-right: 0.55rem;
}

.anchor .meter-unit {
  margin-right: 0.25rem;
}

/* Signal-flow arrows — the same aria-hidden decorative spans, drawn as
   print chevrons (the glyph text is zeroed; the mark is CSS geometry
   pointing down the vertical flow). */
.arrow {
  flex: none;
  align-self: center;
  font-size: 0;
  color: var(--pm-print-dim);
}

.arrow::before {
  content: '';
  display: block;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--pm-print-dim);
  border-bottom: 2px solid var(--pm-print-dim);
  transform: rotate(45deg);
}

.chain-list {
  display: flex;
  flex-direction: column;
  /* The grooves between sections are part of each section's top edge
     (see .node-card below), so the list itself carries no gap. */
  gap: 0;
  /* flex-shrink MUST stay 0: the list may never shrink below its
     sections on the canvas's main axis (the round-1 occlusion defect —
     the .canvas owns the scrolling). */
  flex: 1 0 auto;
  /* Keeps a droppable slot between the arrows while the chain is empty
     (SortableJS drops land on the list element itself; the visible
     taught target next to it is the empty-hint below). */
  min-height: 1.5rem;
  position: relative; /* anchor for the bypassed-state hatch overlay */
}

/* Occlusion guard (unchanged invariant): the flow's fixed
   instrumentation (both anchors with their meters, the MC-4 safe-output
   note, the FEW-3 watchdog alert) always paints above section paint. */
.anchor,
.safe-output-note,
.watchdog-alert {
  position: relative;
  z-index: 1;
}

/* --- FEW-3 + OQ-9: the cord layer ---------------------------------------
   Read-only signal cords (MIC OUT -> sections in chain order -> OUT IN),
   painted by src/canvas.js's renderCords() from the positions map. The
   layer is APPENDED as the canvas face's LAST child (never a first
   child, never inside .chain-list — those DOM contracts are pinned by
   other tests) and paints BEHIND the sections purely by z-index.
   OQ-9 delivered the in-world vocabulary: cords run jack-to-jack in the
   panel's own tokens (--pm-* only), and each jack is a drawn RING +
   dark SOCKET pair sitting ON the border line it serves — the same
   anatomy and 15px size as the anchor print rings, one vocabulary for
   every jack on the board. Section jacks sit half-buried on the slab's
   edge (the ring reads as a socket machined into the border); the
   visible half is the grab affordance. Cord editing is FEW-4's scope —
   only the jacks are pointer-live. */
.cord-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 0; /* behind every section (z-index >= 1) and all fixed
                 instrumentation (the occlusion guard above) */
  pointer-events: none; /* read-only: a cord is never a hit target */
  overflow: visible; /* cords may leave the face's box at the board's
                        foot while scrolling (FEW-5 re-homes this) */
}

.cord {
  fill: none;
  stroke: var(--pm-print-dim);
  stroke-width: 2;
  stroke-linecap: round;
}

/* FEW-4 + OQ-9 — the JACK POINTS: the cord layer's ONLY pointer-live
   children. The layer keeps pointer-events:none; each jack group turns
   them back on for itself alone (pointer-events inherits to the hit
   disc, ring, and socket — the whole group grabs). The RING is the
   drawn jack (15px, the anchor print size); the SOCKET dot inside it is
   the dark bore; the transparent HIT disc is wider for the hand. A hot
   ring (a compatible drop target under the dragged end) and the dashed
   ghost cord use the accent ink; hover quietly lifts the ring to full
   print before any of that — the affordance teaches itself at rest. */
.cord-jack {
  pointer-events: all;
  cursor: grab;
}

.cord-jack .jack-hit {
  fill: transparent;
}

.cord-jack .jack-ring {
  fill: none;
  stroke: var(--pm-print);
  stroke-width: 2;
}

.cord-jack .jack-socket {
  fill: var(--pm-register-bg);
}

.cord-jack:hover .jack-ring {
  stroke: var(--pm-print-hi);
}

.cord-jack-hot .jack-ring {
  stroke: var(--pm-accent);
}

.cord-jack-hot .jack-socket {
  fill: var(--pm-accent);
}

.cord-ghost {
  fill: none;
  stroke: var(--pm-accent);
  stroke-width: 2;
  stroke-dasharray: 4 4;
  stroke-linecap: round;
}

/* Taught empty state — a printed EMPTY SOCKET on the face: recessed
   register ground, hairline dashed print boundary (a drop target earns a
   visible boundary, in the print register — no card island). */
.empty-hint {
  flex: 1 1 auto;
  padding: 1.1rem 1rem;
  border: 1px dashed var(--pm-key-edge);
  border-radius: 2px;
  background: var(--pm-register-bg);
  color: var(--pm-print);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-align: center;
}

/* ===========================================================================
   SECTIONS (the chain's effects) — WEIGHTED SLABS on the one faceplate
   (src/canvas.js writes the same data-family/data-initials hooks the old
   cards carried). Anatomy per the locked structure: FAMILY PRINT BLOCK
   (rail) left + ENCODER FIELD right. OQ-9 (QA-5): each section now
   carries its own face — the "everything is smashed together" ground is
   gone. The slab is TONAL + EDGE, never shadow:
     - FACE STEP: --pm-slab, one step lighter than the chassis ground it
       sits IN (a raised casting, not a floating card);
     - CUT EDGE: a 1px sawn dark cut all round (the groove vocabulary —
       the slot the slab was cut from);
     - MACHINED TOP LIP: inset 1px slab-lip directly under the top cut —
       cut beside lip, the house boundary grammar; brighter than a
       section groove, dimmer than the deck seam (a part, never the
       joint).
   No drop shadow at rest, no radius, no border islands — the Rest-Flat
   rule holds; the sections still read as parts of the one chassis.
   Family coding stays PRINT: the 2-letter code in the family's
   desaturated ink (8.66–10.76 measured on chassis; ~8.2–10.2 on the
   slab — well clear) + the 3px family tick down the rail's left edge.
   Color is never the only signal (code text + label always carry the
   family identity too).
   =========================================================================== */

.node-card {
  /* FEW-2 free positioning: every section is an absolutely positioned
     FULL-WIDTH row of the chain list (left:0; right:0; top:0), translated
     to its board seat by JS (`transform: translate(x, y)` — snap-grid
     quantized in src/canvas.js). Position is STYLE ONLY: the DOM order
     always equals the chain order (PD-4), so the bring-to-front z-order
     (JS zIndex) is paint, never order. The list's scrollable extent is
     maintained by JS (min-height over the lowest seat's measured
     bottom). */
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  /* FEW-3: sections always paint above the cord layer (z-index 0);
     bring-to-front's JS zIndex climbs from this floor. */
  z-index: 1;
  flex: none;
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  align-items: stretch;
  padding: 0.4rem 0.6rem 0.45rem 0.7rem;
  border-radius: 0;
  /* The slab: face step + sawn cut edge + machined top lip. */
  background: var(--pm-slab);
  border: 1px solid var(--pm-groove-cut);
  box-shadow: inset 0 1px 0 var(--pm-slab-lip);
  min-width: 0;
  will-change: transform;
}

/* The focus LIFT (the honest-risk solve): a touched section's print
   brightens a hair while its neighbors recede — brightness and print
   emphasis only, NO shadow (the locked card's risk line). Focus lands in
   a section whenever any of its controls is focused (pointerdown hands
   focus to the control's engine input; keyboard Tab does the same), so
   :focus-within IS the touch signal — no JS state, no drift. */
.node-card:focus-within {
  /* The cut face catches the light: print lifts, the groove lip
     brightens, the family tick saturates to full family ink. */
  filter: brightness(1.13);
}

/* Neighbors recede while one section is touched (scope :has to the list
   so a touched PALETTE chip can never dim the chain). */
.chain-list:has(.node-card:focus-within) .node-card:not(:focus-within) {
  filter: brightness(0.9);
}

@media (prefers-reduced-motion: no-preference) {
  .node-card {
    transition: filter 180ms ease;
  }
}

/* --- Family print block (the rail) ------------------------------------ */

.section-rail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.2rem;
  min-width: 0;
  /* The family tick: 3px of the family ink down the rail's left edge —
     panel-print marking, not a filled chip (OQ-9: one notch wider than
     the old 2px so the family reads at a glance on the slab, still never
     a fill). */
  border-left: 3px solid var(--card-family, var(--pm-print-dim));
  /* The rail is the module's identity block — a machined division
     separates it from the encoder field (cut, not border island). */
  border-right: 1px solid var(--pm-groove-cut);
  padding-left: 0.5rem;
  padding-right: 0.6rem;
}

/* The explicit GRIP ZONE (the locked card's risk line): with no card
   edges, drag-to-reorder must read as a panel part — the machined grip
   dot field IS that part, in the rail, at the handle the SortableJS
   config names. */
.node-drag-handle {
  display: flex;
  align-items: center;
  padding: 0.1rem 0.35rem 0.1rem 0;
  margin-left: -0.35rem;
  cursor: grab;
  min-height: 0.75rem;
  user-select: none;
}

.node-drag-handle:active {
  cursor: grabbing;
}

/* The grip: a 3x3 machined dot field (authored geometry — the dot matrix
   a real grip zone knurls into). Muted at rest, lifting to print when
   the section is hovered. */
.node-drag-icon {
  width: 10px;
  height: 12px;
  background-image: radial-gradient(circle, var(--pm-print-dim) 1px, transparent 1.3px);
  background-size: 4px 4px;
  background-position: center;
  transition: background-image 150ms ease;
}

.node-card:hover .node-drag-icon {
  background-image: radial-gradient(circle, var(--pm-print) 1.1px, transparent 1.4px);
}

/* The family CODE: the rail's large silkscreen print — 1.25rem/700 in the
   family's desaturated ink (large print, comfortably past 3:1; the pair
   table lives in the build record). */
.section-code {
  font-family: var(--font-readout);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1;
  color: var(--card-family, var(--pm-print));
}

/* Family derivations — ONE rule per family carries BOTH family-color
   roles of the section (single source; no per-control class anywhere):
     --card-family  the rail's DESATURATED silkscreen ink (code + tick —
                    panel print, same vocabulary as the legacy chips)
     --knob-arc     the knob arc's SATURATED rq5 --family-* token — the
                    SAME token the palette flank's chip legend uses, so a
                    section's arcs answer in its left-hand-menu color.
                    Measured as non-text arcs on the chassis ground
                    #131316: 6.93–11.07, all ≥3:1 (table in the item-1
                    adjustment record) — no derivation adjustments
                    needed. The palette flank keeps the rq5 tokens until
                    item 2 restyles it. */
.node-card[data-family='gain'] {
  --card-family: var(--pm-family-gain);
  --knob-arc: var(--family-gain);
}

.node-card[data-family='compressor'] {
  --card-family: var(--pm-family-compressor);
  --knob-arc: var(--family-compressor);
}

.node-card[data-family='eq'] {
  --card-family: var(--pm-family-eq);
  --knob-arc: var(--family-eq);
}

.node-card[data-family='delay'] {
  --card-family: var(--pm-family-delay);
  --knob-arc: var(--family-delay);
}

.node-card[data-family='reverb'] {
  --card-family: var(--pm-family-reverb);
  --knob-arc: var(--family-reverb);
}

.node-card[data-family='limiter'] {
  --card-family: var(--pm-family-limiter);
  --knob-arc: var(--family-limiter);
}

.node-card[data-family='distortion'] {
  --card-family: var(--pm-family-distortion);
  --knob-arc: var(--family-distortion);
}

.node-card[data-family='chorus'] {
  --card-family: var(--pm-family-chorus);
  --knob-arc: var(--family-chorus);
}

.node-card[data-family='gate'] {
  --card-family: var(--pm-family-gate);
  --knob-arc: var(--family-gate);
}

.node-card[data-family='autotune'] {
  --card-family: var(--pm-family-autotune);
  --knob-arc: var(--family-autotune);
}

/* The rail's foot: the section's header-zone controls (fold chevron +
   eject ×). Expanded they sit at the rail's bottom edge; folded they
   ride the slim row's right end. */
.section-foot {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: auto;
  padding-top: 0.25rem;
}

/* --- The experimental badge (autotune only, single-sourced) ------------
   UI-2 (cycle 3): the FORMAL badge, one class, two placements — the
   palette chip and the section rail. Item 1b unified them in the
   Pattern Machine register: display-amber silkscreen on transparent —
   a machine marking, not a bright object, so the accent budget stays
   with the register and the one orange key (13.36 on the chassis /
   12.42 on the etch-toned key). A status tag, not a control: no focus,
   no pointer affordance; SR access is by content. Text at the 11px
   floor. */
.node-experimental-badge {
  flex: none;
  padding: 0.1rem 0.35rem;
  border: 1px solid var(--pm-display);
  border-radius: 3px;
  color: var(--pm-display);
  background: transparent;
  font-family: var(--font-readout);
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1;
  white-space: nowrap;
}

/* The palette-chip placement (pinned right inside the chip key). */
.node-chip .node-experimental-badge {
  margin-left: auto;
}

/* --- The encoder field (right) ---------------------------------------- */

.section-main {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The collapsible field. .node-params is a one-row grid animating
   1fr→0fr; .node-params-inner (wrap-AFTER-render, src/canvas.js) carries
   min-height:0 + overflow:hidden — the pair that lets a 0fr track clip
   the rows. Folded, visibility:hidden removes the rows from tab order
   and the accessibility tree (an animating height alone would leave
   invisible focusable knobs); the end-of-transition visibility flip
   under the motion guard lets the fold finish first. Under
   prefers-reduced-motion none of the transitions exist — folding is an
   instant state change. Session-only: chain rebuilds re-create sections
   expanded (accepted v1 behavior). */
.node-params {
  display: grid;
  grid-template-rows: 1fr;
  min-width: 0;
}

.node-params-inner {
  overflow: hidden;
  min-height: 0;
}

.node-card.collapsed .node-params {
  grid-template-rows: 0fr;
}

.node-card.collapsed .node-params-inner {
  visibility: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .node-params {
    transition: grid-template-rows 180ms ease;
  }

  /* visibility transitions discretely and stays VISIBLE until the END of
     the duration when animating toward hidden — the fold finishes first;
     expand restores it instantly. */
  .node-card.collapsed .node-params-inner {
    transition: visibility 180ms;
  }
}

/* --- Folded state: the section becomes a groove line -------------------
   The rail lays out horizontally (grip · code · label · badge, then the
   foot's buttons pinned right), the field is folded away, and the whole
   section recedes to the chassis — a printed groove line, exactly the
   locked collapsed row. */
.node-card.collapsed {
  grid-template-columns: 1fr;
  padding: 0.3rem 0.75rem 0.35rem 0.9rem;
}

.node-card.collapsed .section-rail {
  flex-direction: row;
  align-items: center;
  gap: 0.55rem;
  border-left-width: 3px;
}

.node-card.collapsed .section-code {
  font-size: 0.9rem;
}

.node-card.collapsed .section-foot {
  margin-top: 0;
  margin-left: auto;
  padding-top: 0;
}

.node-card.collapsed .section-main {
  display: none;
}

/* --- Header-zone controls (fold chevron, eject ×) ---------------------- */

/* The chevron: a drawn mark (a square's two edges) rotating to point
   right when folded — the disclosure convention, as geometry. */
.node-collapse {
  flex: none;
  /* REQUIRED with the hit-floor ::before below: without this anchor the
     absolutely-positioned ::before resolves against the CARD and becomes
     a full-card invisible overlay owned by the collapse button — knobs
     (positioned) still paint above it, but trim sliders and the drag
     handle fall under it, so clicking a trim or grabbing the card
     collapses it instead (the defect the 2026-08-31 browser round
     caught). */
  position: relative;
  /* Condensed to the code line (16px + border) so the header band never
     grows a blank strip under the keys. */
  width: 1rem;
  height: 1rem;
  padding: 0;
  border: 1px solid var(--pm-key-edge);
  border-radius: 3px;
  background: var(--pm-chassis);
  color: var(--pm-print);
  cursor: pointer;
}

.chevron-mark {
  display: block;
  width: 5px;
  height: 5px;
  margin: 2px auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg); /* points down at the visible field */
  margin-top: 3px;
}

.node-collapse:hover,
.node-collapse:active {
  color: var(--pm-print-hi);
  border-color: var(--pm-print);
}

/* Compactness round: the rail's small buttons shrank below the recorded
   24px pointer floor — this invisible ::before restores it (visual box
   1.15rem, hit box ~25px). */
.node-collapse::before,
.node-remove-btn::before {
  content: '';
  position: absolute;
  inset: -4px;
}

.node-card.collapsed .node-collapse .chevron-mark {
  transform: rotate(-45deg); /* points right: content will follow */
}

@media (prefers-reduced-motion: no-preference) {
  .node-collapse {
    transition: border-color 150ms ease, color 150ms ease;
  }
}

/* The remove × — a real control in the section's header zone. The × is
   DRAWN (two crossed bars), never a text glyph; danger is signaled by
   the bezel answering to the rq5 safety-red EDGE role (non-text 3.45 on
   chassis ≈ the legacy pairing on card; red is never the text color). */
.node-remove-btn {
  flex: none;
  position: relative;
  width: 1rem;
  height: 1rem;
  padding: 0;
  border: 1px solid var(--pm-key-edge);
  border-radius: 3px;
  background: var(--pm-chassis);
  color: var(--pm-print);
  cursor: pointer;
}

.remove-mark,
.remove-mark::before,
.remove-mark::after {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7px;
  height: 1.5px;
  background: currentColor;
}

.remove-mark::before,
.remove-mark::after {
  content: '';
  margin: -0.75px 0 0 -3.5px;
}

.remove-mark {
  background: transparent;
}

.remove-mark::before {
  transform: translate(-50%, -50%) rotate(45deg);
}

.remove-mark::after {
  transform: translate(-50%, -50%) rotate(-45deg);
}

.node-remove-btn:hover,
.node-remove-btn:active {
  color: var(--pm-print-hi);
  border-color: var(--red-edge);
}

@media (prefers-reduced-motion: no-preference) {
  .node-remove-btn {
    transition: border-color 150ms ease, color 150ms ease;
  }
}

/* --- The width-resize grip (2026-08-31 round) -------------------------
   A machined corner mark at the section's bottom-right — the same dot
   field vocabulary as the position grip, anchored to the corner that
   moves. Pointer-only (like the position grip): the resize is a style
   edit, never an order or a sound. Hidden outside the horizontal board
   (vertical rows own their width) and while folded. */
.node-resize {
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 16px;
  height: 16px;
  cursor: ew-resize;
  touch-action: none; /* the horizontal drag is ours, not the page's scroll */
  user-select: none;
  background-image: radial-gradient(circle, var(--pm-print-dim) 1px, transparent 1.3px);
  background-size: 5px 5px;
  background-position: bottom right;
  opacity: 0.55;
}

.node-card:hover .node-resize {
  background-image: radial-gradient(circle, var(--pm-print) 1.1px, transparent 1.4px);
  opacity: 1;
}

.node-card.collapsed .node-resize {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .node-resize {
    transition: opacity 150ms ease;
  }
}

/* --- SortableJS drag states (the one real shadow in the world) ---------
   Chosen (held, whole drag): the section's groove lip goes accent — the
   part being pulled into the signal path. Drag (the lifted live clone):
   a REAL physical lift, so it earns the world's one offset+blur shadow.
   Ghost (the insertion slot): a dashed print slot — where the section
   WILL land, reading as a groove reservation, not a section. */
.node-card.sortable-chosen {
  cursor: grabbing;
  box-shadow:
    inset 0 1px 0 var(--pm-accent),
    inset 0 2px 0 rgba(255, 107, 53, 0.25);
}

.node-card.sortable-drag {
  background: var(--pm-slab);
  opacity: 0.92;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.6);
}

.node-card.sortable-ghost {
  opacity: 0.4;
  border-top: 1px solid var(--pm-groove-cut);
  box-shadow: inset 0 1px 0 var(--pm-groove-lip);
}

.node-card.sortable-ghost::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 1px dashed var(--pm-key-edge);
  border-radius: 2px;
}

/* --- Agent mutation pulse ----------------------------------------------
   One accent blink on the section's machined top lip (the slab's lip
   flashes signal orange) — a state blink, not a glow; the animation
   NAME is pinned by src/canvas.js's animationend listener and MUST NOT
   change. Entirely inside the reduced-motion guard. */
@media (prefers-reduced-motion: no-preference) {
  .node-card.agent-pulse {
    animation: node-card-agent-pulse 150ms ease-in-out 2;
  }

  @keyframes node-card-agent-pulse {
    0%,
    100% {
      box-shadow: inset 0 1px 0 var(--pm-slab-lip);
    }

    50% {
      box-shadow: inset 0 1px 0 var(--pm-accent);
    }
  }
}

/* ===========================================================================
   DISABLED TEXTURE — the ONE unambiguous non-interactive grammar: a
   diagonal hatch over the panel print plus a recede. Used in exactly two
   states (never anywhere else): the pre-Start panel gate and the
   bypassed chain. Accent-marked elements that stay live (the display
   register, the anchors/meters/footer) never carry it.
   Item 1b: the two flanking ZONES join the grammar — the legacy rq5
   dim-with-inset-hairline is retired, so all three voice-deck zones
   gate with the same hatch + recede the canvas face has always used.
   =========================================================================== */

/* Pre-Start gate, the flanking zones: pointer lock (the functional
   gate, unchanged) + the SAME recede + diagonal hatch the canvas face
   carries below — one grammar, three zones. */
.layout.engine-not-started .palette,
.layout.engine-not-started .presets {
  position: relative;
  opacity: 0.55;
  pointer-events: none;
}

.layout.engine-not-started .palette::before,
.layout.engine-not-started .presets::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: repeating-linear-gradient(
    -45deg,
    transparent 0,
    transparent 7px,
    rgba(11, 12, 16, 0.55) 7px,
    rgba(11, 12, 16, 0.55) 9px
  );
}

/* Pre-Start gate, the canvas face: pointer lock (the functional
   gate, unchanged) + hatch over the FACE (.canvas, i.e. everything
   except the register and the footer, which both stay live and answer
   "press Start"). */
.layout.engine-not-started .canvas-panel {
  opacity: 1;
  pointer-events: none;
  box-shadow: none;
}

.layout.engine-not-started .canvas {
  position: relative;
  opacity: 0.55;
}

.layout.engine-not-started .canvas::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: repeating-linear-gradient(
    -45deg,
    transparent 0,
    transparent 7px,
    rgba(11, 12, 16, 0.55) 7px,
    rgba(11, 12, 16, 0.55) 9px
  );
}

/* Pre-Start, the empty-hint reads as a printed socket, not a dashed
   drop-target (dropping is impossible until Start — the copy flip in
   src/canvas.js's onEngineStarted carries the teaching). */
.layout.engine-not-started .empty-hint {
  border: 1px solid var(--pm-key-edge);
}

/* The gated region answers the pointer with not-allowed (the panels are
   pointer-events:none, so hit-testing resolves to this .layout element).
   Removed the instant onEngineStarted() drops the class. */
.layout.engine-not-started {
  cursor: not-allowed;
}

/* ===========================================================================
   PRESETS ZONE (PS-3 + VIS-3, re-cast by item 1b) — the voice deck's
   right-hand print zone, one vocabulary with the palette zone:
   silkscreen zone legend over a groove-cut rule, the current-preset
   name as the zone's mono machine readout (12px tabular — the shared
   per-control value tier, 11.78 on the chassis), the unsaved flag in
   the signal orange as text (6.54 — a system state), controls as the
   shared control KEYS. The preset <select> is already dark-coherent
   via the shared rules: color-scheme dark (root) renders the native
   dropdown dark, key-edge bezel, bright print.
   =========================================================================== */

.presets {
  /* The lip half of the face|presets zone groove (the face's right
     border is the cut — see .canvas-panel above). */
  border-left: 1px solid var(--pm-groove-lip);
  background: transparent;
}

/* Silkscreen zone legend — identical treatment to .palette h2 above
   (one vocabulary across both zones). */
.presets h2 {
  margin: 0 0 0.6rem;
  padding-bottom: 0.45rem;
  border-bottom: 1px solid var(--pm-groove-cut);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pm-print);
}

.preset-current {
  margin-bottom: 0.75rem;
}

/* The preset name is machine state — the zone's mono bank-slot readout,
   at the shared 12px value tier (one register with the system etch and
   the per-control values; the chain face's 13.6px register line stays
   the loudest machine voice on the page). Block so the unsaved flag
   reads as its own tight line beneath. */
.preset-name {
  display: block;
  font-family: var(--font-readout);
  font-size: 0.75rem;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  margin-bottom: 0.25rem;
  overflow-wrap: anywhere;
  color: var(--pm-print-hi);
}

.unsaved-dot {
  color: var(--pm-accent);
  font-weight: 600;
  font-size: 0.75rem;
}

/* Direct children only — Save As… and the <select> stack full-width in the
   column; the Load/Delete pair below is laid out separately via
   .preset-actions so they can sit side by side instead. */
.presets > .control {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
}

.preset-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.preset-actions .control {
  flex: 1 1 0;
  margin-top: 0;
  min-width: 0;
}

/* R2-3 — the inline Save As naming row: the same control vocabulary as
   the rest of the zone, so naming a preset mid-show stays in-zone (no
   browser prompt()). Hidden by default; src/presets-ui.js shows it on
   Save As… and hides it again on Save/Cancel (display:none also removes
   the input from the tab order — no dead stops). The input is a text
   field on the key vocabulary: key ground, cut-edge bezel, native-dark
   via the root color-scheme. Layout is measured to the 220px zone: the
   input takes the full row (flex-basis 100%), and Save (the one orange
   key pairing, Start's twin) + Cancel sit beneath it sharing a second
   line exactly like the Load/Delete pair — side-by-side input+buttons
   cannot fit 220px without shrinking the input to unreadability. */
.preset-name-row {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

input.preset-name-input {
  display: block;
  width: 100%;
  padding: 0.5rem;
  border-radius: 4px;
  border: 1px solid var(--pm-key-edge);
  background: var(--pm-key);
  font-size: 0.9rem;
  color: var(--pm-print-hi);
}

/* The Save/Cancel sub-row — the Load/Delete pairing, verbatim. */
.preset-name-actions {
  display: flex;
  gap: 0.5rem;
}

.preset-name-actions .control {
  flex: 1 1 0;
  min-width: 0;
}

input.preset-name-input:focus-visible {
  /* The global orange ring already covers it — this block exists to
     keep the vocabulary explicit next to the row it belongs to. */
  outline: 2px solid var(--pm-accent);
  outline-offset: 1px;
}

.preset-name-row .control-primary {
  flex: 1 1 0;
  min-width: 0;
  background: var(--pm-accent);
  border-color: var(--pm-accent);
  color: var(--pm-ink);
  font-weight: 700;
}

.preset-name-row .control-primary:hover {
  background: var(--pm-accent-hi);
  border-color: var(--pm-accent-hi);
  color: var(--pm-ink);
}

.preset-name-row .control-primary:focus-visible {
  outline-color: var(--pm-ink);
  outline-offset: -2px; /* ink ring on the orange fill, same as Start */
}

.preset-name-row .control:not(.control-primary) {
  flex: 1 1 0;
  min-width: 0;
}

/* R2-3 — the armed second step of Delete: the safety-red EDGE role as
   the bezel (tokens only; red FILL stays Bypass-only), plus the
   "DELETE?" relabel from src/presets-ui.js. Edge + copy carry the
   state together — neither alone. */
#delete-preset-btn.danger-arm {
  border-color: var(--red-edge);
  color: var(--pm-print-hi);
}

#delete-preset-btn.danger-arm:not(:disabled):hover {
  border-color: var(--red-edge);
}

/* PS-4 — factory/user dropdown groups. The <optgroup> labels read as
   silkscreen micro-legends (the etch-label vocabulary: small, bold,
   tracked, print), so the native menu keeps the faceplate voice; the
   option rows themselves stay native and dark via the root
   color-scheme: dark rule (no per-option styling — the shared rules
   already render the popup dark). Grouping is structural only: the
   factory options carry 'factory:'-prefixed values in the DOM, so the
   styling never carries meaning on its own. Uppercasing degrades to the
   markup's title case where a platform ignores text-transform here. */
#preset-select optgroup {
  /* 0.7rem — the etch-label floor applies to the native popup too. */
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pm-print);
}

/* PS-4 — the quiet inline refusal note under the preset controls (e.g.
   Delete on a factory entry): muted micro-text that self-hides, not a
   modal — the panel reserves amber for the unsaved state, so an
   informational note stays neutral. */
.preset-note {
  margin: 0.5rem 0 0;
  font-size: 0.7rem;
  color: var(--pm-print);
}

/* Visually hide (but keep accessible to assistive tech) the device-select
   label — the mockup shows no separate label text, relying on context. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ===========================================================================
   Agent feedback (FEW-1 structure + VIS-6 console treatment) — themes the
   components src/agent-ui.js creates dynamically: #agent-chip (inserted
   into the .topbar immediately before #bypass-toggle-button) and
   #agent-toast-region (appended as <body>'s last child). FEW-1's JS
   contract — 6 s auto-dismiss with hover-pause, the 3-toast cap, Escape,
   the undo-button invariant, role/aria — is entirely in src/agent-ui.js
   and untouched here; this block is presentation only. Every new
   transition/animation lives inside
   @media (prefers-reduced-motion: no-preference); under reduce the chip
   keeps its static state look and toasts appear/disappear instantly
   (VIS-4's .agent-pulse rule above already carries its own guard).
   =========================================================================== */

/* The chip reads as a deck native (item 1b: a small KEY on the system
   deck): silkscreen-adjacent legend type (same 0.75rem/700/0.08em
   register as .status and the zone legends), key ground + cut-edge
   bezel. It is NOT a control — it reports, never acts — so at rest it
   stays quiet. */
.agent-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  flex: none;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--pm-key-edge);
  border-radius: 4px;
  background: var(--pm-key);
  color: var(--pm-print);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

/* unavailable — the load-time default and deliberately the least loud
   state on the deck: muted legend, cut-edge seam, no lamp. (Explicit
   for completeness; this is the base treatment above.) */
.agent-chip[data-state='unavailable'] {
  border-color: var(--pm-key-edge);
  color: var(--pm-print);
}

.agent-chip[data-state='unavailable']::before {
  content: none;
}

/* tools-ready / acting — the agent lane is live: bezel rises to full
   print, the label lifts to the SIGNAL ORANGE (agent-active is one of
   the accent's system states; 5.85 on the key ground as text), and an
   8px orange status square (non-text ≥3) lights beside it. Color is
   never the only signal — the copy reads AGENT READY / AGENT ACTING
   either way. */
.agent-chip[data-state='tools-ready'],
.agent-chip[data-state='acting'] {
  border-color: var(--pm-print);
}

.agent-chip[data-state='tools-ready'] .agent-chip-label,
.agent-chip[data-state='acting'] .agent-chip-label {
  color: var(--pm-accent);
}

.agent-chip[data-state='tools-ready']::before,
.agent-chip[data-state='acting']::before {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--pm-accent);
}

/* Acting adds ONE slow activity breath (opacity 1→0.7→1, ~1.2 s loop —
   a deliberate exception to the 150–250 ms state-transition budget,
   pinned by the VIS-6 spec as a status lamp, not a transition). Guarded:
   reduced motion keeps the static tools-ready look and the state stays
   readable through the 'AGENT ACTING' copy. */
@media (prefers-reduced-motion: no-preference) {
  .agent-chip[data-state='acting'] {
    animation: agent-chip-activity 1.2s ease-in-out infinite;
  }

  @keyframes agent-chip-activity {
    0%,
    100% {
      opacity: 1;
    }

    50% {
      opacity: 0.7;
    }
  }
}

.agent-toast-region {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  /* Above the sticky top bar (z-index 10) and the dev harness (15) so
     stacked toasts overlay both — z-order unchanged from FEW-1. */
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(22rem, calc(100vw - 2rem));
}

/* Toasts answer in the module-card vocabulary: bg-card surface,
   line-strong bezel (a floating card overlaps arbitrary grounds, so its
   boundary earns the meaningful-outline token), the same 8px radius and
   lifted-depth shadow the sortable-drag cards carry. Comfortable
   padding, ~340px cap (inside the region's 22rem slot). */
.agent-toast {
  max-width: 340px;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--bg-card);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.45);
  font-size: 0.85rem;
  color: var(--text-primary);
}

/* Adversarial-rejection toasts — the FEW-3 watchdog vocabulary: the
   safety-red EDGE role as a full 2px bezel on ALL sides (never a
   left-edge accent stripe, never red text — red-edge is 3.44 < 4.5 as small
   text per rq5). The bezel IS the refusal signal; the body stays
   text-primary and the summary copy already says what was refused.
   :has() selects the toast from its .agent-toast-error child (the only
   hook — FEW-1's markup is frozen). */
.agent-toast:has(.agent-toast-error) {
  border: 2px solid var(--red-edge);
}

.agent-toast-summary {
  color: var(--text-primary);
}

/* Machine footnote zone: clamped/error/undone notes read in the mono
   register (--font-readout) at micro size, muted. The FIRST note after
   the summary carries a hairline seam (adjacent-sibling, so two stacked
   notes never double-seam) — the card's summary-line/footnote split. */
.agent-toast-clamped,
.agent-toast-error,
.agent-toast-undone {
  margin-top: 0.35rem;
  font-family: var(--font-readout);
  font-size: 0.7rem;
  line-height: 1.45;
  color: var(--text-muted);
}

.agent-toast-summary + .agent-toast-clamped,
.agent-toast-summary + .agent-toast-error,
.agent-toast-summary + .agent-toast-undone {
  margin-top: 0.45rem;
  padding-top: 0.45rem;
  border-top: 1px solid var(--hairline);
}

/* Refusal body: primary text on the card ground (11.04) — tone comes
   from the bezel and the copy, not from a failing red. */
.agent-toast-error {
  color: var(--text-primary);
}

.agent-toast-undone {
  font-weight: 600;
}

/* Undone toasts dim (data-undone is set by FEW-1's undo path): the
   information was consumed and reverted; the card recedes. 0.75 keeps
   the muted mono note legible while clearly reading as past-tense. */
.agent-toast[data-undone='true'] {
  opacity: 0.75;
}

/* Undo — the toast's ONE action, in the Start-button pairing: ink-on-
   amber fill (on-accent 8.46–10.60 on every amber fill), hover to
   accent-hover, pressed to accent-active, and the amber-safe DARK focus
   ring while amber-filled (amber-on-amber 1.16 is unusable — the exact
   case --focus-ring-on-accent exists for). Specificity note: the
   :not(:disabled) state selectors outrank `button.control`'s same-shape
   rules by class count; the base still wins its tie by file order, as
   in FEW-1. */
.agent-toast .agent-toast-undo {
  margin-top: 0.5rem;
  padding: 0.25rem 0.9rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.agent-toast .agent-toast-undo:not(:disabled):hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--on-accent);
}

.agent-toast .agent-toast-undo:not(:disabled):active {
  background: var(--accent-active);
  border-color: var(--accent-active);
  color: var(--on-accent);
}

.agent-toast .agent-toast-undo:focus-visible,
.agent-toast .agent-toast-undo:active:focus-visible {
  outline-color: var(--focus-ring-on-accent);
  /* QA-4 V2: same geometry fix as Start — the ink ring belongs on the
     amber fill, not the card ground outside it. */
  outline-offset: -2px;
}

/* Entrance: 180 ms transform/opacity only — a toast slides up into
   place without touching layout (the region is fixed). There is NO exit
   animation BY CONSTRUCTION: FEW-1's dismiss path removes the element
   synchronously, so under motion preferences and reduce alike the
   disappearance is instant — the no-animation fallback and the animated
   path share it. */
@media (prefers-reduced-motion: no-preference) {
  .agent-toast {
    animation: agent-toast-in 180ms ease-out;
  }

  @keyframes agent-toast-in {
    from {
      opacity: 0;
      transform: translateY(6px);
    }
  }

  /* The undo fill states above transition like every amber control. */
  .agent-toast .agent-toast-undo {
    transition:
      background-color 150ms ease,
      border-color 150ms ease,
      color 150ms ease;
  }
}

/* ===========================================================================
   MC-4 — host-owned safe-output note. src/audio-graph.js creates this
   element from JS (agent-ui creation pattern; never hardcoded in
   index.html) right after the OUT anchor when the -6 dBFS output
   attenuator first comes into existence. Small muted print on the
   faceplate — disclosure only; VIS-4/6 may restyle it with the console
   vocabulary later.
   =========================================================================== */

.safe-output-note {
  flex: none;
  /* In the top-down flow the note sits BENEATH the OUT anchor —
     left-aligned so it reads as that block's caption, not a free-floating
     caption for the whole canvas. Redesign item 1: panel print on the
     chassis (7.47), the print register's caption size. */
  align-self: flex-start;
  font-size: 0.75rem;
  color: var(--pm-print);
  white-space: nowrap;
}

/* ===========================================================================
   FEW-3 — watchdog alert. src/meter-taps.js creates this element from
   JS (agent-ui creation pattern; never hardcoded in index.html) ONLY
   when the RQ-3 runtime watchdog trips (output peak above the derived
   ceiling sustained >250 ms, or a ~1 s monotonic 1–8 kHz band rise):
   the chain gate is force-muted and this alert appears next to the OUT
   anchor, after the safe-output-note, until a human clicks Restore.
   LOUD but token-pure per rq5: the safety-red EDGE role as a 2px ring
   on panel ground (red-edge is never a text color — 3.45 < 4.5), bold
   primary text, and the Restore button in the bypass-ON pairing
   (red-fill ground + #FFFFFF text 5.08 PASS + red-edge ring) — the
   loudest vocabulary the token set allows. The element carries
   role="alert" so screen readers announce the mute. No animation:
   nothing here needs to attract via motion (reduced-motion safe).
   =========================================================================== */

.watchdog-alert {
  flex: none;
  /* A full-width banner beneath the OUT block + safe-output note — the
     loudest thing the vocabulary allows, at the loudest width. Redesign
     item 1: chassis ground, squared cut (no island radius), and the one
     real exception to "no shadow" stays OUT (safety red earns no
     shadow either — the 2px red EDGE ring is the loudness). */
  align-self: stretch;
  justify-content: center;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.625rem;
  border: 2px solid var(--red-edge);
  border-radius: 2px;
  background: var(--pm-chassis);
}

.watchdog-alert-text {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--text-primary);
  white-space: nowrap;
}

.watchdog-alert .watchdog-restore {
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--red-edge);
  border-radius: 6px;
  background: var(--red-fill);
  color: #FFFFFF;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
}

.watchdog-alert .watchdog-restore:not(:disabled):hover {
  border-color: var(--accent);
}

.watchdog-alert .watchdog-restore:not(:disabled):active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* While pressed (amber fill), the dark focus-ring variant keeps the
   global :focus-visible ring visible — same exception as .control:active. */
.watchdog-alert .watchdog-restore:not(:disabled):active:focus-visible {
  outline-color: var(--focus-ring-on-accent);
}

/* Issue #7 — the fallback-mode hidden-tab HINT. Same element pattern as
   the trip alert above but deliberately NON-alarming: the calm accent
   (amber) edge instead of safety-red, role="status" (not "alert"), no
   button — the remedy is bringing the tab back, not a click. Shown
   only when the audio-worklet watchdog could NOT load (rAF-only
   fallback) and the page went hidden while the engine is live; removed
   on visibilitychange back. */
.watchdog-alert.watchdog-hint {
  border-color: var(--accent);
}

.watchdog-alert.watchdog-hint .watchdog-alert-text {
  font-weight: 600;
  color: var(--text-muted);
}

/* ===========================================================================
   MC-6 — dev test harness (agent simulator). src/mcp-harness.js creates
   #mcp-harness from JS only when location.search contains 'dev'
   (index.html carries just the script tag; without the param none of
   these rules ever apply to a rendered element). Fixed right-side
   overlay on the bg-panel surface: hairline seams inside, one
   line-strong bezel around, mono readout (--font-readout) for tool
   names / JSON / the event log, text hierarchy via text-primary /
   text-muted. The amber accent is used SPARINGLY per the MC-6 spec —
   Run :active and the [error] flag only. top: 5.5rem keeps the sticky
   topbar (and the Bypass button in it) fully visible; z-index 15 sits
   above the app layout and below agent toasts (20), which may
   transiently overlay the panel foot and self-dismiss in 6 s — fine
   for a dev surface. Buttons reuse .control plus a mcp-harness-* class
   for sizing; the global :focus-visible ring above applies unchanged.
   =========================================================================== */

.mcp-harness {
  position: fixed;
  top: 5.5rem;
  right: 1rem;
  bottom: 1rem;
  z-index: 15;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  width: min(26rem, calc(100vw - 2rem));
  max-width: 420px;
  background: var(--bg-panel);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
  font-size: 0.8rem;
  color: var(--text-primary);
}

.mcp-harness-header {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.75rem;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--hairline);
}

.mcp-harness-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.mcp-harness-title {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--text-primary);
}

.mcp-harness-close {
  padding: 0.25rem 0.6rem;
  font-size: 0.75rem;
}

.mcp-harness-statusline,
.mcp-harness-chipmirror {
  font-size: 0.72rem;
  color: var(--text-muted);
}

.mcp-harness-chipmirror {
  font-family: var(--font-readout);
}

.mcp-harness-section {
  padding: 0.75rem;
  border-bottom: 1px solid var(--hairline);
}

.mcp-harness-section-title {
  margin: 0;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

/* Tool cards — one per def from McpTools.getDefs(). */

.mcp-harness-tool {
  padding: 0.6rem;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: var(--bg-card);
}

.mcp-harness-tool + .mcp-harness-tool {
  margin-top: 0.5rem;
}

.mcp-harness-tool-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.mcp-harness-tool-name {
  font-family: var(--font-readout);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-primary);
}

.mcp-harness-badge {
  flex: none;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--hairline);
  border-radius: 3px;
  font-size: 0.6875rem;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.mcp-harness-tool-desc {
  margin: 0.35rem 0 0;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--text-muted);
}

.mcp-harness-param {
  font-family: var(--font-readout);
  font-size: 0.6875rem;
  line-height: 1.6;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mcp-harness-params {
  margin-top: 0.35rem;
}

.mcp-harness-args {
  display: block;
  width: 100%;
  min-height: 5rem;
  margin-top: 0.5rem;
  padding: 0.4rem;
  resize: vertical;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: var(--bg-body);
  color: var(--text-primary);
  font-family: var(--font-readout);
  font-size: 0.72rem;
  line-height: 1.5;
}

.mcp-harness-runrow {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.mcp-harness-run {
  padding: 0.3rem 0.8rem;
  font-size: 0.75rem;
}

/* Accent use #1 (sparingly): the moment of invocation. */
.mcp-harness-run:active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.mcp-harness-result {
  max-height: 14rem;
  overflow: auto;
  margin: 0.5rem 0 0;
  padding: 0.4rem;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: var(--bg-body);
  font-family: var(--font-readout);
  font-size: 0.6875rem;
  line-height: 1.5;
  color: var(--text-primary);
  white-space: pre;
}

/* Accent use #2: the [error] flag on error:true results. */
.mcp-harness-result[data-error="true"] {
  border-color: var(--accent);
}

/* Event stream — scrolling mono log, newest at the bottom. */

.mcp-harness-events-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.mcp-harness-clear {
  padding: 0.2rem 0.6rem;
  font-size: 0.7rem;
}

.mcp-harness-log {
  height: 9rem;
  overflow-y: auto;
  padding: 0.4rem;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: var(--bg-body);
  font-family: var(--font-readout);
  font-size: 0.6875rem;
  line-height: 1.7;
}

.mcp-harness-log-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mcp-harness-log-time,
.mcp-harness-log-type {
  margin-right: 0.4rem;
  color: var(--text-muted);
}

.mcp-harness-log-detail {
  color: var(--text-muted);
}

/* Mutations carry the payload that matters — lift them to primary. */
.mcp-harness-log-line[data-kind="agentui:mutation"] .mcp-harness-log-detail {
  color: var(--text-primary);
}

/* Undo bar — sticky foot of the panel. No clear-stack control (MC-6). */

.mcp-harness-undo {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: auto;
  padding: 0.6rem 0.75rem;
  background: var(--bg-panel);
  border-top: 1px solid var(--hairline);
}

.mcp-harness-undo-btn {
  padding: 0.3rem 0.7rem;
  font-size: 0.75rem;
}

.mcp-harness-undo-depth {
  font-family: var(--font-readout);
  font-size: 0.6875rem;
  color: var(--text-muted);
}

.mcp-harness-undo-hint {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  font-size: 0.6875rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ===========================================================================
   VIS-5 — MIC IN / OUT level meters (src/meters.js, window.Meters).
   Each meter unit is created from JS INSIDE one of the two .anchor labels
   (query by text, "MIC IN" / "OUT") as a plain child of the anchor
   (VIS-7: in the vertical canvas the anchor is a horizontal bar —
   silkscreen label left, this unit right at the bar's end; still no
   restructuring of .canvas's flex children, so the anchor → arrow →
   chain-list rhythm and MC-4's safe-output-note
   insertion point are untouched, and nothing here is absolutely
   positioned over anything. The unit is: one canvas (the 96×26 lamp bar
   + tick + clip dot + canvas-drawn dB scale) over one mono readout line.
   All lamp/zone/tick colors are painted on the canvas from the rq5
   tokens (--meter-low/mid/clip, --text-primary, --hairline as the unlit
   lamp glass, --text-muted numerals) resolved at runtime — this block is
   layout + type only, and deliberately adds NO animation or transition
   of any kind: meter ballistics are functional motion drawn per-frame on
   the canvas, so they stay live under prefers-reduced-motion by
   construction (VIS-6's reduced-motion rule already has nothing to
   quiet here). CSS size MUST stay in lockstep with src/meters.js's
   CANVAS_W/CANVAS_H logical constants (96×26) — the JS sets the DPR-
   scaled backing store; this fixes the CSS footprint.
   =========================================================================== */

.meter-unit {
  /* VIS-7: inside the full-width vertical-flow anchor the unit sits at the
     bar's right end (label left, meter right) — the old 6px margin-top was
     air under the label in the horizontal flow and is gone. */
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  margin-top: 0;
  width: 96px;
}

/* Block, no inline-baseline wobble; fixed CSS footprint = the logical
   canvas size (the backing store is devicePixelRatio-scaled in JS). */
.meter-canvas {
  display: block;
  width: 96px;
  height: 26px;
}

/* The mono dB readout — the console's LCD/VFD register (machine values
   in --font-readout per the design brief), muted like the scale
   numerals; tabular numerals so −∞ / −12.0 / CLIP don't jitter. The
   'CLIP' latch text renders in the meter-clip stop as the one state
   color the readout ever carries. LADDER: per-control value tier
   (0.75rem/400) — weight pinned explicitly because the readout inherits
   through two 700-weight ancestors (.anchor, .canvas-footer-legend) and
   must match the knob/trim values' weight; '−12.0' / 'CLIP' / '−∞' fit
   the fixed 96px unit right-aligned at this size. */
.meter-readout {
  font-family: var(--font-readout);
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-muted);
  white-space: nowrap;
}

/* QA-4 V1 fix: the CLIP state is triple-redundant (red pin at 0 dB + red
   dot on the canvas + this text) — the stop token itself is a non-text
   color (4.01 as text < 4.5), so the text reads in primary while the
   canvas carries the red. */
.meter-readout[data-clip='true'] {
  color: var(--text-primary);
}

/* ===========================================================================
   R2-1 ($impeccable layout, 2026-08-29) — PINNED OUT FOOTER. Created from
   JS by src/meters.js (buildFooterMirror) as a child of the canvas PANEL,
   immediately after the scrolling .canvas: in the bounded vertical canvas
   the panel is the fixed frame and .canvas owns the scrolling, so this
   row is the always-visible region — output level readable from across
   the room regardless of scroll position or card collapse (the closing
   critique's P2: the OUT anchor/meter sits below the fold with the
   default preset expanded). One compact row, fixed height (the 96×26
   meter footprint is the tallest item — no layout pumping): silkscreen
   OUT legend left, the SAME lamp-bar canvas scene + mono dB readout
   (the mirror of the OUT unit's own state — one feed, one ballistics,
   two views) right.

   Family rules it inherits by construction: same 96×26 CSS lockstep as
   .meter-canvas above (no size variant); tokens only; no transitions or
   keyframes (functional metering stays live under reduced motion);
   NEVER de-emphasized while bypassed (the entry-5 dim is scoped to
   .chain-list + .flow-toggle — this footer is the operator's output
   ground truth in that state too, same family as the anchors/meters);
   outside .canvas entirely, so it covers nothing and the round-1
   occlusion guard's zero-overlap invariant is untouched. In horizontal
   flow mode the panel un-bounds (the page owns the scroll) and the
   footer scrolls away WITH the panel — the pinned guarantee is the
   vertical default's; it still renders there as part of the panel.
   =========================================================================== */

/* --- The base plate (redesign item 1 restyle of the R2-1 pinned OUT
   footer): the panel's bottom edge is the OUT corner — jack print legend
   right, the SAME 96×26 lamp scene + mono readout (one feed, one
   ballistics, two views; the mirror is aria-hidden). Fixed height (the
   meter footprint is the tallest item — no layout pumping); machined
   groove along its top edge; NEVER de-emphasized while bypassed or
   gated (the operator's output ground truth in every state — the
   dim/hatch grammar is scoped to the chain and the face, never the base
   plate). Since the fixed one-page round the panel is bounded in BOTH
   flow modes, so the footer is pinned in both — the output ground truth
   never scrolls away. */
.canvas-footer {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.75rem;
  padding: 0.4rem 0.9rem;
  border-top: 1px solid var(--pm-groove-cut);
  box-shadow: inset 0 1px 0 var(--pm-groove-lip);
}

/* OUT jack print: the drawn jack ring + the legend, the anchor
   vocabulary's bottom-edge twin. */
.canvas-footer-legend {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--font-readout);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--pm-print);
  white-space: nowrap;
}

.canvas-footer-legend::before {
  content: '';
  flex: none;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--pm-print);
  background: radial-gradient(circle, var(--pm-register-bg) 0 2px, transparent 2px);
}

/* The mirrored unit reuses .meter-unit/.meter-canvas/.meter-readout
   verbatim; this override only enforces the single-row compact
   register (column stack inherited as-is — canvas over readout). */
.canvas-footer .meter-unit {
  width: 96px;
}

/* ===========================================================================
   BOARD CHROME. Vertical flow is RETIRED (2026-08-31, user direction):
   the board has ONE reading — horizontal, condensed modules
   left-to-right — and .flow-horizontal is a permanent class the canvas
   applies at init (the scoping stays so the rules read as the board's
   own). The FLOW toggle, the vertical-mode geometry branches, and the
   TIDY key (retired the same day — the board is a free canvas) are all
   deleted; the panel carries no chrome controls of its own.
   =========================================================================== */

.canvas-panel {
  position: relative;
}

/* THE BOARD READING (horizontal, the only one): MIC IN at the left,
   chain runs left→right, OUT at the right. The panel stays a bounded
   column of the fixed one-page console and the FACE owns the pan
   (overflow both axes — the row's reach plus any card dragged below
   it), anchors are compact stacked blocks with their meters beneath,
   the drawn chevrons point right, sections take their content width in
   a row list. */
.canvas-panel.flow-horizontal .canvas {
  flex-direction: row;
  overflow: auto;
}

.canvas-panel.flow-horizontal .chain-list {
  flex-direction: row;
  align-items: flex-start;
}

.canvas-panel.flow-horizontal .arrow {
  font-size: 0;
}

.canvas-panel.flow-horizontal .arrow::before {
  transform: rotate(-45deg); /* points right down the row */
}

/* CONDENSED SECTION (2026-08-31 horizontal-default round, tightened by
   the dead-space round the same day). In the horizontal reading each
   section is a condensed MODULE: the rail collapses into a compact
   header band (grip · code · label · badge · fold/eject right) over the
   encoder field, which wraps below. The card's WIDTH is the JS-applied
   per-card width (layout entry `w`, written by the corner resize grip)
   defaulting to the resizer's FLOOR (11rem/176px) — at the floor each
   control row fits exactly one unit, so every module opens as a SINGLE
   STACK of controls with no right-hand dead space; widening the card
   re-wraps the field to fill. Height HUGS CONTENT (no forced
   min-height) and TIDY aligns the row's tops. */
.canvas-panel.flow-horizontal .node-card {
  /* The CSS numbers are the PRE-JS fallback only — JS paints every
     card's content-hug width as an inline style (real browsers) or the
     128px layout-less default. min-width MUST stay at the resize floor
     (6rem): a larger CSS floor silently overrides the hug and re-opens
     the right-hand dead space (the defect this block carried through
     two rounds). */
  width: 6rem;
  min-width: 6rem;
  max-width: 24rem;
  padding: 0.35rem 0.4rem 0.4rem;
  /* One column: header band over the encoder field. */
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
}

.canvas-panel.flow-horizontal .node-card.collapsed {
  padding: 0.3rem 0.45rem 0.35rem;
}

/* The header band: the rail's content laid out horizontally, the
   machined division moved from its right edge to its bottom (the band
   now divides TOP from BODY, not print from field). The whole band is
   the card's MOVE grip (src/canvas.js arms the seat drag from it);
   the buttons and controls inside it stay interactive. */
.canvas-panel.flow-horizontal .section-rail {
  flex-direction: row;
  align-items: center;
  gap: 0.4rem;
  border-right: none;
  border-bottom: 1px solid var(--pm-groove-cut);
  /* Condensed band (2026-08-31): the code IS the height driver now (the
     module name is retired to the code's tooltip); the fold/eject keys
     match it so the band adds one tight print row, never a blank strip
     under the buttons. */
  padding: 0 0.2rem 0.2rem 0.25rem;
}

.canvas-panel.flow-horizontal .section-foot {
  margin-top: 0;
  margin-left: auto;
  padding-top: 0;
  gap: 0.2rem;
}



/* The body: short fields center vertically in whatever room the module
   has (with no forced min-height that is simply content height). */
.canvas-panel.flow-horizontal .section-main {
  justify-content: center;
}

.canvas-panel.flow-horizontal .node-params-inner {
  gap: 0.25rem 0.45rem;
}

/* The 6ch value slot exists for the centered stacked units; inside the
   side-label module rows it is right-hand dead space — values hug their
   text. */
.canvas-panel.flow-horizontal .param-value {
  min-width: 0;
}

/* Trims stretch to the body width (the fixed 10rem throw belongs to the
   full-width vertical row); pad groups fold within the module. */
.canvas-panel.flow-horizontal .trim-row {
  width: auto;
  max-width: 100%;
}

.canvas-panel.flow-horizontal .trim-unit {
  width: 100%;
}

.canvas-panel.flow-horizontal .pad-group {
  max-width: 100%;
}

.canvas-panel.flow-horizontal .safe-output-note {
  align-self: center;
  margin-top: 0;
}

.canvas-panel.flow-horizontal .watchdog-alert {
  align-self: center;
  max-width: 22rem;
}

/* ===========================================================================
   BYPASS-ENGAGED CANVAS INDICATION (refinement entry 5 semantics, the
   Pattern Machine disabled grammar). src/main.js toggles `bypassed` on
   the .canvas-panel from the SAME single-source read
   (AudioBypass.isEngaged()) that drives the strip button, so the two can
   never drift.

   What reads as bypassed: ONLY the chain — the sections and the flow
   switch recede (0.55, the house precedent for inert regions) under the
   ONE disabled texture (the same diagonal hatch as the pre-Start gate —
   one grammar for "not in the circuit", two states). The BYPASSED
   silkscreen line prints beneath the folded sections.

   What must NOT recede (the operator's ground truth while bypassed —
   the dry path is what the room hears): both .anchor blocks with their
   meters, the safe-output note, the watchdog alert, the empty-hint, the
   display register, and the OUT footer. These sit outside .chain-list,
   so the scoping below leaves them at full strength.
   =========================================================================== */

.canvas-panel .chain-list {
  transition: opacity 150ms ease;
}

.canvas-panel.bypassed .chain-list {
  opacity: 0.55;
}

/* The hatch half of the grammar, painted OVER the receded sections
   (pointer-events:none — a pseudo cannot intercept, and the sections
   stay interactive in bypass: the operator may tune while dry). */
.canvas-panel.bypassed .chain-list::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: repeating-linear-gradient(
    -45deg,
    transparent 0,
    transparent 7px,
    rgba(11, 12, 16, 0.45) 7px,
    rgba(11, 12, 16, 0.45) 9px
  );
}

.canvas-panel.bypassed .chain-list::after {
  content: 'BYPASSED';
  flex: none;
  align-self: center;
  padding-top: 0.35rem;
  font-family: var(--font-readout);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--pm-print);
}

/* Horizontal flow: the state line rides the row after the last section
   instead of stacking beneath the column — same register, same tokens. */
.canvas-panel.flow-horizontal.bypassed .chain-list::after {
  align-self: center;
  padding: 0 0 0 0.5rem;
}
