/* Tone Lab rack: the app screen. The top bar, the status bar, the rack of module cards, the cables behind them, the module picker and
   the key help. See design/guidelines.md (Cards, Cables, Patching, Moving, adding and removing cards, The top bar, The status bar, The
   module picker). Load after tokens.css, base.css and controls.css, on <body class="app">.
   Layering inside the rack, from back to front: svg.cables (z-index -1), cards, svg.leads (1), controls and jacks (2), svg.xray (3),
   then jacks again (4) while x-raying, then the move proxy and drop marker (5). svg.xray also holds the cable in hand; jacks rise to 4
   while x-raying or patching, as patching sets .xraying too. The rack isolates its own stacking context, so -1 is above the rack and
   below every card, and the move proxy, though fixed, stays under the top bar and the status bar. */

/* ---------- app top bar ---------- */
.app-head { position: sticky; top: 0; z-index: 30; background: var(--surface); border-bottom: 1px solid var(--line); }
.bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-height: 48px; padding: 8px 16px; }
.brand { display: flex; align-items: center; gap: 8px; margin-right: 4px; color: var(--ink); white-space: nowrap; }
.brand b { font: 600 15px/1 var(--font-ui); letter-spacing: -.02em; }
/* The logo's four dots count the beat: the dot on the beat turns --live. */
.logo { width: 24px; height: 24px; flex: none; }
.logo circle { fill: var(--ink); }
.logo circle.hit { fill: var(--live); }
/* Proposed: not in guidelines.md yet */
/* The output level, L over R, in a .field: the app's, in the scope's place until it has a scope. Its fill is a transform, so a level
   change redraws only the bar. */
.level .bars { display: flex; flex-direction: column; gap: 4px; width: 120px; }
.level b { display: block; height: 6px; background: var(--knob-track); }
.level i { display: block; height: 100%; background: var(--set); transform: scaleX(0); transform-origin: left; }

/* Proposed in guidelines.md, not decided yet */
/* ---------- the status bar (guidelines.md, The status bar): one line pinned to the bottom of the window, holding the status line
   (forms.css) ---------- */
/* Fixed, so a message that appears, changes, clears or wraps never moves the rack, and shown even when empty, so the live region in
   it stays in the accessibility tree. The page keeps the bar's one-line height free at its foot, a constant, so the page's height
   never changes with the message either; a message that wraps covers only bottom padding. Focus, scrolling into view and autoscroll
   stop 8 px above it, measured by the page when it can wrap (pages.md). --status-bar is that one-line height, built from the bar's
   own line, padding and 1 px rule, so the room at the foot follows the bar if they change. */
:root { --status-line: 16px; --status-pad: 4px; --status-bar: calc(var(--status-line) + 2 * var(--status-pad) + 1px); }
.status-bar { position: fixed; inset: auto 0 0; z-index: 30; padding: var(--status-pad) 16px; border-top: 1px solid var(--line); background: var(--surface); }
.status-bar .status { min-height: var(--status-line); font-size: 13px; line-height: var(--status-line); white-space: normal; }
body:has(> .status-bar) { padding-bottom: var(--status-bar); }
:root:has(> body > .status-bar) { scroll-padding-bottom: calc(var(--status-bar) + 8px); }

/* ---------- the rack and its cable layers ---------- */
.app-main { position: relative; }
.rack {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  /* Wide gaps, so cables behind the cards have somewhere to show. */
  gap: 32px;
  /* drawCables sets --cable-room so the lowest cable stays inside the rack. */
  padding: 28px 32px max(36px, var(--cable-room, 0px));
  user-select: none;
  -webkit-user-select: none;
}
/* Cables run behind the cards. Leads cross each card from a patched jack to its edge; hovering lifts cables into svg.xray. */
.rack > svg:is(.cables, .leads, .xray) { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.rack > svg.cables { z-index: -1; }
.rack > svg.leads { z-index: 1; }
/* Above the controls: a lifted cable is the one thing you are looking at. Jacks stay on top, so it still plugs into them. */
.rack > svg.xray { z-index: 3; }
.rack.xraying .jack { z-index: 4; }
.rack.xraying > svg:is(.cables, .leads) { opacity: .25; }
.rack.xraying .card.xr-peer { border-color: var(--ink); }
.rack.xraying .card.xr-src { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
/* 2 px in the signal colour over a white case, so crossings stay readable. */
.cable .case { fill: none; stroke: var(--cable-case-c); stroke-width: var(--cable-case); stroke-linecap: round; }
.cable .wire { fill: none; stroke: var(--k); stroke-width: var(--cable-w); stroke-linecap: round; }
.cable .hit { fill: none; stroke: transparent; stroke-width: 12px; pointer-events: stroke; }
.cable:hover .wire { stroke-width: 3px; }
.cable:hover .case { stroke-width: 7px; }

/* ---------- patching (guidelines.md, Patching): bindPatching and mountCables's hold() set these ---------- */
/* The jack you pressed or dragged from (.armed) and the input the cable would go into (.hot): the hover ring, thickened. No glow and
   no new colour. .rack.patching marks the rack while a cable is in hand; it has no rules of its own, and nothing on a card changes,
   so there is still no stacking context. */
.jack.armed i, .jack.hot i { outline: var(--jack-bw) solid var(--k); outline-offset: 2px; }
/* A drag from these draws a cable, so on touch it can't scroll the page. Empty inputs still scroll. */
.jack.out, .jack.in.patched { touch-action: none; }
/* The cable in hand, at the hover weights. Its free end at the pointer and the snap into a jack say whether it will plug in. */
.xray .cable.held .wire { stroke-width: 3px; }
.xray .cable.held .case { stroke-width: 7px; }

/* ---------- module cards ---------- */
/* No z-index, transform, filter or opacity on cards: a stacking context per card would put the cables above every jack. */
.card { display: flex; flex-direction: column; flex: 0 1 auto; min-width: 0; border: 1px solid var(--card-line); background: var(--surface); }
.card-head { display: flex; align-items: center; gap: 2px; min-height: 28px; padding: 4px 4px 0; }
/* Pictogram, then the name with its type in brackets. A line box tall enough for descenders, so the g in "Logic" keeps its tail.
   No overflow clip: a long name widens the card instead. */
.card h3 { display: flex; align-items: center; gap: 4px; margin-left: 2px; color: var(--ink); font: 600 13px/16px var(--font-ui); white-space: nowrap; cursor: text; }
.card h3:hover { background: var(--well); box-shadow: 0 0 0 3px var(--well); }
.card h3 .type { color: var(--muted); font-weight: 400; }
.ti { display: block; flex: none; width: 14px; height: 14px; margin-right: 2px; color: var(--ink); stroke-width: 1.6; overflow: visible; }
.card-head .btn.sq { width: 20px; height: 20px; border: 0; background: none; color: var(--muted); }
.card-head .btn.sq:hover { background: var(--well); color: var(--ink); }
.card-head .btn.sq .ic { width: 12px; height: 12px; }
.card-head .grip { cursor: grab; touch-action: none; }
.card-head .grip + h3 { margin-left: 0; }
/* On touch the grip takes WCAG 2.5.8's 24 px target but still draws at 20. The 2 px it reaches out is the head's gap and padding,
   so it stops at the title's edge and leaves the title its double-click. The grip is positioned already, and the card stays free of
   a stacking context. */
@media (any-pointer: coarse) {
  .card-head .grip::after { content: ""; position: absolute; inset: -2px; }
}
.card-head .pwr::before { width: 12px; height: 12px; }
.card-head .btn.pwr[aria-pressed="true"] { background: var(--set); color: var(--surface); }
.card-head .btn.run { height: 20px; margin-right: 2px; padding: 0 6px; gap: 4px; font-size: 11px; }
.card-head .btn.run .ic { width: 9px; height: 9px; }
.card-head .btn.run.on { border-color: var(--set); background: var(--set); color: var(--surface); }
.playing .card-head .btn.run.on { border-color: var(--live); background: var(--live); color: var(--ink); }
.card.bypassed h3, .card.bypassed h3 .ti { color: var(--muted); }
/* On the controls rather than .main: opacity there would make a stacking context and drop the controls under the cables. */
.card.bypassed :is(.knob, .ctl > *, .ui) { opacity: .35; }

/* ---------- moving cards (guidelines.md, Moving, adding and removing cards): bindCards sets these, after .xr-src and the grip so
   they win ---------- */
.rack.moving, .rack.moving * { cursor: grabbing; }
.rack.moving > svg.cables { opacity: .25; }
/* Leads and lifted cables go, so the hole is empty and no cable crosses the marker. */
.rack.moving > svg:is(.leads, .xray) { visibility: hidden; }
/* The card stays put as a hole until the drop, so nothing reflows. It can't fade or slide: either would give it a stacking context. */
.rack.moving .card.move-src { border-color: var(--line); background: var(--bg); box-shadow: none; }
.card.move-src > .body { visibility: hidden; }
/* The outline that follows the pointer, and the drop marker: ink, as focus is, since colour means signal kind and --live means playing. */
.move-proxy { position: fixed; z-index: 5; border: 1px solid var(--ink); pointer-events: none; }
.move-mark { position: absolute; z-index: 5; width: 4px; background: var(--ink); pointer-events: none; }

/* Inputs on the left rail, outputs on the right, so signal reads left to right across the rack. */
.body { flex: 1; display: flex; gap: 8px; padding: 8px; }
.main { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 8px; min-width: 0; }
.ins, .outs { display: flex; flex-direction: column; }
.ins { padding-right: 4px; box-shadow: 1px 0 0 var(--line); }
.outs { padding-left: 4px; box-shadow: -1px 0 0 var(--line); }
.ins:empty, .outs:empty, .ctls:empty { display: none; }
/* Knobs under the switches: the input rail already sets the card's height, so stacking narrows the card for free. */
.ctls { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.opts { display: grid; grid-template-columns: fit-content(64px) auto; gap: 4px 8px; }
.opts > .ctl { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; justify-items: start; }
.opts .tl { white-space: normal; }
.knobs { display: flex; }
/* Dimmed rather than hidden: the card keeps its size and shows what the other modes offer. */
.off { opacity: .3; }
.card .sel { width: 112px; height: 24px; padding: 0 24px 0 8px; border: 0; box-shadow: inset 0 0 0 1px var(--ctl-line); background-position: right 8px center; font-size: 11.5px; }
.card .sel:hover { box-shadow: inset 0 0 0 1px var(--ink); }
.ui { display: flex; flex-direction: column; gap: 6px; }
/* Lifts controls above the cable layer so a click lands on the control; cables stay hoverable in the gaps. */
.card :is(.knob, .seg, .sel, .btn, .st, .pad, .ms) { position: relative; z-index: 2; }
/* Each of those stacks in source order, so a focused one rises, or the next pad or button would cover its ring. */
.card :is(.knob, .sel, .btn, .st, .pad):focus-visible { z-index: 3; }

/* ---------- module bodies ---------- */
/* Mixer and Submix: each channel is a strip, its level knob straight above its mute, solo and input. */
.card:is([data-type="mixer"], [data-type="submix"]) .body { display: grid; grid-template-columns: repeat(4, 60px); align-content: start; justify-items: center; gap: 4px; }
.card:is([data-type="mixer"], [data-type="submix"]) :is(.main, .ctls, .knobs, .ins, .ui) { display: contents; }
.card:is([data-type="mixer"], [data-type="submix"]) .ins .jack { grid-row: 3; }
/* Their inputs lead down out of the bottom edge, so the labels go above the jacks. */
.card:is([data-type="mixer"], [data-type="submix"]) .ins .jack { flex-direction: column-reverse; }
.card[data-type="submix"] .body { grid-template-columns: repeat(4, 60px) auto; }
.card[data-type="submix"] .outs { grid-area: 3 / 5; }
.ms { grid-row: 2; display: flex; }
.ms .btn { width: 22px; height: 18px; padding: 0; font-size: 10.5px; }
.ms .btn + .btn { margin-left: -1px; }

/* Trigger Grid: a seek strip of step numbers, where the page has one, over one row of pads per lane. */
.lanes { display: grid; grid-template-columns: max-content max-content; align-items: center; gap: 2px 6px; }
.lanes .lane { display: contents; }
.lanes .seek-row > * { margin-bottom: 4px; }
.lanes .tl { font-size: 10.5px; }
.lanes .lane[data-lane] .tag { cursor: text; }
.lanes .lane[data-lane] .tag:hover { background: var(--well); box-shadow: 0 0 0 2px var(--well); }
/* Lane names label the output jacks too. A wide name, such as "wwwwww", grows the output rail rather than running into the card's edge. */
.card[data-type="grid"] .outs .jack { width: auto; min-width: 40px; }
/* Pads: 2 px gaps and an extra 6 px every four, so the beats read at a glance. */
.pads { display: flex; gap: 2px; }
.pad { flex: none; width: 18px; height: 18px; padding: 0; border: 0; background: var(--cell); cursor: pointer; }
.pad:nth-child(4n+1):not(:first-child) { margin-left: 6px; }
.pad:hover { background: var(--cell-hover); }
.pad.on { background: var(--set); }
.pad.now { box-shadow: inset 0 0 0 2px var(--live); }
.pad.on.now { background: var(--live); }
.pad.off { opacity: .3; }
/* Seek cells: the marker is a fill, where the playhead is a ring. */
.pad.seek { background: none; box-shadow: inset 0 0 0 1px var(--line); color: var(--muted); font: 500 10px/18px var(--font-ui); text-align: center; font-variant-numeric: tabular-nums; }
.pad.seek:hover { background: var(--well); }
.pad.seek[aria-current] { background: var(--set); box-shadow: none; color: var(--surface); }
.card[data-type="euclid"] .pad { pointer-events: none; }

/* Sequencers: one column per step. */
.steps { display: grid; grid-template-columns: repeat(8, minmax(40px, 1fr)); }
.stc { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 6px 0; }
.stc + .stc { box-shadow: -1px 0 0 var(--well); }
.stc.now { box-shadow: inset 0 2px 0 var(--live); }
.stc .seek { width: 24px; }
/* A step bar is thin, so it takes the track's darker grey rather than --cell. */
.st { display: block; width: 24px; height: 8px; padding: 0; border: 0; background: var(--knob-track); cursor: pointer; }
.st:hover { background: var(--cell-hover); }
.st[aria-pressed="true"] { background: var(--set); }
.stc.now .st[aria-pressed="true"] { background: var(--live); }
/* Seek cells take over the step numbers, so a sequencer without a strip, such as the app's, keeps them. Hidden rather than removed,
   each label keeps the room its readout shows in. */
.stc > .seek ~ .knob .lbl { visibility: hidden; }

.leds { display: flex; gap: 2px; }
.led { width: 10px; height: 10px; background: var(--cell); }
.led:nth-child(4n+1):not(:first-child) { margin-left: 4px; }
.led.on { background: var(--set); }
.led.off { opacity: .3; }

.card[data-type="compressor"] .ui { flex-direction: row; align-items: center; gap: 8px; align-self: stretch; }
.meter { flex: 1; min-width: 120px; height: 6px; background: var(--knob-track); }
.meter b { display: block; width: 0; height: 100%; background: var(--set); }
.viz { display: block; width: 240px; height: 96px; background: var(--well); }
.viz path { fill: none; stroke: var(--ink); stroke-width: 1.25; vector-effect: non-scaling-stroke; }

/* ---------- renaming in place: the field grows with the name, up to 24 characters; lane names take 6 ---------- */
.card h3 .rename { height: 22px; margin: -3px 0; padding: 0 6px; border: 0; border-radius: 0; background: var(--surface); box-shadow: inset 0 0 0 2px var(--ink); color: var(--ink); font: 600 13px/1 var(--font-ui); outline: none; user-select: text; -webkit-user-select: text; }
.card h3 .rename::placeholder { color: var(--muted); font-weight: 500; opacity: 1; }
.card h3 .count { padding-left: 2px; color: var(--muted); font: 500 10.5px/1 var(--font-ui); font-variant-numeric: tabular-nums; }
.card h3.editing, .card h3.editing:hover { background: none; box-shadow: none; }
.lanes .rename-lane { width: calc(6ch + 12px); height: 18px; margin: -1px 0; padding: 0 4px; border: 0; border-radius: 0; background: var(--surface); box-shadow: inset 0 0 0 2px var(--ink); color: var(--ink); font: 500 10.5px/1 var(--font-ui); outline: none; user-select: text; -webkit-user-select: text; }
.lanes .rename-lane::placeholder { color: var(--muted); opacity: 1; }

/* ---------- the module picker: modules in drawers by category, from specimen.html ---------- */
/* The only place colour marks a category: each drawer sets data-cat, and tokens.css turns that into --cat for its square. */
.picker { padding: 0 32px 32px; }
/* Drawers share their rules, so the picker reads as one ruled table. */
.drawers { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1px; }
.drawer { padding: 16px; background: var(--surface); box-shadow: 0 0 0 1px var(--line); }
.drawer h3 { display: flex; align-items: center; gap: 8px; font: 600 15px/1.2 var(--font-ui); }
.drawer h3 i, .blurb i { flex: none; width: 8px; height: 8px; background: var(--cat); }
.drawer h3 span { margin-left: auto; color: var(--muted); font: 500 13px/1 var(--font-ui); font-variant-numeric: tabular-nums; }
.drawer > div { display: flex; flex-wrap: wrap; gap: 2px; margin-top: 12px; }
/* A line under the drawers says what the module under the pointer does. */
.blurb { display: flex; align-items: center; gap: 8px; min-height: 48px; margin-top: 1px; padding: 12px 16px; background: var(--surface); box-shadow: 0 0 0 1px var(--line); color: var(--text); }
.blurb b { color: var(--ink); font-weight: 600; white-space: nowrap; }

/* ---------- key help under the rack ---------- */
.hint { padding: 0 32px 32px; color: var(--muted); font: 500 12px/22px var(--font-ui); }
