/* Feld — dense, Figma-shaped chrome around a music canvas.
 *
 * The palette is four colours: a blue (#485999), a green (#479A71), a red
 * (#CE605F) and a pale lavender (#E9E9F7). Light mode — the default, and the
 * base :root below — uses them as given; dark mode lifts the blue into the
 * same hue family (#6D82C7) so a 1px line still reads on near-black, and the
 * lavender becomes the primary button, a pale key on a dark board.
 *
 * Clip colours are a separate palette entirely — those belong to the music,
 * not the chrome — but the two are no longer strangers. The blue was rotated
 * +16° of hue to sit with the clip palette while staying unmistakably chrome;
 * see the note on --accent for why sixteen and not more.
 *
 * Light is the base rather than an override so the app renders correctly with
 * no `data-theme` at all — the default survives a failed inline bootstrap. */

:root {
  --bg: #e9e9f7;
  --panel: #ffffff;
  --panel-2: #f2f2fa;
  --line: #dcdcec;
  --line-soft: #e7e7f3;
  --text: #191c23;
  --dim: #626976;
  /* One secondary grey, not two. --dim-2 measured 3.17:1 here and 3.07:1 in
     dark, under the 4.5:1 floor for text below 17pt, across 91 `color` uses.
     Solving for the nearest passing colour at the same hue landed on #626977
     — which is --dim. At an accessible floor there is no room for a third
     tier of grey text, so the tier below --dim now comes from weight and size
     instead. Kept as an alias rather than find-and-replaced: the name still
     says "quieter than --dim", which is true, it's just no longer true in
     colour. */
  --dim-2: var(--dim);
  /* The old --dim-2 value, for things that are not text: fills, hairline
     shadows, decorative glyphs. Never put a word in this colour. */
  --faint: #8a919e;
  /* The chrome blue, twice adjusted and both for reasons worth keeping.
     First darkened a step, because the original measured 4.36:1 against the
     canvas ground — a miss only visible where accent text sits on --bg rather
     than a panel. Then the whole family rotated +16° of hue, from 211 to 227,
     to sit with the Orchid clip palette instead of beside it.

     +16 rather than more: it has to stay clearly NOT a clip colour. Counter-
     intuitively, rotating toward the palette's indigo moves it FURTHER from
     what a notes clip actually looks like (ΔE 27.1 → 30.2 against the pale
     `#a5a0d9` card), because the card is a light tint and the chrome is a
     mid-dark fill. Past about +20 it starts closing on the raw `#655CCC`
     instead, which is the thing to avoid. Contrast improved on the way:
     accent on --bg is now 5.70:1, white on --cta 6.64:1. */
  --accent: #465796;
  /* Effect displays. An RGB triple rather than a colour, because every use is
     an rgba() at a different weight — see the contrast note in css below. */
  --fx-ink: 70, 87, 150;
  --accent-soft: rgba(72, 89, 153, 0.11);
  /* Play and record. Both were left behind when the palette landed, and for
     different reasons — worth stating, because the fix isn't the one you'd
     reach for by eye.

     The GREEN was the real offender and it wasn't its hue: at h150 it already
     sits 55° clear of the clip green, but it was saturation 37 in an app where
     every other accent had moved to 52–62. A muted sage among vivid company
     reads as a leftover. Same hue, house saturation.

     The RED needed the opposite of the obvious move. Warming it toward orange
     to get away from the clip pink walks it straight into the peer terracotta
     — at h8 that collision measures ΔE 8.9, against 14.3 if it stays put. So
     the hue holds and the correction is contrast: white-on-#ce605f measured
     **3.84:1** and had been failing all along. This clears 4.76:1. */
  --good: #33a36b;
  --warn: #a8721c;
  --bad: #cc423e;
  --solo: #a97c05;
  /* On a pale board the lavender would vanish — the blue takes the key. */
  --cta: #485999;
  --cta-ink: #ffffff;
  --cta-hover: #3d4d87;
  /* The mark's four strokes: it climbs through the palette, then trails off. */
  --logo-ink: #2a2a2a;
  --hover: #e2e2f1;
  --line-hover: #c2c2d8;
  /* Tuned for a 4px track: the old values were a wash designed to sit behind
     a 20px filled block, and vanished once the track got thin. */
  --sl-fill: #6f7fb8;
  --sl-fill-2: #58669c;
  --card: #f7f7fc;
  --accent-hover: #3d4d87;
  --accent-ink: #35427a;
  --accent-line: rgba(72, 89, 153, 0.42);
  --accent-soft-2: rgba(72, 89, 153, 0.2);
  --solo-line: rgba(169, 124, 5, 0.42);
  --scroll: #c5c5da;
  --sunken: #dedeef;
  --float: rgba(255, 255, 255, 0.96);
  --scrim: rgba(28, 32, 42, 0.32);
  --shadow-lg: 0 30px 90px rgba(24, 30, 45, .22);
  /* Piano keys are always white and black, so their edges stay dark in both themes. */
  --key-line: #23262f;
  --panel-w: 260px;              /* a sound's name needs ~110px to exist */
  --top-h: 48px;
  --r: 6px;
  --font: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* --- The type scale ---------------------------------------------------
   * Four roles, not twelve sizes. An audit found 12 distinct size/weight
   * pairs in the panels, all inside a 3px band — which is why nothing read as
   * primary or secondary and the whole thing looked uniformly small.
   * Hierarchy comes from WEIGHT and size; size only separates the jobs below.
   * Adding another size is how the drift started.
   *   title  a thing's name — panel headers, sound names
   *   body   labels and most running text
   *   value  numbers; tabular so columns of them line up
   *   micro  meta and captions
   *   label  the uppercase section landmarks
   *
   * `badge` is for numerals living inside a 16px chip or a step-sequencer
   * cell. That's not reading text and the box sets its size.
   *
   * --- Sized against the platform, not by eye (Aug 2026) ------------------
   * A measurement pass found 93 of the 118 text elements in the chrome
   * rendering at 10 or 11px, and exactly three at macOS's Body size of 13pt.
   * The scale wasn't dense, it was demoted — every role sitting one step
   * below its platform equivalent, so the sizes reached for by default were
   * the ones macOS reserves for Caption and Footnote. Each role below now
   * names the macOS style it answers to; don't move one without the others.
   */
  --fs-title: 15px;              /* Title 3 */
  --fs-body: 13px;               /* Body */
  --fs-value: 13px;              /* Body */
  --fs-micro: 12px;              /* Callout */
  --fs-label: 11px;              /* Subheadline — see --fw-label */
  --fs-badge: 11px;              /* Footnote */
  --fw-title: 600;
  --fw-body: 450;
  --fw-value: 500;
  --fw-micro: 500;
  /* Section heads are the one thing that stays small. They're a landmark you
     scan past, not text you read, so they buy their legibility with weight
     and a passing colour rather than with size. */
  --fw-label: 640;

  /* --- Sizing ----------------------------------------------------------
   * Two control heights and two radii. Was nine and five: every value
   * individually reasonable, no two in agreement. Heights grew by 2 and 4 to
   * carry 13px text with its optical padding intact. */
  --h-ctl: 34px;                 /* anything you click or type into */
  --h-mini: 28px;                /* the small square ones inside rows */
  --r-ctl: 6px;
  --r-mini: 4px;

  /* --- The spacing grid -------------------------------------------------
   * Every gap, pad and margin is a multiple of 8, with 4 as the only
   * half-step. The same audit that found 12 type sizes found 94 spacing
   * values — 6px and 7px and 9px and 10px all doing the same job in
   * different places, which is what "inconsistently spaced" actually is:
   * not one bad number, a hundred nearly-equal ones.
   *
   * Steps have jobs. Reach for the one that matches the RELATIONSHIP, not
   * the one that looks right — looking right is how you get 94 values.
   *
   * Deliberately excluded, and they are not exceptions to argue with:
   *   - hairlines (1px borders, 1–2px optical nudges) — not spacing
   *   - anything positioned in the music's own coordinates: clips on the
   *     canvas, piano-roll notes, the keybed. Those answer to bars and
   *     semitones, and an 8px grid has nothing to say about them.
   *
   * --- Which mechanism (Aug 2026) ---------------------------------------
   * THE CONTAINER OWNS THE RHYTHM. A stack of things is a flex column with a
   * `gap`; children carry no margin of their own. If two children of one
   * container want different spacing, that is the signal to GROUP them — see
   * `section()` in ui/dom.js, which wraps runs of `.row` so the flush list and
   * the spaced blocks stop arguing inside one box — not to reach for a margin.
   *
   * A margin is still right for exactly three things:
   *   - `margin-left/-top: auto` — flex alignment, not spacing at all
   *   - a hairline nudge (1–2px), per the exclusion above
   *   - space BETWEEN siblings of one class, which `gap` cannot express
   *     without inventing a wrapper: `.sect + .sect`, `* + .edit-sect-h`.
   *     Note the shape — a `+` rule needs no `:first-child` exception to
   *     cancel it at the top. Both of those started life as a margin on every
   *     header plus an override to undo it on the first, which is the tell
   *     that the space belonged between the items, not on them.
   */
  --sp-1: 4px;                   /* inside one control — icon to its label */
  --sp-2: 8px;                   /* between controls that act together */
  --sp-3: 12px;                  /* a panel's side padding; row to row */
  --sp-4: 16px;                  /* between groups inside a section */
  --sp-5: 24px;                  /* between sections */
  --sp-6: 32px;                  /* major blocks; a modal's padding */
  --sp-7: 48px;                  /* the rare big one */
}

/* Numbers are the UI font with tabular figures, not a monospace face.
   Mono read as debug output; tabular figures align just as well. */
.tnum, .sl-val, .pos-read, .bpm, .knob-cap, .clip-pos, .num-val {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Dark mode. Same structure, cooler neutrals, and the accent lifted so a 1px
   line still reads on near-black. Set by data-theme rather than a media query:
   the choice is explicit and it sticks. */
:root[data-theme="dark"] {
  --bg: #0f1015;
  --panel: #171820;
  --panel-2: #1c1e27;
  --line: #262936;
  --line-soft: #1f222d;
  --text: #e7e8ec;
  /* Lifted from #838a99 so it clears 4.5:1 on the card ground too, not just
     on the panel. See the light block for why --dim-2 is an alias. */
  --dim: #868d9d;
  --dim-2: var(--dim);
  --faint: #5f6675;
  --accent: #6d82c7;
  --fx-ink: 150, 168, 220;
  --accent-soft: rgba(109, 130, 199, 0.16);
  --good: #45c485;
  --warn: #ffbe4d;
  --bad: #cc423e;
  --solo: #ffd23d;
  /* On a dark board the lavender takes the key: a pale button, dark ink. */
  --cta: #e9e9f7;
  --cta-ink: #171820;
  --cta-hover: #f6f6fc;
  /* The trailing stroke goes lavender rather than soft blue. */
  --logo-ink: #eeeeee;
  --hover: #262a37;
  --line-hover: #333a49;
  --sl-fill: #5c6fb3;
  --sl-fill-2: #7b8ed0;
  --card: #23262f;
  --accent-hover: #8294d1;
  --accent-ink: #e9e9f7;
  --accent-line: #6d82c755;
  --accent-soft-2: rgba(109, 130, 199, 0.24);
  --solo-line: #ffd23d55;
  --scroll: #333846;
  --sunken: #10121a;
  --float: rgba(24, 26, 34, 0.94);
  --scrim: rgba(6, 7, 10, 0.6);
  --shadow-lg: 0 30px 90px rgba(0, 0, 0, .6);
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%; overflow: hidden;
  background: var(--bg); color: var(--text);
  font: var(--fw-body) var(--fs-body)/1.4 var(--font);
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overscroll-behavior: none;
}

button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; border: none; background: none; }
::selection { background: var(--accent-soft); }

/* ---------- top bar ---------- */

#topbar {
  height: var(--top-h);
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 0 var(--sp-3);
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: relative; z-index: 20;
}

.tb-left { display: flex; align-items: center; gap: var(--sp-2); flex: 1 1 0; min-width: 0; }
.tb-mid { display: flex; align-items: center; gap: var(--sp-1); }
.tb-right { display: flex; align-items: center; gap: var(--sp-1); flex: 1 1 0; justify-content: flex-end; }
/* Buttons that belong together sit tighter than the gap between clusters. */
.tb-group { display: flex; align-items: center; gap: var(--sp-1); }
.tb-right .tb-group { gap: var(--sp-2); }

/* The mark stands on its own — no plate, no wordmark. The artwork arrived as
   two files differing only in fill (#EEEEEE / #2A2A2A); it's one path filled
   with currentColor here, so the theme sets it in one place and the mark
   inherits hover for free. */
.logo {
  display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: var(--r-ctl); flex: none;
  /* Zero padding, and a mark that refuses to shrink. A generic button rule
     was applying `padding: 1px var(--sp-2)`, which flex then took out of the SVG's
     width — the mark was rendering 30 wide by 40 tall on a square viewBox,
     i.e. squashed, not merely small. */
  padding: 0;
  color: var(--logo-ink);
  transition: opacity .12s;
}
.logo:hover { opacity: .78; }
/* Three guards, because this broke once already and it wasn't obvious.
   `flex: none` stops a flex parent taking its padding out of the mark's width
   (which rendered it 30 wide by 40 tall on a square viewBox — squashed, not
   just small). `aspect-ratio` means that even if something DOES constrain it
   one day, it scales instead of distorting. `min-width` keeps it from being
   compressed to nothing inside a narrow container. */
.logo-mark {
  display: block;
  flex: none;
  aspect-ratio: 1 / 1;
  min-width: fit-content;
}
.logo b { transform: translateY(-0.5px); }

/* The one piece of text in the bar that names the thing you're working on, so
   it's allowed to sit a step above the token scale rather than inside it. */
.proj-name {
  background: none; border: 1px solid transparent; border-radius: var(--r-ctl);
  padding: var(--sp-1) var(--sp-2); font-weight: var(--fw-title); font-size: 15px;
  min-width: 60px; max-width: 260px; width: 100%;
}
.proj-name:hover { border-color: var(--line); }
.proj-name:focus { outline: none; border-color: var(--accent); background: var(--bg); }

.tb-btn {
  height: var(--h-ctl); min-width: 32px; padding: 0 var(--sp-2);
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-ctl); color: var(--dim); transition: background .12s, color .12s;
}
.tb-btn:hover { background: var(--panel-2); color: var(--text); }
.tb-btn.active { color: var(--accent); background: var(--accent-soft); }
.tb-btn.wide { font-size: var(--fs-body); font-weight: var(--fw-title); letter-spacing: .2px; padding: 0 var(--sp-3); }
.tb-btn.play { color: var(--text); background: var(--panel-2); min-width: 40px; }
.tb-btn.play:hover { background: var(--hover); }
.tb-btn.play.on { background: var(--good); color: #0b1a14; }
.tb-btn.rec:hover { color: var(--bad); }
.tb-btn.rec.on { background: var(--bad); color: #fff; animation: rec-pulse 1.6s ease-in-out infinite; }
/* Counting in: the button holds the beat number and ticks once per beat. */
.tb-btn.rec.counting {
  background: var(--accent-soft); color: var(--accent);
  font-size: var(--fs-body); font-weight: var(--fw-title);
  font-variant-numeric: tabular-nums;
}
@keyframes rec-pulse { 50% { opacity: .62; } }

.divider { width: 1px; height: 16px; background: var(--line); margin: 0 var(--sp-1); }

.pos-read {
  font: var(--fw-value) var(--fs-body)/1 var(--font); color: var(--text);
  min-width: 48px; text-align: center; letter-spacing: .3px;
}

.bpm-wrap { display: flex; align-items: center; gap: var(--sp-1); }
.bpm-wrap span { font-size: var(--fs-badge); color: var(--dim-2); letter-spacing: .6px; }
.bpm {
  width: 40px; background: var(--panel-2); border: 1px solid transparent;
  border-radius: var(--r-ctl); padding: var(--sp-1) var(--sp-1); text-align: center;
  font: var(--fw-value) var(--fs-body) var(--font);
}
.bpm { cursor: ns-resize; }
.bpm:hover { border-color: var(--line-hover); }
.bpm:focus { outline: none; border-color: var(--accent); cursor: text; }
/* Set while a number is being dragged, so the drag can't select the text. */
body.num-drag { user-select: none; cursor: ns-resize; }

.avatars { display: flex; align-items: center; }
.avatar {
  width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-micro); font-weight: 700; color: var(--sunken);
  border: 2px solid var(--panel); margin-left: -6px;
  flex: none; user-select: none;
}
.avatar:first-child { margin-left: 0; }
.avatar.me { cursor: pointer; }
.avatar.more { background: var(--panel-2) !important; color: var(--dim); font-size: var(--fs-badge); }

.status { display: flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-body); color: var(--dim); }
.status i { width: 6px; height: 6px; border-radius: 50%; background: var(--faint); }
.status.s-live i { background: var(--good); box-shadow: 0 0 0 3px rgba(71,154,113,.18); }
.status.s-connecting i, .status.s-reconnecting i { background: var(--warn); animation: pulse 1.1s infinite; }
.status.s-offline i { background: var(--faint); }
@keyframes pulse { 50% { opacity: .3; } }

.share {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  height: var(--h-ctl); padding: 0 var(--sp-3); border-radius: var(--r-ctl);
  background: var(--cta); color: var(--cta-ink); font-weight: var(--fw-title); font-size: var(--fs-body);
}
.share:hover { background: var(--cta-hover); }

/* ---------- layout ---------- */

#stage { display: flex; height: calc(100% - var(--top-h)); }

.panel {
  width: var(--panel-w); flex: none;
  background: var(--panel);
  overflow-y: auto; overflow-x: hidden;
  scrollbar-width: thin; scrollbar-color: var(--scroll) transparent;
}
/* Each panel keeps its own width once you've dragged it; both fall back to the
   shared default, so the narrow-screen override below still works untouched. */
#left { border-right: 1px solid var(--line); width: var(--panel-w-left, var(--panel-w)); }
#right { border-left: 1px solid var(--line); width: var(--panel-w-right, var(--panel-w)); }

/* The grab strip sits ON the border, half in each neighbour — wide enough to
   hit, invisible until you go near it. */
.panel-grip {
  position: absolute; top: 0; bottom: 0; width: 9px; z-index: 15;
  cursor: col-resize; touch-action: none;
}
.panel-grip::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 4px; width: 1px;
  background: var(--accent); opacity: 0; transition: opacity .12s;
}
.panel-grip:hover::after, .panel-grip.on::after { opacity: 1; }
body.col-resizing { cursor: col-resize; user-select: none; }
body.col-resizing .panel-grip::after { opacity: 1; }
.panel::-webkit-scrollbar { width: 8px; }
.panel::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 4px; border: 2px solid var(--panel); }
.panel::-webkit-scrollbar-track { background: transparent; }

#canvas-wrap { flex: 1; position: relative; min-width: 0; }
#canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; }

.hint {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%);
  background: var(--float); border: 1px solid var(--line);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-ctl); font-size: var(--fs-body); color: var(--dim);
  white-space: nowrap; pointer-events: none;
  backdrop-filter: blur(8px);
  transition: opacity .5s, transform .5s;
}
.hint b { color: var(--text); font-weight: var(--fw-title); margin-right: var(--sp-1); }
.hint.gone { opacity: 0; transform: translateX(-50%) translateY(8px); pointer-events: none; }
/* When the hint IS the call to action, clicking it does the thing it asks for. */
.hint.cta { pointer-events: auto; cursor: pointer; border-color: var(--accent-soft); }
.hint.cta:hover { border-color: var(--accent); color: var(--text); }

/* The empty canvas, saying what it's for. Quiet enough not to read as a modal,
   central enough to be the thing you look at. Only the button takes clicks —
   the rest of the canvas keeps panning, marquee-selecting and accepting drops
   straight through it. */
.canvas-empty {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
  pointer-events: none; transition: opacity .35s;
}
.canvas-empty.gone { opacity: 0; }
.ce-in { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); text-align: center; }
.ce-title { font-size: 15px; font-weight: var(--fw-title); color: var(--dim); letter-spacing: .1px; }
.ce-sub { font-size: var(--fs-body); color: var(--dim-2); max-width: 280px; line-height: 1.5; }
.ce-cta {
  pointer-events: auto;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  height: 32px; padding: 0 var(--sp-4); border-radius: 8px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text); font-size: var(--fs-body); font-weight: var(--fw-title);
  transition: background .12s, border-color .12s;
}
.ce-cta:hover { background: var(--hover); border-color: var(--accent); }

/* ---------- panel primitives ---------- */

.pnl-h {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-3) var(--sp-2); font-size: var(--fs-title); font-weight: var(--fw-title);
  letter-spacing: .4px; color: var(--text);
}
.pnl-h.sticky {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel); border-top: 1px solid var(--line-soft);
  padding-top: var(--sp-3);
}
.pnl-h-count { margin-left: auto; color: var(--dim-2); font-weight: var(--fw-value); }
.pnl-dot { width: 8px; height: 8px; border-radius: 2px; flex: none; }

.ghost-btn {
  margin-left: auto; font-size: var(--fs-micro); font-weight: var(--fw-title);
  padding: var(--sp-1) var(--sp-2); border-radius: 4px; color: var(--dim);
  border: 1px solid var(--line);
}
.ghost-btn:hover { color: var(--text); border-color: var(--line-hover); background: var(--panel-2); }
.ghost-btn.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
/* The way back out of a sound. A long sound name truncates rather than
   squeezing the one control that undoes the selection. */
.ghost-btn.back { flex: none; white-space: nowrap; }
.pnl-h > span:not(.pnl-dot):not(.pnl-h-count) {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* Sections separate with space, not rules. A stack of ALL-CAPS headers over
   hairlines read as an admin panel — and on a short panel the rules outnumbered
   the content. Sentence case, no divider, generous room above. */
/* One rhythm per container.
 *
 * A section is a flex column with a single gap; `section()` in dom.js groups
 * runs of `.row` into `.rows` so the one thing that wants a DIFFERENT rhythm
 * (a flush list) is its own container. Everything a section holds — the
 * header, a button row, a note, a knob grid — now takes the gap and carries
 * no margin of its own.
 *
 * The 24px that used to sit on top of every `.sect-h` was never "space inside
 * a section"; it was space BETWEEN sections wearing the wrong hat, which is
 * why it needed a `:first-child` override to cancel it at the top of a panel.
 * As a sibling rule it needs no exception. */
.sect { display: flex; flex-direction: column; gap: var(--sp-2); padding: 0 var(--sp-3) var(--sp-1); }
/* --sp-5, not --sp-4: the previous section's padding-bottom (--sp-1) is still
   in play, so 4 + 24 reproduces the 28px the old header margin produced. */
.sect + .sect { margin-top: var(--sp-5); }
.sect:last-child { padding-bottom: var(--sp-5); }
/* Flush by design: `min-height: --h-ctl` on each row is the separation. */
.rows { display: flex; flex-direction: column; }
/* A header and a row label were both `--dim` and three points apart in size,
   so a section title read as just another field name. The differentiator is
   typographic, not chromatic — uppercase and tracked-out, the same treatment
   `.edit-sect-h` already uses in the editors, which makes it a landmark you
   scan past rather than a word you read. */
.sect-h {
  font-size: var(--fs-label); font-weight: var(--fw-label);
  color: var(--dim); text-transform: uppercase; letter-spacing: .7px;
}
/* A foldable section's header is the control — the whole strip, not the
   chevron, because a 13px target in a panel you're scrolling is no target. */
.sect-fold-h {
  display: flex; align-items: center; gap: var(--sp-1);
  width: 100%; text-align: left; padding: 0; color: var(--dim);
}
.sect-fold-h:hover { color: var(--text); }
.sect-fold-h svg { transition: transform .14s; flex: none; }
.sect-fold.folded .sect-fold-h svg { transform: rotate(-90deg); }
/* .sect-body's layout and its folded state now live with the fold animation at
   the end of this file — a `display: none` here would win over the animated
   grid-template-rows no matter where the animation rule sat. */
.sub-h {
  font-size: var(--fs-label); font-weight: var(--fw-label);
  color: var(--dim); margin: var(--sp-4) 0 var(--sp-2);
}

.row { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--h-ctl); }
.row-l { width: 56px; flex: none; font-size: var(--fs-body); font-weight: var(--fw-body); color: var(--dim); }
.sl { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
/* A switch is 32px wide, so its label has no reason to be squeezed into the
   56px column the sliders need — "Include tail" was wrapping onto two lines
   next to an inch of empty space. The label takes the room instead and the
   switch sits at the end, which is also how Preferences does it. */
.row.row-sw .row-l { width: auto; flex: 1; }
.row.row-sw .sl { flex: none; }

/* A slider, not a progress bar. The old control was a 20px filled rectangle
   with no handle — the single most unfinished-looking thing in the app. Thin
   recessed track, accent fill, and a grip that shows itself on approach. */
.sl-track {
  flex: 1; height: var(--h-ctl); position: relative;
  cursor: ew-resize; min-width: 0; display: flex; align-items: center;
}
.sl-track::before {
  content: ''; position: absolute; left: 0; right: 0; height: 4px;
  border-radius: 2px; background: var(--sunken);
}
.sl-fill {
  position: absolute; left: 0; height: 4px; border-radius: 2px;
  background: var(--sl-fill); pointer-events: none;
}
.sl-track:hover .sl-fill { background: var(--sl-fill-2); }
/* Always visible, and that's the whole point.
 *
 * The grip used to be `opacity: 0` until hover, which meant a slider at rest
 * was a coloured bar with no handle — nothing about it said "drag me", and a
 * panel full of them read as progress meters. A control has to advertise
 * itself before you touch it, not after. Hover and drag now only make it
 * bigger, which is feedback rather than an introduction. */
.sl-grip {
  position: absolute; top: 50%; width: 13px; height: 13px; margin: -6.5px 0 0 -6.5px;
  /* Filled with the ink, ringed in the panel colour. A panel-coloured DISC was
     tried first and works on the filled half of the rail (3.9:1) but nearly
     vanishes on the empty half in dark, where panel and sunken are two shades
     of the same near-black — and a handle parked at zero is exactly when you
     most need to find it. The ink reads against both halves in both themes. */
  border-radius: 50%; background: var(--text); border: 2px solid var(--panel);
  box-shadow: 0 1px 2px rgba(20, 24, 36, .3); pointer-events: none;
  transition: transform .12s;
}
.sl-track:hover .sl-grip, .sl-track.dragging .sl-grip { transform: scale(1.2); }
/* Bipolar controls (pan) get a centre mark, so "middle" is a place you can see. */
.sl-track.bipolar::after {
  content: ''; position: absolute; left: 50%; width: 1px; height: 8px;
  background: var(--line-hover); margin-left: -0.5px;
}
.sl-val {
  width: 56px; flex: none; background: none; border: 1px solid transparent;
  border-radius: var(--r-mini); padding: var(--sp-1) var(--sp-1); text-align: right;
  font: var(--fw-value) var(--fs-value) var(--font); color: var(--text);
}
.sl-val:hover { border-color: var(--line); }
.sl-val:focus { outline: none; border-color: var(--accent); background: var(--bg); text-align: left; }

/* Qualified by element on purpose. `.sel` also means "selected" on rows like
   .sound and .clip-row, and an unqualified .sel was giving those a select box's
   22px height and flex:1 — which collapsed the row and pushed its own label out
   the top. */
/* `--h-mini`, not `--h-ctl`. A row is --h-ctl tall, so a control that filled it
   left no air: two dropdowns in consecutive rows shared an edge and read as one
   tall box. At 28 the row's own height becomes the separation, and the panel
   gets a little shorter for free. */
select.sel, input.txt {
  flex: 1; min-width: 0; height: var(--h-mini);
  background: var(--panel-2); border: 1px solid transparent; border-radius: var(--r-ctl);
  padding: 0 var(--sp-2); font-size: var(--fs-body);
}
/* The chevron is two triangles drawn in the padding the line above reserves,
   so its offsets track --sp-4 rather than being their own numbers. */
select.sel { appearance: none; cursor: pointer; padding-right: var(--sp-5);
  background-image: linear-gradient(45deg, transparent 50%, var(--dim) 50%), linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position: calc(100% - var(--sp-3)) 14px, calc(100% - var(--sp-2)) 14px;
  background-size: 4px 4px; background-repeat: no-repeat; }
select.sel:hover, input.txt:hover { border-color: var(--line-hover); }
select.sel:focus, input.txt:focus { outline: none; border-color: var(--accent); }
input.txt.mono { font: var(--fw-body) var(--fs-micro) var(--font); color: var(--dim); font-variant-numeric: tabular-nums; }
select.add-fx { width: 100%; color: var(--dim); }

/* Was 32×16 — the smallest control in the app, and the only one you could
   miss with a pointer. */
.tgl { width: 40px; height: 24px; border-radius: 12px; background: var(--panel-2); position: relative; transition: background .15s; }
.tgl.on { background: var(--accent); }
.tgl-dot { position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: var(--dim); transition: transform .15s, background .15s; }
.tgl.on .tgl-dot { transform: translateX(16px); background: #fff; }

.btn-row { display: flex; gap: var(--sp-2); }
.btn {
  flex: 1; height: var(--h-ctl); border-radius: var(--r-ctl);
  background: var(--panel-2); color: var(--dim);
  font-size: var(--fs-body); font-weight: var(--fw-value);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1);
  border: 1px solid transparent;
}
.btn:hover { background: var(--hover); color: var(--text); }
.btn.sm { flex: none; padding: 0 var(--sp-2); height: var(--h-mini); font-size: var(--fs-micro); }

/* Mute and Solo are two answers to one question, so they're one control with a
   seam down the middle rather than two buttons arguing for the same space. */
.seg-pair {
  display: flex; margin-top: var(--sp-3); height: var(--h-ctl);
  border: 1px solid var(--line); border-radius: var(--r-ctl); overflow: hidden;
}
.seg-btn {
  flex: 1; color: var(--dim);
  font-size: var(--fs-body); font-weight: var(--fw-value);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .12s, color .12s;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--line); }
.seg-btn:hover { background: var(--hover); color: var(--text); }
.seg-btn.on { background: var(--accent-soft); color: var(--accent); }
.seg-btn.on.solo { background: rgba(255,210,61,.15); color: var(--solo); }
.btn.on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-line); }
.btn.on.solo { background: rgba(255,210,61,.15); color: var(--solo); border-color: var(--solo-line); }
.btn.danger:hover { background: rgba(255,107,107,.14); color: var(--bad); }

.mini {
  width: var(--h-mini); height: var(--h-mini); flex: none; border-radius: var(--r-mini);
  font-size: var(--fs-badge); font-weight: 700; color: var(--dim-2);
  display: inline-flex; align-items: center; justify-content: center;
}
.mini:hover { background: var(--hover); color: var(--text); }
.mini.on { background: var(--accent-soft); color: var(--accent); }
.mini.on.solo { background: rgba(255,210,61,.16); color: var(--solo); }

.colors { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--sp-1); }
.swatch {
  height: 16px; border-radius: var(--r-mini); border: 1.5px solid transparent;
  transition: transform .08s;
}
.swatch:hover { transform: scale(1.14); }
/* A ring in the panel background, so the mark reads on any hue underneath. */
.swatch.on {
  border-color: var(--panel);
  box-shadow: 0 0 0 1.5px var(--text);
  transform: scale(1.06);
}
/* "Automatic" — no colour of its own, because that's the point: it hands the
   choice back to the clip style. A diagonal rule rather than an empty box, so
   it doesn't read as a swatch that failed to load. */
.swatch.auto {
  background:
    linear-gradient(to top right,
      transparent calc(50% - 1px), var(--faint) calc(50% - 1px),
      var(--faint) calc(50% + 1px), transparent calc(50% + 1px)),
    var(--panel-2);
  border-color: var(--line);
}
.swatch.auto.on { border-color: var(--panel); }

/* ---------- palette ---------- */

.palette { padding: 0 var(--sp-3) var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-1); }
.pal-item {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-2); border-radius: var(--r-ctl);
  border: 1px solid transparent; cursor: grab; user-select: none;
  transition: transform .08s;
}
.pal-item:hover { filter: brightness(1.25); }
.pal-item:active { cursor: grabbing; transform: scale(.985); }
.pal-swatch { width: 4px; height: 22px; border-radius: 2px; flex: none; }
.pal-name { font-size: var(--fs-body); font-weight: var(--fw-title); }
.pal-sub { font-size: var(--fs-micro); color: var(--dim-2); margin-top: 1px; }

/* ---------- layers ---------- */

.layers { padding: var(--sp-1) var(--sp-2) var(--sp-5); }
.layer {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--r-ctl); cursor: default;
  border: 1px solid transparent;
}
.layer:hover { background: var(--panel-2); }
.layer.sel { background: var(--accent-soft); border-color: var(--accent-line); }
.layer.muted { opacity: .45; }
.layer-dot { width: 4px; height: 20px; border-radius: 2px; flex: none; }
.layer-txt { flex: 1; min-width: 0; }
.layer-name { font-size: var(--fs-body); font-weight: var(--fw-value); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.layer-meta { font: var(--fw-body) var(--fs-badge) var(--font); color: var(--dim-2); margin-top: 1px; }

.empty {
  padding: var(--sp-3) var(--sp-2); font-size: var(--fs-body); color: var(--dim-2); line-height: 1.55;
}

/* ---------- room ---------- */

.room-link { display: flex; gap: var(--sp-1); align-items: center; }
.room-link input.txt { flex: 1; }
.room-status { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-micro); color: var(--dim); margin-top: var(--sp-2); }
.room-status i { width: 6px; height: 6px; border-radius: 50%; background: var(--faint); flex: none; }
.room-status.s-live i { background: var(--good); }
.room-status.s-connecting i, .room-status.s-reconnecting i { background: var(--warn); }

.people { margin-top: var(--sp-2); display: flex; flex-direction: column; gap: var(--sp-1); }
.person { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-body); }
.person .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.person em { color: var(--dim-2); font-style: normal; font-size: var(--fs-micro); }

/* ---------- fx ---------- */

/* The list stacks the cards; a card no longer pushes its neighbour away. */
.fx-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.fx-card {
  background: var(--panel-2); border-radius: var(--r-ctl);
  padding: var(--sp-2) var(--sp-2) var(--sp-1);
  border: 1px solid var(--line-soft);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.fx-card.dragging {
  border-color: var(--accent); box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
  opacity: .95; position: relative; z-index: 5;
}
.fx-grip { cursor: grab; color: var(--dim-2); touch-action: none; }
.fx-grip:hover { color: var(--text); }
.fx-card.dragging .fx-grip { cursor: grabbing; }
.fx-head { display: flex; align-items: center; gap: var(--sp-1); }
/* The name is the fold control, so it's a button that fills the bar — the
   whole title is the target, not the 14px chevron. */
.fx-name {
  flex: 1; font-size: var(--fs-body); font-weight: var(--fw-title);
  display: flex; align-items: center; gap: var(--sp-1);
  text-align: left; color: inherit; min-width: 0;
}
.fx-fold { display: inline-flex; color: var(--dim-2); transition: transform .14s; }
.fx-name:hover .fx-fold { color: var(--text); }
.fx-card.collapsed .fx-fold { transform: rotate(-90deg); }
/* Folded: the head is the whole card, so it loses the gap meant for a body —
   see the fold-animation block at the end of this file. */
/* 46px cut "Feedback" off mid-word; 56 is the grid step that holds it. */
.fx-card .row-l { width: 56px; font-size: var(--fs-micro); }
/* Inset and rounded, not a full-height fill. At `--h-ctl` tall in a row of the
   same height, consecutive tracks butted together and read as one continuous
   panel rather than three separate controls. */
.fx-card .sl-track::before { background: var(--sunken); height: 6px; border-radius: 3px; }
.eq-curve {
  display: block; width: 100%; height: 54px; margin: 0 0 var(--sp-2);
  border-radius: 4px; background: var(--sunken); border: 1px solid var(--line-soft);
}

/* The delay's band: a readout at each end, and one track lit between the two
   cuts. The lit stretch is the point — it's the part of the echo that
   survives, which two separate sliders can state but can't show. */
/* Bands sit in the card's gap now — see .fx-card. */
.band-heads { display: flex; justify-content: space-between; gap: var(--sp-2); }
.band-cap { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.band-cap.right { align-items: flex-end; }
.band-cap > span { font-size: var(--fs-micro); color: var(--dim); }
.band-cap.automated > span { color: var(--accent); }
.band-val {
  width: 64px; height: var(--h-mini); padding: 0 var(--sp-1);
  background: var(--sunken); border: 1px solid var(--line); border-radius: var(--r-mini);
  color: var(--text); font: var(--fw-value) var(--fs-micro) var(--font);
  font-variant-numeric: tabular-nums; text-align: center;
}
.band-cap.right .band-val { text-align: center; }
.band-val:hover { border-color: var(--line-hover); }
.band-val:focus { outline: none; border-color: var(--accent); background: var(--bg); }
.band-track {
  position: relative; height: var(--h-mini); margin-top: var(--sp-1);
  cursor: ew-resize; touch-action: none;
}
.band-track::before {
  content: ''; position: absolute; left: 0; right: 0; top: 50%; margin-top: -2px;
  height: 4px; border-radius: 2px; background: var(--sunken); border: 1px solid var(--line-soft);
}
.band-fill {
  position: absolute; top: 50%; margin-top: -2px; height: 4px;
  border-radius: 2px; background: var(--sl-fill); pointer-events: none;
}
.band-track:hover .band-fill { background: var(--sl-fill-2); }
.band-thumb {
  position: absolute; top: 50%; width: 8px; height: 16px; margin: -8px 0 0 -4px;
  border-radius: 2px; background: var(--text); border: 1px solid var(--sunken);
  pointer-events: none;
}

/* ---------- drum lanes ---------- */

.lane-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2); border-radius: 4px; cursor: pointer;
  font-size: var(--fs-body);
}
.lane-head:hover { background: var(--panel-2); }
.lane-head.open { background: var(--panel-2); }
.lane-name { flex: 1; }
.chev { color: var(--dim-2); font-size: var(--fs-body); width: 10px; text-align: center; }
.lane-body { padding: var(--sp-2) var(--sp-2) var(--sp-1) var(--sp-2); margin-bottom: var(--sp-1); border-left: 2px solid var(--line); margin-left: var(--sp-2); }
.lane-body .row-l { width: 44px; font-size: var(--fs-micro); }

.range-row { align-items: center; gap: var(--sp-2); }
.range-txt { flex: 1; text-align: center; font: var(--fw-value) var(--fs-micro) var(--font); color: var(--dim); }

/* ---------- shortcuts ---------- */

.keys { display: flex; flex-direction: column; gap: var(--sp-1); }
.key-row { display: flex; align-items: baseline; gap: var(--sp-2); font-size: var(--fs-micro); color: var(--dim-2); }
.key-row kbd {
  font: var(--fw-value) var(--fs-badge) var(--font); color: var(--dim);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--r-mini); padding: var(--sp-1) var(--sp-1); flex: none; min-width: 62px; text-align: center;
}

/* ---------- toast ---------- */

#toast {
  position: fixed; bottom: 22px; left: 50%; transform: translate(-50%, 14px);
  background: var(--card); border: 1px solid var(--scroll);
  color: var(--text); padding: var(--sp-2) var(--sp-4); border-radius: 8px;
  font-size: var(--fs-body); font-weight: var(--fw-value); pointer-events: none;
  opacity: 0; transition: opacity .2s, transform .2s; z-index: 60;
  box-shadow: 0 10px 30px var(--scrim);
}
#toast.show { opacity: 1; transform: translate(-50%, 0); }

/* The way out of a drifted browser zoom. Deliberately not a toast: a toast
   fades, and this has to stay until the thing it describes is fixed. Bottom
   centre so it can't cover the top bar's own zoom controls. */
.zoom-bar {
  position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  justify-content: center;
  background: var(--card); border: 1px solid var(--warn);
  color: var(--text); padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
  border-radius: 10px; font-size: var(--fs-body); z-index: 70;
  box-shadow: 0 10px 30px var(--scrim);
  max-width: min(560px, calc(100vw - var(--sp-6)));
}
/* The message keeps its own line rather than being squeezed under the button —
   `.btn` is flex:1 by default, which had it growing over the text. */
.zoom-bar > span { flex: 1 1 auto; min-width: 0; }
.zoom-bar .btn { flex: 0 0 auto; padding: 0 var(--sp-3); }

/* ---------- left panel: sounds ---------- */

.add-sound {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  margin: var(--sp-1) var(--sp-3) var(--sp-2); height: 30px; width: calc(100% - 20px);
  border-radius: 6px; background: var(--cta); color: var(--cta-ink);
  font-size: var(--fs-body); font-weight: var(--fw-title);
}
.add-sound:hover { background: var(--cta-hover); }
.add-sound svg { opacity: .9; }

.sounds { padding: var(--sp-1) var(--sp-2) var(--sp-2); position: relative; }

/* Drag-to-reorder: the row you're carrying fades, and a line shows the slot. */
.sounds.reordering { cursor: grabbing; }
.sounds.reordering .sound { transition: none; }
.sound.drag-src { opacity: .4; }
.drop-line {
  position: absolute; left: 8px; right: 8px; height: 2px;
  margin-top: -1px; border-radius: 2px;
  background: var(--accent); pointer-events: none;
}

/* A fixed four-column grid, so every row is the same height whatever its name
   and meta are. As a flex row with a wrapping meta line, rows came out anywhere
   between 46px and 76px tall, which read as a layout bug. */
.sound {
  display: grid; grid-template-columns: 9px 4px minmax(0, 1fr) auto;
  align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-2); border-radius: 6px; cursor: grab;
  box-shadow: inset 0 0 0 1px transparent; user-select: none;
  transition: background .1s, box-shadow .1s;
}
.sound:hover { background: var(--panel-2); }
.sound:active { cursor: grabbing; }
/* Selection is drawn in the sound's *own* colour — it matches the coloured
   spotlight this same click puts on that sound's clips out on the canvas. Blue
   is reserved for clip selection, which is what the canvas rings in blue. */
.sound.sel {
  background: color-mix(in srgb, var(--c) 13%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 36%, transparent);
}
.sound.sel .sound-name { color: var(--text); }
.sound.muted { opacity: .5; }
/* Soloing one sound silences the rest, so the rest have to LOOK silenced —
   faded and drained of their colour, which is the only label a sound has. */
.sound.silenced { opacity: .34; }
.sound.silenced .sound-dot { filter: grayscale(1); box-shadow: none; }
.sound.silenced .sound-name { color: var(--dim); font-weight: var(--fw-value); }
/* And the one you *are* hearing is marked, so the state reads in both directions. */
.sound.soloed { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--solo) 55%, transparent); }
.solo-chip {
  font-size: var(--fs-badge); font-weight: var(--fw-title); letter-spacing: .04em; text-transform: uppercase;
  padding: var(--sp-1) var(--sp-2); border-radius: 999px; cursor: pointer;
  /* Near-black in both themes. The light gold (#a97c05) looks dark enough to
     want white, but it measures 3.77:1 against white and 4.85:1 against this
     ink — the eye reads the hue as dark and the maths disagrees. */
  color: #1b1403; background: var(--solo); border: 0;
}
.solo-chip:hover { filter: brightness(1.08); }
.sound-chev {
  text-align: center; font-size: var(--fs-badge); line-height: 1;
  color: var(--dim-2); transition: transform .12s; transform: rotate(0deg);
}
.sound-chev.open { transform: rotate(90deg); color: var(--dim); }
.sound-dot { height: 26px; border-radius: 2px; box-shadow: 0 0 9px -3px var(--c); }
.sound.sel .sound-dot { box-shadow: 0 0 12px -2px var(--c); }
.sound-txt { min-width: 0; }
.sound-name,
.sound-meta { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sound-name { font-size: var(--fs-body); font-weight: var(--fw-title); }
.sound-meta { font-size: var(--fs-micro); color: var(--dim); margin-top: 1px; }
/*
 * Mute and solo hold a real column — no overlay, no fade over the name.
 *
 * They were 17px wide, the smallest targets in the app by a wide margin.
 * Widening them to --h-mini is only affordable because Duplicate and Remove
 * left the row (see js/ui/left.js): two buttons cost 58px where four cost 115,
 * and the name keeps the rest. Floating them over the text was tried and was
 * worse than the problem — a name half-under a gradient with a letter sitting
 * on top of it.
 */
.sound-acts { display: flex; gap: 1px; }
/* Quiet until you reach for them, but the column is always reserved — fading
   them in must not move the name that sits beside them. A latched mute or solo
   stays lit with the pointer anywhere, because that state has to be findable
   without hunting for it. */
.sound .mini { opacity: 0; }
.sound:hover .mini, .sound.sel .mini, .sound .mini.on, .sound:focus-within .mini { opacity: 1; }
.mini.kill:hover { background: rgba(255,107,107,.16); color: var(--bad); }

/* clips revealed under the selected sound — indented to the text column above */
.clip-list { margin: -1px 0 var(--sp-2) var(--sp-5); padding-left: var(--sp-2); border-left: 1px solid var(--line); }
.clip-row {
  display: flex; align-items: center; gap: var(--sp-2); cursor: pointer;
  padding: var(--sp-1) var(--sp-2); border-radius: var(--r-ctl); border: 1px solid transparent;
}
.clip-row:hover { background: var(--panel-2); }
.clip-row.sel { background: var(--accent-soft); border-color: var(--accent-line); }
.clip-tick { width: 4px; height: 14px; border-radius: 2px; background: var(--c); flex: none; opacity: .8; }
.clip-label { flex: 1; min-width: 0; font-size: var(--fs-body); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.clip-pos { font: var(--fw-body) var(--fs-badge) var(--font); color: var(--dim-2); flex: none; }

/* A curve on this sound, listed with its clips. Dimmer than a clip row and
   led by the curve glyph rather than a colour tick: it belongs to the sound,
   not to a place on the canvas, and shouldn't read as another clip. */
.auto-row .clip-label { color: var(--dim); font-style: italic; }
.auto-tick { display: flex; color: var(--c); opacity: .85; flex: none; }
.auto-row.sel .clip-label { color: var(--accent); font-style: normal; }

/* A part played more than once folds into one row: a chevron to open it, a
   ×count, and the span of bars it covers. Its placements nest underneath.
   On a narrow panel the bar range gives up its pixels before the name does —
   a clipped range still reads, a name clipped to "Ki…" does not. */
.clip-row.group { gap: var(--sp-2); }
.clip-row.group .clip-label { font-weight: var(--fw-title); min-width: 3.4em; }
.clip-row.group .clip-pos {
  flex: 0 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.clip-chev {
  flex: none; width: 12px; font-size: 10px; color: var(--faint);
  transition: transform .12s ease;
}
.clip-chev:hover { color: var(--text); }
.clip-chev.open { transform: rotate(90deg); }
.clip-count {
  flex: none; font: var(--fw-value) var(--fs-badge) var(--font); color: var(--dim-2);
  background: var(--panel-2); border-radius: 4px; padding: 1px var(--sp-1);
}
.clip-row.group.open .clip-count { color: var(--text); }
.clip-row.nested { margin-left: var(--sp-4); }
.clip-row.nested .clip-label { color: var(--dim); }
.clip-add {
  display: flex; align-items: center; gap: var(--sp-1); width: 100%;
  padding: var(--sp-1) var(--sp-2); margin-top: var(--sp-1); border-radius: var(--r-ctl);
  font-size: var(--fs-micro); font-weight: var(--fw-value); color: var(--dim-2);
}
.clip-add:hover { background: var(--panel-2); color: var(--text); }
.clip-add svg { opacity: .7; }

.panel-foot {
  padding: var(--sp-3) var(--sp-3); margin-top: var(--sp-1); font-size: var(--fs-micro); line-height: 1.5;
  color: var(--dim-2); border-top: 1px solid var(--line-soft);
}

/* ---------- playable keyboard bar ---------- */

/* A centered floating card no wider than the keys — not a full-width strip. */
.kbar {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 12px;
  width: max-content; max-width: calc(100% - 24px);
  /* Sized to what's in it: the left column (sound, octave, quantize, takes)
     and the keys-plus-strip beside it both land at 144px of content — a
     grid number, reached by adding up grid numbers. It was 140 with fewer
     rows, and the overflow was silently clipped: the arp row used to lose its
     bottom edge. */
  height: 176px; z-index: 15;
  display: flex; align-items: stretch; gap: var(--sp-3);
  background: var(--float); border: 1px solid var(--line);
  border-radius: 12px;
  padding: var(--sp-3) var(--sp-4) var(--sp-4); backdrop-filter: blur(10px);
  box-shadow: 0 10px 34px -14px var(--scrim);
}
.kbar-ctrl { display: flex; flex-direction: column; gap: var(--sp-3); width: 168px; flex: none; }
.kbar-top { display: flex; align-items: flex-start; gap: var(--sp-2); min-width: 0; }
.kbar-inst-txt { min-width: 0; flex: 1; }
.kbar-dot { width: 4px; height: 32px; border-radius: var(--r-mini); flex: none; }
.kbar-name { font-size: var(--fs-title); font-weight: var(--fw-title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kbar-sub { font-size: var(--fs-micro); color: var(--dim-2); margin-top: var(--sp-1); }
.kbar-sub.rec { color: var(--bad); font-weight: var(--fw-title); }
.kbar-oct { display: flex; align-items: center; gap: var(--sp-2); }
.kbar-hint { font: var(--fw-value) var(--fs-badge) var(--font); color: var(--dim-2); letter-spacing: .04em; }
.kbar-q { display: flex; align-items: center; gap: var(--sp-2); }
.kbar-q > span { font-size: var(--fs-micro); color: var(--dim-2); flex: none; }
.kbar-q select.sel { height: var(--h-mini); font-size: var(--fs-micro); }
.kbar-q .pref-seg { height: var(--h-mini); }
.kbar-q .pref-seg .seg-btn { padding: 0 var(--sp-2); }
/* Velocity is invisible until it's named. Small, dim, and beside the keys it
   describes — a caption, not an instruction. */
.kbar-velhint {
  font-size: var(--fs-badge); color: var(--dim-2); letter-spacing: .02em;
  margin-left: auto; white-space: nowrap; padding-left: var(--sp-3);
  /* On a narrow window it trails off with an ellipsis rather than being
     chopped mid-glyph by the card edge — truncation reads as deliberate. */
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.oct-btn { width: 24px; height: 24px; border-radius: var(--r-ctl); background: var(--panel-2); color: var(--dim); font-size: 14px; font-weight: var(--fw-title); display: inline-flex; align-items: center; justify-content: center; }
.oct-btn:hover { background: var(--hover); color: var(--text); }
.oct-lbl { font: var(--fw-value) var(--fs-body) var(--font); color: var(--text); min-width: 34px; text-align: center; }
.kbar-close { flex: none; width: 22px; height: 20px; border-radius: var(--r-ctl); color: var(--dim-2); font-size: 14px; line-height: 1; }
.kbar-close:hover { background: var(--panel-2); color: var(--text); }

/* Sized to the keys themselves; scrolls only when the pane is narrower. */
.kbar-right { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.kbar-under { display: flex; align-items: center; gap: var(--sp-3); min-height: var(--h-mini); overflow: hidden; }
/* max-width matters: inside a column the keybed's `width: max-content` is a
   cross-axis size and won't shrink on its own, so a narrow window would push
   the keys straight out through the side of the card instead of scrolling. */
.kbed-wrap { flex: 0 1 auto; width: max-content; max-width: 100%; overflow-x: auto; overflow-y: hidden; border-radius: 8px; }
.kbed {
  --wk: 32px;
  position: relative; height: 112px; min-width: max-content; display: flex;
  padding-bottom: var(--sp-1);
}
.wk {
  width: var(--wk); flex: none; height: 100%;
  background: linear-gradient(#eef0f4, #d5d9e1); border: 1px solid var(--key-line); border-top: none;
  border-radius: 0 0 5px 5px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: var(--sp-1); padding-bottom: var(--sp-2);
  transition: background .04s;
}
.wk.scale { background: linear-gradient(#ffffff, #e9edf4); }
.wk.off { background: linear-gradient(#d3d6dd, #bcc0c9); }
.wk.root .k-cap { color: var(--accent); font-weight: 700; }
.wk.on { background: linear-gradient(#bcd4ff, #8fb6f2) !important; }

/* The note name (or drum) a key plays, above the letter that plays it. */
.k-cap { font: var(--fw-value) var(--fs-badge) var(--font); color: #7a8090; pointer-events: none; }
/* The computer key. Boxed, because it's the label that does the work: without
   it musical typing is an undocumented layout you have to be told about. */
.k-key {
  font: 700 var(--fs-badge) var(--font); color: #5c6474; pointer-events: none;
  border: 1px solid #b7bdc9; border-radius: var(--r-mini); background: rgba(255,255,255,.55);
  min-width: 14px; text-align: center; padding: 1px 0 var(--sp-1);
}
.wk.off .k-key { opacity: .55; }
.bk .k-key {
  color: #cfd5e2; border-color: #4a4f5e; background: rgba(255,255,255,.08);
  font-size: var(--fs-badge); min-width: 12px;
}

.kbed-black { position: absolute; top: 0; left: 0; height: 62%; pointer-events: none; }
.bk {
  position: absolute; top: 0; width: calc(var(--wk) * 0.62); height: 100%;
  background: linear-gradient(#2b2e39, #14161d); border: 1px solid #000;
  border-radius: 0 0 4px 4px; cursor: pointer; pointer-events: auto; z-index: 3;
  display: flex; align-items: flex-end; justify-content: center; padding-bottom: var(--sp-1);
}
.bk.scale { background: linear-gradient(#3a3e4b, #1c1f28); }
.bk.off { background: linear-gradient(#23252e, #101219); opacity: .82; }
.bk.on { background: linear-gradient(#5a86d8, #3a5da0) !important; }

/* ---------- inspector: clip / instrument ---------- */

.inst-chip {
  display: flex; align-items: center; gap: var(--sp-2); cursor: pointer;
  margin: 0 var(--sp-3) var(--sp-3); padding: var(--sp-2) var(--sp-2); border-radius: 6px;
  background: var(--panel-2); border: 1px solid var(--line-soft);
}
.inst-chip:hover { border-color: var(--c); }
.inst-chip-dot { width: 4px; height: 26px; border-radius: 2px; flex: none; }
.inst-chip-txt { flex: 1; min-width: 0; }
.inst-chip-name { font-size: var(--fs-body); font-weight: var(--fw-title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inst-chip-sub {
  font-size: var(--fs-micro); color: var(--dim-2); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.inst-chip-open {
  flex: none; width: 14px; text-align: center;
  font-size: 15px; line-height: 1; color: var(--dim-2);
}
.inst-chip:hover .inst-chip-open { color: var(--c); }

.open-inst {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  width: 100%; height: 30px; border-radius: 6px; margin-bottom: var(--sp-2);
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text); font-size: var(--fs-body); font-weight: var(--fw-title);
}
.open-inst:hover { background: var(--hover); border-color: var(--line-hover); }
.open-inst.big { height: 34px; background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-ink); }
.open-inst.big:hover { background: var(--accent-soft-2); }

.inst-summary { font-size: var(--fs-body); color: var(--dim); display: flex; align-items: center; gap: var(--sp-2); }
.inst-summary-clips { margin-left: auto; color: var(--dim-2); font-size: var(--fs-micro); }
.btn.wide { width: 100%; }

/* ---------- instrument window (modal) ---------- */

.modal-root {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: var(--scrim); backdrop-filter: blur(3px);
  padding: var(--sp-5);
}
.modal-card {
  width: min(1040px, 96vw); height: min(660px, 92vh);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; overflow: hidden;
}
.modal-card.browse { height: min(560px, 88vh); }

.modal-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line); flex: none;
}
.modal-dot { width: 9px; height: 22px; border-radius: var(--r-mini); flex: none; }
.modal-title { font-size: 14px; font-weight: var(--fw-title); }
.modal-name {
  background: none; border: 1px solid transparent; border-radius: var(--r-ctl);
  padding: var(--sp-1) var(--sp-2); font-size: 14px; font-weight: var(--fw-title); max-width: 240px;
}
.modal-name:hover { border-color: var(--line); }
.modal-name:focus { outline: none; border-color: var(--accent); background: var(--bg); }
.modal-sub { font-size: var(--fs-body); color: var(--dim-2); }
.modal-head-sp { flex: 1; }
.modal-x {
  width: var(--h-ctl); height: var(--h-ctl); border-radius: var(--r-ctl); color: var(--dim);
  display: inline-flex; align-items: center; justify-content: center;
}
.modal-x:hover { background: var(--panel-2); color: var(--text); }

.seg-group { display: flex; background: var(--panel-2); border-radius: 6px; padding: var(--sp-1); gap: var(--sp-1); }
.seg {
  height: var(--h-ctl); padding: 0 var(--sp-3); border-radius: var(--r-ctl); font-size: var(--fs-body); font-weight: var(--fw-title);
  color: var(--dim); display: inline-flex; align-items: center;
}
.seg:hover { color: var(--text); }
.seg.on { background: var(--accent); color: #fff; }
.seg.on.solo { background: var(--solo); color: #211c00; }

.modal-body { flex: 1; display: flex; min-height: 0; }
.browse-body, .edit-body { gap: 0; }

/* category rail */
.cat-rail {
  width: 150px; flex: none; border-right: 1px solid var(--line);
  padding: var(--sp-3) var(--sp-2); overflow-y: auto; display: flex; flex-direction: column; gap: var(--sp-1);
}
.cat-item {
  display: flex; align-items: center; gap: var(--sp-2); text-align: left;
  padding: var(--sp-2) var(--sp-2); border-radius: var(--r-ctl); font-size: var(--fs-body); color: var(--dim);
}
.cat-item:hover { background: var(--panel-2); color: var(--text); }
.cat-item.on { background: var(--panel-2); color: var(--text); }
.cat-dot { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.cat-count { margin-left: auto; font-size: var(--fs-micro); color: var(--dim-2); font-variant-numeric: tabular-nums; }

/* preset grid */
.preset-grid {
  flex: 1; min-width: 0; overflow-y: auto; padding: var(--sp-3);
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  grid-auto-rows: min-content; gap: var(--sp-2); align-content: start;
}
/* The name owns its own line and the full width; the category sits under it.
   Sharing a row with the tag and the preview button left the name ~65px, which
   truncated "Glockenspiel" on a card with room to spare. */
.preset-card {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-1);
  text-align: left;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-ctl); background: var(--panel-2);
  border: 1px solid var(--line-soft); color: var(--text);
}
.preset-row { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.preset-card:hover { border-color: var(--c); background: var(--card); }
.preset-card.on { border-color: var(--c); box-shadow: inset 0 0 0 1px var(--c); background: var(--card); }
.preset-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.preset-name { flex: 1; min-width: 0; font-size: var(--fs-body); font-weight: var(--fw-value); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.preset-cat {
  font-size: var(--fs-badge); color: var(--dim-2); text-transform: uppercase; letter-spacing: .4px;
  padding-left: var(--sp-4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.preset-card:not(:has(.preset-dot)) .preset-cat { padding-left: 0; }

/* Search across every library, from the browser header. */
.browse-search {
  display: flex; align-items: center; gap: var(--sp-2); flex: 1; min-width: 90px; max-width: 260px;
  height: 28px; padding: 0 var(--sp-2); border-radius: var(--r-ctl);
  background: var(--panel-2); border: 1px solid var(--line-soft); color: var(--dim);
}
.browse-search:focus-within { border-color: var(--accent); color: var(--text); }
.browse-search input {
  flex: 1; min-width: 0; background: none; border: none; outline: none;
  font-size: var(--fs-body); color: var(--text);
}
.browse-search input::placeholder { color: var(--dim-2); }
.browse-search input::-webkit-search-cancel-button { -webkit-appearance: none; }
.browse-none {
  padding: var(--sp-7) var(--sp-5); text-align: center; display: flex; flex-direction: column; gap: var(--sp-2);
}
.browse-none b { font-size: var(--fs-title); font-weight: var(--fw-title); }
.browse-none span { font-size: var(--fs-body); color: var(--dim-2); }

/* edit right column */
/* A column with one rhythm; the headers add their own step on top of it.
   As a sibling rule that step needs no `:first-child` exception to cancel it
   at the top — same fix as `.sect + .sect`. */
.edit-right {
  width: 268px; flex: none; border-left: 1px solid var(--line);
  padding: var(--sp-3); overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.edit-sect-h {
  display: flex; align-items: center; font-size: var(--fs-label); font-weight: var(--fw-label);
  letter-spacing: .7px; text-transform: uppercase; color: var(--dim);
}
/* One step of extra air above a header, only when something precedes it. */
.edit-right * + .edit-sect-h { margin-top: var(--sp-2); }

.macros { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-3) var(--sp-2); }
.mk { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.mk-label { font-size: var(--fs-badge); color: var(--dim); text-align: center; }
.mk-track {
  width: 30px; height: 62px; border-radius: 6px; background: var(--panel-2);
  position: relative; cursor: ns-resize; overflow: hidden; border: 1px solid var(--line-soft);
}
.mk-track:hover { border-color: var(--line-hover); }
.mk-fill { position: absolute; left: 0; right: 0; bottom: 0; background: linear-gradient(var(--accent), var(--sl-fill-2)); }
.mk-val { font: var(--fw-value) var(--fs-badge) var(--font); color: var(--text); }

.range-inline { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-4); }
.range-lbl { font-size: var(--fs-micro); color: var(--dim); }
.range-txt { flex: 1; text-align: center; font: var(--fw-value) var(--fs-micro) var(--font); color: var(--dim); }

.mini-rows .row-l { width: 52px; }
.fx-list select.add-fx { width: 100%; }

/* keyboard */
.keyboard { flex: none; border-top: 1px solid var(--line); padding: var(--sp-3) var(--sp-4) var(--sp-4); background: var(--panel-2); }
.kb-hint { font-size: var(--fs-micro); color: var(--dim-2); letter-spacing: .3px; }
.keys-wrap { --wk: 34px; overflow-x: auto; }
.keybed { position: relative; height: 96px; display: flex; width: max-content; }
.wkey {
  width: var(--wk); height: 96px; flex: none;
  background: linear-gradient(#eef0f4, #d7dae2); border: 1px solid var(--key-line); border-top: none;
  border-radius: 0 0 4px 4px; display: flex; align-items: flex-end; justify-content: center;
  padding-bottom: var(--sp-1); cursor: pointer;
}
.wkey:active { background: linear-gradient(var(--accent-ink), #a9c4f0); }
.wkey span { font: var(--fw-value) var(--fs-badge) var(--font); color: #6a7180; }
.bkeys { position: absolute; top: 0; left: 0; height: 60px; pointer-events: none; }
.bkey {
  position: absolute; top: 0; width: calc(var(--wk) * 0.6); height: 60px;
  background: linear-gradient(#2a2d38, #16181f); border-radius: 0 0 3px 3px;
  border: 1px solid #000; cursor: pointer; pointer-events: auto; z-index: 2;
}
.bkey:active { background: linear-gradient(#3c5178, #22314d); }

/* drum machine */
.drum-body { }
.drum-left { width: 190px; flex: none; border-right: 1px solid var(--line); padding: var(--sp-3); overflow-y: auto; }
.drum-mid { flex: 1; min-width: 0; padding: var(--sp-4); }
.kit-rail { display: flex; flex-direction: column; gap: var(--sp-2); }
.kit-grid { flex: 1; padding: var(--sp-4); display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--sp-3); align-content: start; overflow-y: auto; }
.kit-card {
  text-align: left; padding: var(--sp-3); border-radius: 8px; background: var(--panel-2);
  border: 1px solid var(--line-soft); color: var(--text);
  display: flex; flex-direction: column; gap: var(--sp-1);
}
.kit-card:hover { border-color: var(--accent); background: var(--card); }
.kit-card.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.kit-card.sm { padding: var(--sp-2) var(--sp-3); }
.kit-name { font-size: var(--fs-title); font-weight: var(--fw-title); }
.kit-desc { font-size: var(--fs-micro); color: var(--dim-2); }
.kit-pads { display: flex; gap: var(--sp-1); margin-top: var(--sp-1); }
.kit-pads i { width: 12px; height: 12px; border-radius: 2px; background: color-mix(in srgb, var(--accent) 22%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); }

.pad-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-2); }
.pad {
  display: flex; flex-direction: column;
  aspect-ratio: 1.25; min-height: 58px; border-radius: 8px; background: var(--panel-2);
  border: 1px solid var(--line); color: var(--dim); overflow: hidden;
}
.pad:hover { background: var(--hover); color: var(--text); border-color: var(--line-hover); }
/* A pad lights as its lane fires. `--flash` is driven per frame from the
   transport position, so the glow lands with the sound rather than with the
   scheduler that queued it 120ms earlier. It all lives on an overlay layer so
   it can't fight the selected pad's own ring. */
.pad { --flash: 0; position: relative; }
.pad::before {
  content: ''; position: absolute; inset: 0; z-index: 2;
  pointer-events: none; border-radius: inherit;
  background: var(--accent);
  opacity: calc(var(--flash) * 0.32);
  box-shadow:
    inset 0 0 0 2px color-mix(in srgb, var(--accent) calc(var(--flash) * 100%), transparent),
    0 0 calc(var(--flash) * 16px) color-mix(in srgb, var(--accent) calc(var(--flash) * 55%), transparent);
}
.pad.on { border-color: var(--accent); color: var(--text); box-shadow: inset 0 0 0 1px var(--accent); }
/* A muted lane is dimmed rather than removed: the pad is still there to unmute. */
.pad.off { border-color: var(--line-soft); }
.pad.off .pad-hit { opacity: .34; }
.pad-hit {
  flex: 1; min-height: 0; text-align: left;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-end;
  gap: 1px; padding: 0 var(--sp-2) var(--sp-2);
  font-size: var(--fs-body); font-weight: var(--fw-title); color: inherit; line-height: 1.2;
}
.pad-hit:active { background: var(--accent-soft); }
.pad-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.pad-src {
  font-size: var(--fs-badge); font-weight: var(--fw-value); color: var(--accent); opacity: .9;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.pad.custom { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.pad.drop { border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--accent); background: var(--accent-soft); }
.pad-ms { display: flex; justify-content: flex-end; gap: var(--sp-1); padding: var(--sp-1) var(--sp-1) 0; flex: none; }
.pad-ms .mini { opacity: 0; }
.pad:hover .pad-ms .mini, .pad-ms .mini.on { opacity: 1; }

/* Selected-pad sound header: name + Replace / Reset. */
.pad-sound { display: flex; align-items: center; gap: var(--sp-2); }
.pad-sound-name {
  flex: 1; min-width: 0; font-size: var(--fs-body); color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mini2 {
  flex: none; padding: var(--sp-1) var(--sp-2); border-radius: var(--r-ctl); font-size: var(--fs-micro); font-weight: var(--fw-title);
  background: var(--panel-2); border: 1px solid var(--line-soft); color: var(--dim);
}
.mini2:hover { background: var(--hover); color: var(--text); border-color: var(--line-hover); }
.kit-credit { margin-top: auto; padding-top: var(--sp-2); font-size: var(--fs-badge); color: var(--dim-2); line-height: 1.4; }

/* Per-drum mute / solo in the inspector — the mixer a kit actually wants. */
.lane-grid { display: flex; flex-direction: column; gap: var(--sp-1); margin: var(--sp-1) 0 var(--sp-3); }
.lane-cell {
  display: flex; align-items: center; gap: var(--sp-1);
  border-radius: 4px; padding: 1px var(--sp-1) 1px 0;
}
.lane-cell:hover { background: var(--panel-2); }
.lane-cell.off .lane-name { opacity: .42; text-decoration: line-through; }
.lane-cell .lane-name {
  flex: 1; min-width: 0; text-align: left; padding: var(--sp-1) var(--sp-1);
  font-size: var(--fs-body); color: var(--dim); border-radius: var(--r-mini);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lane-cell:hover .lane-name { color: var(--text); }
.lane-cell .mini { opacity: .55; }
.lane-cell:hover .mini, .lane-cell .mini.on { opacity: 1; }

/* ---------- synth device (the instrument window) ---------- */

.sdevice { flex: 1; display: flex; gap: var(--sp-5); padding: var(--sp-5) var(--sp-6); align-items: center; }
.sxy-col { flex: none; }

.xy-pad {
  position: relative; width: 260px; height: 260px; border-radius: 14px;
  background: var(--panel-2); border: 1px solid var(--line);
  overflow: hidden; cursor: crosshair; touch-action: none;
}
.xy-glow {
  position: absolute; width: 260px; height: 260px; margin: -130px 0 0 -130px;
  border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, color-mix(in srgb, var(--gc, #8f6fce) 42%, transparent), transparent 62%);
}
.xy-puck {
  position: absolute; width: 18px; height: 18px; margin: -9px 0 0 -9px; border-radius: 50%;
  box-shadow: 0 0 0 5px color-mix(in srgb, currentColor 0%, rgba(255,255,255,.12)), 0 2px 8px rgba(0,0,0,.5);
  pointer-events: none;
}
.xy-x, .xy-y {
  position: absolute; font: var(--fw-title) var(--fs-micro)/1 var(--font); letter-spacing: .12em;
  text-transform: uppercase; color: var(--dim-2); pointer-events: none;
}
.xy-x { bottom: 10px; left: 50%; transform: translateX(-50%); }
.xy-y { top: 50%; left: 10px; transform: translateY(-50%) rotate(-90deg); transform-origin: left center; white-space: nowrap; }

.sknobs {
  flex: 1; display: grid; grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: min-content; gap: var(--sp-5) var(--sp-3); align-content: center;
}
.sknob { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); user-select: none; }
.knob-dial { width: 60px; height: 60px; cursor: ns-resize; touch-action: none; }
.knob-svg { width: 60px; height: 60px; display: block; }
.knob-bg { fill: none; stroke: var(--panel-2); stroke-width: 5; }
.knob-fg { fill: none; stroke-width: 5; stroke-linecap: round; transition: stroke-dasharray .02s linear; }
.knob-label { font-size: var(--fs-title); font-weight: var(--fw-title); color: var(--text); }
.knob-cap { font: var(--fw-value) var(--fs-micro) var(--font); color: var(--dim-2); margin-top: -3px; }

/* footer */
.sfoot {
  flex: none; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--line); background: var(--surface, var(--panel));
}
.pill2 {
  display: inline-flex; align-items: center; gap: var(--sp-2); white-space: nowrap;
  height: 30px; padding: 0 var(--sp-4); border-radius: 20px; font-size: var(--fs-body); font-weight: var(--fw-title);
  border: 1px solid var(--line-strong, var(--line)); color: var(--dim); background: transparent;
}
.pill2:hover { color: var(--text); border-color: #3a4150; }
.pill2.on { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); background: var(--accent-soft); }
.pill2.surprise { background: var(--accent); color: #fff; border-color: transparent; }
.pill2.surprise:hover { background: var(--accent-hover); }
.chevr { font-size: var(--fs-badge); opacity: .7; }

.dkeys { margin-left: auto; display: flex; position: relative; height: 34px; }
.dk {
  width: 15px; height: 34px; background: linear-gradient(#eef0f4, #d5d9e1);
  border: 1px solid var(--key-line); border-top: none; border-radius: 0 0 3px 3px; cursor: pointer;
}
.dk.b { width: 10px; height: 21px; margin: 0 -5px; z-index: 2; background: linear-gradient(#2b2e39, #14161d); border: 1px solid #000; }
.dk:active { background: linear-gradient(#bcd4ff, #8fb6f2); }
.dk.b:active { background: linear-gradient(#5a86d8, #3a5da0); }

/* ---------- sampled instrument ---------- */

.sampler-body { flex: 1; display: flex; min-height: 0; }
.sampler-main {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  gap: var(--sp-4); padding: var(--sp-4) var(--sp-5); overflow-y: auto;
}
/* Centred between the load line and the credit, matching the synth device page. */
.sampler-mid {
  flex: 1; display: flex; flex-direction: column;
  justify-content: center; gap: var(--sp-5); min-height: 0;
}
/* Six macros rather than the synth's eight, so they get more room each. */
.sknobs.wide { flex: none; grid-template-columns: repeat(3, 1fr); gap: var(--sp-5) var(--sp-3); }
.sampler-row { display: flex; align-items: center; gap: var(--sp-4); }
.sampler-row .row { flex: 1; margin: 0; }

/* Attribution — CC BY 3.0 requires it, so it is part of the UI, not a comment. */
.sampler-credit {
  margin-top: auto; padding-top: var(--sp-3); border-top: 1px solid var(--line-soft);
  font-size: var(--fs-micro); color: var(--dim-2); line-height: 1.5;
}
.sampler-credit a { color: var(--dim); text-decoration: underline; text-underline-offset: 2px; }
.sampler-credit a:hover { color: var(--accent); }

/* Download state. Collapses to one quiet line once everything has decoded. */
.load-row {
  display: flex; align-items: center; gap: var(--sp-2); flex: none;
  font-size: var(--fs-body); color: var(--dim); min-height: 18px;
}
.load-row.done { color: var(--dim-2); }
.load-row.done svg { color: var(--good); }
.load-row.bad { color: var(--bad); }
.load-bar {
  flex: 1; max-width: 220px; height: 4px; border-radius: 2px;
  background: var(--panel-2); overflow: hidden;
}
.load-bar i { display: block; height: 100%; background: var(--accent); transition: width .2s linear; }

.kit-card.sampled:hover { border-color: var(--c); }
.kit-card.sampled .kit-name { display: flex; align-items: center; gap: var(--sp-2); }
.kit-foot {
  display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); margin-top: var(--sp-3);
  font: var(--fw-value) var(--fs-badge) var(--font); color: var(--dim-2);
}

/* advanced view */
.adv-body { flex: 1; overflow-y: auto; padding: var(--sp-4) var(--sp-5); }
.adv-cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2) var(--sp-6); }
.adv-body .edit-sect-h:first-child, .adv-body .adv-cols .edit-sect-h:first-child { margin-top: var(--sp-1); }

@media (max-width: 900px) {
  :root { --panel-w: 228px; }
  .tb-left .proj-name { max-width: 120px; }
  .hint { display: none; }
  .cat-rail { width: 116px; }
  .edit-right { width: 220px; }
  .sdevice { flex-direction: column; gap: var(--sp-4); padding: var(--sp-4); }
  .adv-cols { grid-template-columns: 1fr; }
  /* Four columns of pads in a narrow window makes each one too small to label. */
  .pad-grid { grid-template-columns: repeat(2, 1fr); }
  .sknobs.wide { grid-template-columns: repeat(2, 1fr); }
}

/* ---------- theme toggle ---------- */

/* Intentionally low-key: a small unlabelled glyph, dim until you go looking. */
.theme-btn {
  height: var(--h-ctl); width: var(--h-ctl); flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-ctl); color: var(--dim-2); opacity: .6;
  transition: opacity .12s, background .12s, color .12s;
}
.theme-btn:hover { opacity: 1; color: var(--text); background: var(--panel-2); }
.theme-btn:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; opacity: 1; }

/* ---------- preferences ----------
   Three top-bar glyphs became one named control. The panel is wider than the
   mode menu because every row carries a sentence: these are settings you touch
   once, so the space goes to saying what they do rather than to being terse. */

.prefs-wrap { position: relative; }
.prefs-btn { color: var(--dim-2); opacity: .7; }
.prefs-btn:hover, .prefs-btn.active { opacity: 1; color: var(--text); background: var(--panel-2); }
.prefs-menu {
  position: absolute; top: calc(100% + 7px); right: 0; z-index: 60;
  display: flex; flex-direction: column;
  width: 320px; padding: var(--sp-2);
  background: var(--float); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .35);
  backdrop-filter: blur(8px);
}
.pref-head {
  padding: var(--sp-2) var(--sp-2) var(--sp-2); font-size: var(--fs-body); font-weight: var(--fw-title);
  color: var(--text); letter-spacing: .2px;
}
.pref-group { display: flex; flex-direction: column; gap: 1px; }
.pref-group + .pref-group { margin-top: var(--sp-1); padding-top: var(--sp-1); border-top: 1px solid var(--line-soft); }
.pref-group-h {
  padding: var(--sp-1) var(--sp-2) var(--sp-1); font-size: var(--fs-micro); font-weight: var(--fw-title);
  color: var(--dim-2); text-transform: uppercase; letter-spacing: .6px;
}
.pref-row {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  padding: var(--sp-2) var(--sp-2); border-radius: var(--r-ctl); text-align: left;
  /* A four-way segmented control doesn't fit beside its label in a 320px
     panel: `flex: none` on the segments won this fight by crushing the label
     into one word per line. Wrapping lets a control that's too wide drop to
     its own row instead, which is the only outcome that stays readable as
     options get added. */
  flex-wrap: wrap;
}
.pref-row:not(.static):hover { background: var(--hover); }
.pref-row.static { cursor: default; }
/* A basis wide enough that a label never wraps to one word per line — below
   it, the row wraps and the control takes the line underneath. */
.pref-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 150px; }
.pref-txt b { font-size: var(--fs-body); font-weight: var(--fw-title); color: var(--text); }
.pref-txt span { font-size: var(--fs-micro); color: var(--dim-2); line-height: 1.35; }

/* The switch reads as on or off from across the room — position AND colour,
   never colour alone. */
.pref-sw {
  flex: none; width: 30px; height: 18px; border-radius: 9px;
  background: var(--sunken); border: 1px solid var(--line);
  display: flex; align-items: center; padding: var(--sp-1);
  transition: background .14s, border-color .14s;
}
.pref-sw > i {
  width: 12px; height: 12px; border-radius: 50%; background: var(--faint);
  transition: transform .14s, background .14s;
}
.pref-sw.on { background: var(--accent-soft); border-color: var(--accent-line); }
.pref-sw.on > i { transform: translateX(12px); background: var(--accent); }
.pref-row:hover .pref-sw { border-color: var(--line); }

.pref-seg {
  flex: none; display: flex; height: 24px;
  border: 1px solid var(--line); border-radius: var(--r-ctl); overflow: hidden;
}
.pref-seg .seg-btn { padding: 0 var(--sp-3); font-size: var(--fs-micro); }

/* A state, not a control — something is plugged in and working. */
.pref-tag {
  flex: none; height: 20px; padding: 0 var(--sp-2); border-radius: 10px;
  display: flex; align-items: center;
  background: var(--accent-soft); color: var(--accent);
  font-size: var(--fs-micro); font-weight: var(--fw-title);
}
.pref-tag.idle { background: var(--sunken); color: var(--dim-2); }

.pref-name {
  flex: none; width: 116px; height: 24px; padding: 0 var(--sp-2);
  border-radius: var(--r-ctl); border: 1px solid var(--line);
  background: var(--sunken); color: var(--text); font-size: var(--fs-micro);
}
.pref-name:focus { outline: none; border-color: var(--accent); }

/* The export progress bar. Width is the percentage — set imperatively from
   the render's suspension loop, so it moves without the panel rebuilding. */
.exp-bar {
  height: 8px; border-radius: 4px; background: var(--sunken);
  border: 1px solid var(--line); overflow: hidden;
}
.exp-bar > i {
  display: block; height: 100%; background: var(--accent);
  border-radius: 3px; transition: width .12s linear;
}

/* ---------- right panel: pattern preview ---------- */

/* The preview is a picture of the pattern and opens it on double-click, so it
   says so with the cursor rather than only in a tooltip. */
.pat-prev-wrap { cursor: pointer; }

canvas.pat-prev {
  display: block; width: 100%; height: 46px;
  border-radius: var(--r-ctl); background: var(--sunken);
  border: 1px solid var(--line-soft);
}
.pat-prev.empty {
  display: flex; align-items: center; justify-content: center;
  height: 46px; border-radius: var(--r-ctl);
  background: var(--sunken); border: 1px dashed var(--line-hover);
  font-size: var(--fs-micro); color: var(--dim-2); text-align: center; padding: 0 var(--sp-3);
}
.pat-meta {
  display: flex; align-items: center; gap: var(--sp-2);
  margin: var(--sp-2) 0 var(--sp-2); font: var(--fw-value) var(--fs-micro) var(--font); color: var(--dim);
}
.pat-loop {
  margin-left: auto; display: inline-flex; align-items: center; gap: var(--sp-1);
  color: var(--accent); background: var(--accent-soft);
  padding: var(--sp-1) var(--sp-2); border-radius: 10px; font-size: var(--fs-micro);
}

/* ---------- right panel: inline macros ---------- */

.pnl-knobs {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-3) var(--sp-1); margin: var(--sp-1) 0 var(--sp-3);
}
.sknob.sm { gap: var(--sp-1); }
.sknob.sm .knob-dial, .sknob.sm .knob-svg { width: 40px; height: 40px; }
.sknob.sm .knob-bg, .sknob.sm .knob-fg { stroke-width: 6; }
.sknob.sm .knob-label { font-size: var(--fs-micro); font-weight: var(--fw-title); }
.sknob.sm .knob-cap { font-size: var(--fs-badge); margin-top: -2px; }

/* ---------- right panel: summaries ---------- */

.multi-sum {
  display: flex; gap: var(--sp-2); margin-bottom: var(--sp-3);
  font: var(--fw-value) var(--fs-micro) var(--font); color: var(--dim);
}
.multi-sum span:last-child { margin-left: auto; }
/* Footnote, one step below Callout. It does NOT go lighter: --dim-2 is already
   an alias of --dim because a third grey tier measured 3.17:1 and failed the
   4.5 floor. Size is the axis still available. */
.row-note { font-size: var(--fs-badge); color: var(--dim-2); line-height: 1.45; }

/* ---------- right panel: shortcuts disclosure ---------- */

details.sc { border-top: 1px solid var(--line-soft); padding: 0 var(--sp-3) var(--sp-5); }
details.sc summary {
  cursor: pointer; list-style: none;
  padding: var(--sp-3) 0 var(--sp-2); font-size: var(--fs-micro); font-weight: var(--fw-title);
  text-transform: uppercase; letter-spacing: .07em; color: var(--dim-2);
  display: flex; align-items: center; gap: var(--sp-1);
}
details.sc summary::-webkit-details-marker { display: none; }
details.sc summary::before {
  content: ""; width: 0; height: 0; flex: none;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
  transition: transform .15s;
}
details.sc[open] summary::before { transform: rotate(90deg) translateX(-1px); }
details.sc summary:hover { color: var(--text); }

/* --- record pane ---------------------------------------------------------- */

.rec-pane { flex: 1; display: flex; align-items: stretch; justify-content: center; padding: var(--sp-5) var(--sp-5); overflow-y: auto; }
.rec-center { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); margin: auto; width: min(440px, 100%); text-align: center; }
.rec-btn {
  width: 86px; height: 86px; border-radius: 50%;
  background: var(--panel-2); border: 2px solid var(--line-hover);
  display: flex; align-items: center; justify-content: center;
  transition: transform .12s, border-color .12s;
}
.rec-btn:hover { transform: scale(1.05); border-color: #e5484d; }
.rec-btn .rec-dot { width: 34px; height: 34px; border-radius: 50%; background: #e5484d; }
.rec-btn.live { border-color: #e5484d; animation: recpulse 1.6s ease-in-out infinite; }
.rec-btn .rec-stop { width: 28px; height: 28px; border-radius: var(--r-ctl); background: #e5484d; }
@keyframes recpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(229, 72, 77, .35); }
  50% { box-shadow: 0 0 0 14px rgba(229, 72, 77, 0); }
}
.rec-big-hint { font-size: 14px; font-weight: var(--fw-title); }
.rec-sub { font-size: var(--fs-body); color: var(--dim-2); }
.rec-err { font-size: var(--fs-body); color: var(--bad); }
.rec-timer { font-size: 22px; font-weight: var(--fw-title); font-variant-numeric: tabular-nums; }
.rec-meter { width: min(300px, 90%); height: 8px; border-radius: 4px; background: var(--sunken); overflow: hidden; border: 1px solid var(--line-soft); }
.rec-meter i { display: block; height: 100%; width: 0; background: var(--ok, #46a758); border-radius: 4px; transition: width .05s linear; }
.rec-meter i.hot { background: #e5484d; }
.rec-opts { display: flex; flex-direction: column; gap: var(--sp-2); width: min(320px, 100%); margin-top: var(--sp-2); }
.rec-opts .row { margin: 0; }
.rec-check { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-body); color: var(--dim); justify-content: center; }
.rec-note { font-size: var(--fs-micro); color: var(--dim-2); max-width: 330px; line-height: 1.45; }
.rec-row { display: flex; align-items: center; gap: var(--sp-2); justify-content: center; }
.rec-name { width: 190px; }
.rec-wave { width: min(420px, 100%); height: 80px; border-radius: 6px; background: var(--sunken); border: 1px solid var(--line-soft); }
.rec-wave.big { width: 100%; height: 110px; }
.btn.primary { background: var(--cta); border-color: var(--cta); color: var(--cta-ink); }
.btn.primary:hover { filter: brightness(1.08); }

/* --- contextual audio toolbar --------------------------------------------- */

.audiobar {
  position: absolute; left: 0; top: 0; z-index: 12;
  display: flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1); border-radius: 8px;
  background: var(--float); border: 1px solid var(--line);
  box-shadow: 0 8px 26px rgba(0, 0, 0, .3);
  backdrop-filter: blur(8px);
}
.ab-btn {
  width: var(--h-ctl); height: var(--h-ctl); border-radius: var(--r-ctl);
  display: flex; align-items: center; justify-content: center;
  color: var(--dim); transition: background .1s, color .1s;
}
.ab-btn:hover { background: var(--hover); color: var(--text); }
.ab-btn.on { color: var(--accent); background: var(--accent-soft); }
.ab-sep { width: 1px; height: 15px; background: var(--line); margin: 0 var(--sp-1); }

/* --- piano roll ------------------------------------------------------------ */

.pr-card { width: min(1060px, 94vw); height: min(640px, 88vh); display: flex; flex-direction: column; }
.pr-tools { display: flex; align-items: center; gap: var(--sp-3); flex: 1; min-width: 0; justify-content: flex-end; margin-right: var(--sp-2); }
.pr-toolbar {
  display: flex; align-items: center; gap: 1px; flex: none;
  padding: var(--sp-1); border-radius: var(--r-ctl);
  background: var(--panel-2); border: 1px solid var(--line-soft);
}
.pr-tool {
  width: var(--h-ctl); height: var(--h-ctl); border-radius: var(--r-ctl);
  display: flex; align-items: center; justify-content: center;
  color: var(--dim); transition: background .1s, color .1s;
}
.pr-tool:hover { color: var(--text); background: var(--hover); }
.pr-tool.on { color: var(--accent); background: var(--accent-soft); }
.pr-sep { width: 1px; height: 16px; background: var(--line); flex: none; }
.pr-keys { cursor: pointer; }
.pr-snap { display: flex; align-items: center; gap: var(--sp-2); }
.pr-snap .row-l { width: auto; font-size: var(--fs-micro); color: var(--dim-2); }
.pr-snap .sl { width: 96px; }
.pr-hint { font-size: var(--fs-micro); color: var(--dim-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pr-main {
  flex: 1; min-height: 0;
  display: grid;
  grid-template-columns: 56px 1fr;
  grid-template-rows: 1fr 92px;
  border-top: 1px solid var(--line);
}
.pr-keys-clip { overflow: hidden; border-right: 1px solid var(--line); background: var(--sunken); }
.pr-keys { display: block; }
.pr-scroll { overflow: auto; scrollbar-width: thin; scrollbar-color: var(--scroll) transparent; }
.pr-grid { display: block; cursor: cell; }
.pr-vel-label {
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-badge); letter-spacing: 1px; color: var(--dim);
  border-top: 1px solid var(--line); border-right: 1px solid var(--line);
  background: var(--sunken);
}
.pr-vel-clip { overflow: hidden; border-top: 1px solid var(--line); }
.pr-vel { display: block; cursor: ns-resize; }

/* --- record count-in ------------------------------------------------------- */

.rec-count {
  font-size: 64px; font-weight: var(--fw-title); line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--accent);
}

/* --- social panel (comments + chat) ---------------------------------------- */

.social {
  position: absolute; z-index: 14;
  display: flex; flex-direction: column;
  background: var(--float); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: var(--shadow-lg);
  backdrop-filter: blur(14px);
  overflow: hidden;
  /* Free-floating: dragged by the header, resized from the corner grip. */
  resize: both;
  min-width: 240px; min-height: 260px;
}
.soc-head {
  display: flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-2) 0;
  cursor: grab; user-select: none;
}
.soc-head:active { cursor: grabbing; }
.soc-grab { display: flex; color: var(--dim-2); flex: none; padding: 0 1px; }
.soc-tabs {
  flex: 1 1 0; min-width: 0; display: flex; gap: var(--sp-1); padding: var(--sp-1);
  background: var(--panel-2); border-radius: 8px;
}
.soc-tab {
  /* Three tabs in a ~296px panel: they shrink before they collide, and the
     label ellipsises rather than pushing the dock and close buttons off. */
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-1); border-radius: 6px;
  font-size: var(--fs-body); font-weight: var(--fw-title); color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.soc-tab:hover { color: var(--text); }
.soc-tab.on { background: var(--card); color: var(--text); box-shadow: 0 1px 4px rgba(0,0,0,.18); }
.soc-badge {
  display: none; align-items: center; justify-content: center; flex: none;
  min-width: 14px; height: 14px; padding: 0 var(--sp-1); border-radius: 8px;
  background: var(--accent); color: #fff; font-size: var(--fs-badge); font-weight: 700;
}
.modal-x.sm { width: 26px; height: 26px; }

.soc-view { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: var(--sp-3); gap: var(--sp-2); }
.soc-actions { display: flex; gap: var(--sp-2); align-items: center; }
.soc-add {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-ctl);
  background: var(--accent-soft); color: var(--accent);
  font-size: var(--fs-body); font-weight: var(--fw-title);
  border: 1px dashed transparent;
}
.soc-add:hover { background: var(--accent-soft-2); }
.soc-add.arming { background: transparent; border-color: var(--accent); color: var(--accent); }
.soc-mini {
  flex: none; padding: var(--sp-1) var(--sp-2); border-radius: 6px;
  font-size: var(--fs-micro); font-weight: var(--fw-title); color: var(--dim-2);
}
.soc-mini:hover { background: var(--hover); color: var(--text); }
.soc-mini.ic { padding: var(--sp-1) var(--sp-1); opacity: 0; }
.soc-card:hover .soc-mini.ic { opacity: .8; }
.soc-mini.ic:hover { opacity: 1; }
.soc-mini.ic.on { color: var(--good); opacity: 1; }

.soc-list {
  flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--sp-2);
  scrollbar-width: thin; scrollbar-color: var(--scroll) transparent;
  padding-bottom: var(--sp-1);
}
.soc-empty {
  margin: auto; text-align: center; color: var(--dim-2);
  font-size: var(--fs-body); line-height: 1.5; padding: var(--sp-5) var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-1);
}
.soc-empty-t { font-weight: var(--fw-title); font-size: var(--fs-title); color: var(--dim); }

/* Header, body, replies and the reply box on one rhythm. */
.soc-card {
  background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: 9px; padding: var(--sp-2) var(--sp-3) var(--sp-2); cursor: pointer;
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.soc-card:hover { border-color: var(--line-hover); }
.soc-card.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent-line); }
.soc-card.resolved { opacity: .55; }
.soc-card.composer { cursor: default; border-color: var(--accent-line); }
.soc-card-h { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.soc-card-h b { font-size: var(--fs-body); font-weight: var(--fw-title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.soc-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.soc-dot.sm { width: 6px; height: 6px; }
.soc-ago { font-size: var(--fs-micro); color: var(--dim-2); flex: none; }
.soc-bar {
  margin-left: auto; flex: none;
  font: var(--fw-title) var(--fs-badge) var(--font); color: var(--accent);
  background: var(--accent-soft); border-radius: 4px; padding: var(--sp-1) var(--sp-1);
}
.soc-card-h .soc-mini.ic { margin-left: var(--sp-1); }
.soc-card-h .soc-bar + .soc-mini.ic { margin-left: var(--sp-2); }
.soc-text { font-size: var(--fs-body); line-height: 1.45; white-space: pre-wrap; word-break: break-word; }
.soc-replies { display: flex; flex-direction: column; gap: var(--sp-2); border-left: 2px solid var(--line); padding-left: var(--sp-2); }
.soc-reply { display: block; }
.soc-reply b { font-size: var(--fs-micro); margin-right: var(--sp-1); }
.soc-reply .soc-dot { display: inline-block; margin-right: var(--sp-1); }
.soc-reply .soc-text { font-size: var(--fs-body); }
.soc-replylink {
  font-size: var(--fs-micro); font-weight: var(--fw-title); color: var(--dim-2);
  padding: 0; align-self: flex-start;
}
.soc-replylink:hover { color: var(--accent); }
.soc-replybox { display: flex; gap: var(--sp-1); align-items: flex-end; }

.soc-input {
  flex: 1; min-width: 0; resize: none;
  background: var(--sunken); border: 1px solid var(--line-soft); border-radius: var(--r-ctl);
  padding: var(--sp-2) var(--sp-2); font: inherit; font-size: var(--fs-body); line-height: 1.4;
  color: var(--text);
}
.soc-input:focus { outline: none; border-color: var(--accent); }
.soc-inputrow { display: flex; gap: var(--sp-2); align-items: flex-end; }
.soc-send {
  flex: none; width: 30px; height: 30px; border-radius: var(--r-ctl);
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff;
}
.soc-send:hover { background: var(--accent-hover); }
.soc-offline { font-size: var(--fs-micro); color: var(--warn); padding: 0 var(--sp-1); display: none; }
.soc-row { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
.btn.sm2 { padding: var(--sp-1) var(--sp-3); font-size: var(--fs-body); }

.soc-list.chat { gap: var(--sp-1); }
.soc-msg-h { display: flex; align-items: center; gap: var(--sp-2); }
.soc-msg-h b { font-size: var(--fs-body); font-weight: var(--fw-title); }
/* Only BETWEEN groups, so the first needs no exception to cancel. */
* + .soc-msg-h { margin-top: var(--sp-2); }
.soc-msg { font-size: var(--fs-body); line-height: 1.45; padding: var(--sp-1) 0 var(--sp-1) var(--sp-4); white-space: pre-wrap; word-break: break-word; }

.tb-btn.soc-toggle { position: relative; }
.tb-badge {
  display: none; position: absolute; top: 1px; right: 0;
  align-items: center; justify-content: center;
  min-width: 15px; height: 15px; padding: 0 var(--sp-1); border-radius: var(--r-ctl);
  background: var(--bad); color: #fff; font-size: 10px; font-weight: 700;
  pointer-events: none;
}

/* --- storage panel ---------------------------------------------------------- */

.st-card { width: min(640px, 92vw); max-height: min(660px, 88vh); display: flex; flex-direction: column; }
.st-body { display: block; padding: var(--sp-4) var(--sp-4) var(--sp-5); overflow-y: auto; }
.st-note { font-size: var(--fs-body); color: var(--dim); line-height: 1.55; margin: var(--sp-1) 0 var(--sp-3); max-width: 560px; }
.st-list { display: flex; flex-direction: column; border: 1px solid var(--line-soft); border-radius: 8px; overflow: hidden; }
.st-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); background: var(--panel-2);
}
.st-row + .st-row { border-top: 1px solid var(--line-soft); }
.st-name {
  flex: 1; min-width: 0; font-size: var(--fs-body); font-weight: var(--fw-title);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: flex; align-items: center; gap: var(--sp-2);
}
.st-tag {
  flex: none; font-size: var(--fs-badge); font-weight: var(--fw-title); letter-spacing: .2px;
  padding: var(--sp-1) var(--sp-2); border-radius: 4px;
  background: var(--sunken); color: var(--dim-2);
}
.st-tag.inuse { background: var(--accent-soft); color: var(--accent); }
.st-meta { flex: none; font-size: var(--fs-micro); color: var(--dim-2); }
.st-meta.mono { font-family: var(--font); min-width: 52px; text-align: right; }
.st-row .soc-mini.ic.show { opacity: .6; }
.st-row .soc-mini.ic.show:hover { opacity: 1; color: var(--bad); }
.st-empty {
  padding: var(--sp-4); text-align: center; font-size: var(--fs-body); color: var(--dim-2);
  border: 1px dashed var(--line); border-radius: 8px;
}
.st-foot { display: flex; align-items: center; justify-content: space-between; margin-top: var(--sp-2); }
.st-grid { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.st-stat {
  display: flex; flex-direction: column; gap: 1px;
  background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: 8px; padding: var(--sp-2) var(--sp-4); min-width: 74px;
}
.st-stat b { font-size: 14px; font-weight: 700; }
.st-stat span { font-size: var(--fs-badge); color: var(--dim-2); letter-spacing: .3px; }
.btn.disabled { opacity: .4; pointer-events: none; }

/* --- mode wash --------------------------------------------------------------
   The canvas itself says which mode your hands are in. Suggest and view change
   what every gesture means; a 59px pill in the top bar isn't enough signal for
   that. A thin inset frame, always in the corner of your eye, is. */
#canvas-wrap { position: relative; }
#canvas-wrap.mode-suggest::after,
#canvas-wrap.mode-view::after {
  content: ''; position: absolute; inset: 0; z-index: 30;
  pointer-events: none; border-radius: 2px;
}
#canvas-wrap.mode-suggest::after {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent);
  background: linear-gradient(to bottom, color-mix(in srgb, var(--accent) 7%, transparent), transparent 42px);
}
#canvas-wrap.mode-view::after {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--faint) 45%, transparent);
}

/* --- version history -------------------------------------------------------- */
.hist-list { display: flex; flex-direction: column; gap: var(--sp-1); margin-top: var(--sp-3); overflow: auto; max-height: 46vh; }
.hist-row { display: flex; align-items: center; gap: var(--sp-3); }
.hist-when { display: flex; flex-direction: column; gap: 1px; min-width: 116px; }
.hist-when b { font-size: var(--fs-body); font-weight: var(--fw-title); }
.hist-when span { font-size: var(--fs-micro); color: var(--dim-2); }
.hist-what { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-body); color: var(--dim); }
.hist-diff { font: var(--fw-value) var(--fs-badge) var(--font); color: var(--dim-2); }
.hist-tag {
  font-size: var(--fs-badge); font-weight: var(--fw-title); letter-spacing: .2px;
  padding: 1px var(--sp-2); border-radius: var(--r-ctl);
  background: var(--accent-soft); color: var(--accent);
}

/* --- suggestions ------------------------------------------------------------ */

/* Edit / Suggest / View — a compact dropdown by the Share button. The pill
   names the current mode; the menu explains the other two. */
.mode-wrap { position: relative; }
.mode-pick {
  display: flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-3); border-radius: var(--r-ctl);
  font-size: var(--fs-body); font-weight: var(--fw-title); letter-spacing: .2px;
  color: var(--dim); background: var(--panel-2); border: 1px solid var(--line-soft);
  transition: background .1s, color .1s;
}
.mode-pick:hover, .mode-pick.open { color: var(--text); border-color: var(--line); }
.mode-pick.suggesting { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }
.mode-menu {
  position: absolute; top: calc(100% + 7px); right: 0; z-index: 60;
  display: flex; flex-direction: column; gap: var(--sp-1);
  width: 252px; padding: var(--sp-1);
  background: var(--float); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .35);
  backdrop-filter: blur(8px);
}
.mode-item {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-2); border-radius: var(--r-ctl); text-align: left;
}
.mode-item:hover { background: var(--hover); }
.mode-item-check { width: 14px; flex: none; margin-top: 1px; color: var(--accent); opacity: 0; }
.mode-item.on .mode-item-check { opacity: 1; }
.mode-item-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mode-item-txt b { font-size: var(--fs-body); font-weight: var(--fw-title); color: var(--text); }
.mode-item-txt span { font-size: var(--fs-micro); color: var(--dim-2); line-height: 1.35; }

/* The suggest-mode chip: a floating pill tracking the open take. */
.suggestbar {
  position: absolute; left: 50%; top: 10px; transform: translateX(-50%);
  z-index: 12;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-3); border-radius: 999px;
  background: var(--float); border: 1px solid var(--line);
  box-shadow: 0 8px 26px rgba(0, 0, 0, .3);
  backdrop-filter: blur(8px);
  font-size: var(--fs-body);
}
.sug-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  animation: sug-pulse 1.6s ease-in-out infinite;
}
@keyframes sug-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.sug-label { color: var(--text); font-weight: var(--fw-title); white-space: nowrap; }
.sug-btn {
  display: flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-2); border-radius: 999px;
  font-size: var(--fs-micro); font-weight: var(--fw-title); color: var(--dim);
}
.sug-btn:hover { background: var(--hover); color: var(--text); }
.sug-btn.done { background: var(--cta); color: var(--cta-ink); }
.sug-btn.done:hover { background: var(--cta-hover); }

/* Cards on the social rail. */
.soc-card.sug { cursor: default; }
.soc-card.sug.open-take { border-color: var(--accent-line); }
.sug-count {
  margin-left: auto; flex: none;
  min-width: 16px; height: 16px; padding: 0 var(--sp-1); border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel-2); color: var(--dim);
  font-size: var(--fs-badge); font-weight: 700;
}
.sug-note { font-style: italic; }
.sug-sum { font-size: var(--fs-micro); color: var(--dim); margin-top: var(--sp-1); }
.sug-stale { font-size: var(--fs-micro); color: var(--warn); margin-top: var(--sp-1); }
.sug-live { font-size: var(--fs-micro); color: var(--dim-2); }
.sug-actions .btn { display: inline-flex; align-items: center; gap: var(--sp-1); }
.sug-actions .btn.listening { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }
.sug-actions .btn.danger:hover { color: var(--bad); }

/* --- social panel docking --------------------------------------------------- */

/* Docked, the panel is a layout column beside the inspector — same chrome as
   the fixed panels, nothing floating over the canvas. */
.social.docked {
  position: static; flex: none;
  height: auto; min-height: 0;
  border-radius: 0; box-shadow: none; backdrop-filter: none;
  background: var(--panel);
  border: none; border-left: 1px solid var(--line);
  resize: horizontal;
  min-width: 240px; max-width: 420px;
}
.social.docked .soc-head { cursor: grab; }

/* The strip that lights up where the panel will snap while dragging. */
.dock-hint {
  position: fixed; width: 4px; z-index: 99;
  background: var(--accent); border-radius: 2px;
  box-shadow: 0 0 10px var(--accent-line);
  pointer-events: none;
}

/* --- home & welcome --------------------------------------------------------- */

button.logo { cursor: pointer; transition: transform .12s, box-shadow .12s; }
button.logo:hover { transform: scale(1.07); box-shadow: 0 2px 10px rgba(0,0,0,.3); }

.home-root { z-index: 60; }
.home-card-outer { width: min(560px, 92vw); height: auto; max-height: min(640px, 90vh); display: flex; flex-direction: column; padding: var(--sp-5) var(--sp-5); overflow-y: auto; }
.home-card-outer > *, .welcome-card > * { flex: 0 0 auto; }
.logo.lg { width: 68px; height: 68px; border-radius: 12px; flex: none; }
.home-head { display: flex; align-items: center; gap: var(--sp-4); margin-bottom: var(--sp-4); }
.home-title { flex: 1; min-width: 0; }
.home-title h1 { margin: 0; font-size: 19px; font-weight: var(--fw-title); letter-spacing: -.3px; }
.home-tag { font-size: var(--fs-body); color: var(--dim); margin-top: var(--sp-1); }

.home-new {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); border-radius: 10px;
  background: var(--cta); color: var(--cta-ink); font-size: var(--fs-title); font-weight: var(--fw-title);
  transition: background .12s;
}
.home-new:hover { background: var(--cta-hover); }
.home-new-sub { margin-left: auto; font-size: var(--fs-micro); font-weight: var(--fw-value); opacity: .75; }

.home-sect {
  margin: var(--sp-4) 0 var(--sp-2); font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: 1px; text-transform: uppercase; color: var(--dim-2);
}
.home-grid { display: flex; flex-direction: column; gap: var(--sp-2); }
.home-card {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-3); border-radius: 9px; cursor: pointer;
  background: var(--panel-2); border: 1px solid var(--line-soft);
}
.home-card:hover { border-color: var(--line-hover); background: var(--card); }
.home-card.here { border-color: var(--accent-line); }
.home-dot { width: 10px; height: 10px; border-radius: var(--r-mini); background: var(--c, #888); flex: none; }
.home-card-txt { flex: 1; min-width: 0; }
.home-card-name { font-size: var(--fs-title); font-weight: var(--fw-title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.home-card-sub { font-size: var(--fs-micro); color: var(--dim-2); display: flex; gap: var(--sp-2); align-items: baseline; }
.home-card.here .home-card-sub { color: var(--accent); }
.home-card-id { font-family: var(--mono); font-size: var(--fs-badge); opacity: .7; }
.home-card .soc-mini.ic.show { opacity: 0; }
.home-card:hover .soc-mini.ic.show { opacity: .6; }
.home-card .soc-mini.ic.show:hover { opacity: 1; }
/* A copy in flight. The button has to stay visible even if the pointer has
   left the card — otherwise the one card doing something looks the most idle. */
.home-card .soc-mini.ic.show:disabled { opacity: .25; cursor: default; }
.home-card .soc-mini.ic.show:disabled:hover { background: none; color: var(--dim-2); }
.home-card .soc-mini.ic.show.busy, .home-card:hover .soc-mini.ic.show.busy { opacity: 1; color: var(--accent); }

/* Buttons that switch themselves off mid-action (duplicating a project, and
   anything that follows) read as unavailable rather than merely unstyled. */
.btn:disabled { opacity: .5; cursor: default; }
.btn:disabled:hover { background: var(--panel-2); color: var(--dim); }

.home-foot {
  display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-4);
  padding-top: var(--sp-4); border-top: 1px solid var(--line-soft);
  font-size: var(--fs-body); font-weight: var(--fw-title);
}
.home-foot-note { margin-left: auto; font-size: var(--fs-micro); font-weight: 400; color: var(--dim-2); }

.welcome-card {
  width: min(400px, 92vw); height: auto; padding: var(--sp-6) var(--sp-6);
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); text-align: center;
}
.welcome-card h1 { margin: var(--sp-2) 0 0; font-size: 20px; font-weight: var(--fw-title); letter-spacing: -.3px; }
.welcome-label { margin-top: var(--sp-4); font-size: var(--fs-body); color: var(--dim); align-self: flex-start; }
/* `input.home-name`, not `.home-name`: the generic `select.sel, input.txt` rule
   is element+class and so outranked a bare class, which meant this field kept
   the 34px row height and zero vertical padding meant for a dense panel — a
   thin strip under a chunky button, on the first screen anyone sees. The width
   was never the problem; it always matched the button. */
input.home-name {
  width: 100%; flex: 0 0 auto; height: auto;
  font-size: 14px; padding: var(--sp-3); text-align: center;
}
/* Same specificity trap as the field above, in mirror image: `.btn` is a bare
   class too, but it comes later in the file, so its 34px row height won. The
   two are set together and to the same number on purpose — the welcome screen
   is one column of two controls, and any difference between them reads as a
   mistake rather than as hierarchy. */
button.home-go {
  width: 100%; flex: 0 0 auto; height: 46px;
  margin-top: var(--sp-3); font-size: var(--fs-title);
}

/* --- step sequencer --------------------------------------------------------- */

.sq-card { width: min(980px, 94vw); height: auto; max-height: 88vh; }
.sq-tools { display: flex; align-items: center; gap: var(--sp-2); flex: 1; justify-content: flex-end; margin-right: var(--sp-2); flex-wrap: wrap; }
.sq-len { display: flex; align-items: center; gap: var(--sp-1); }
.sq-len-read { font-size: var(--fs-micro); color: var(--dim); min-width: 104px; text-align: center; }
.sq-swing { width: 190px; }
.sq-swing .row-l { width: 40px; font-size: var(--fs-micro); }
.sq-main { display: flex; min-height: 0; border-top: 1px solid var(--line); }
/* 128px fitted 12px names beside two 24px buttons. At 13px and 28px it cut
   "Closed hat" to "Clo…" — the gutter has to grow with what's in it. */
.sq-lanes { flex: none; width: 168px; border-right: 1px solid var(--line); background: var(--sunken); }
.sq-lane { display: flex; align-items: center; gap: var(--sp-1); height: 38px; padding: 0 var(--sp-2) 0 var(--sp-1); }
.sq-lane.off .sq-lane-name { opacity: .4; text-decoration: line-through; }
.sq-lane-name {
  flex: 1; min-width: 0; text-align: left; padding: var(--sp-1) var(--sp-2);
  font-size: var(--fs-body); font-weight: var(--fw-title); color: var(--dim); border-radius: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sq-lane-name:hover { background: var(--hover); color: var(--text); }
.sq-lane .mini { opacity: .5; }
.sq-lane:hover .mini, .sq-lane .mini.on { opacity: 1; }
.sq-scroll { overflow: auto; flex: 1; scrollbar-width: thin; scrollbar-color: var(--scroll) transparent; }
.sq-grid { display: block; cursor: pointer; touch-action: none; }
.sq-hint { padding: var(--sp-2) var(--sp-4); font-size: var(--fs-micro); color: var(--dim-2); border-top: 1px solid var(--line-soft); }

.pad-src.empty { opacity: .5; font-style: italic; }

/* --- the comment popover (thread-at-the-pin) -------------------------------- */
.cpop {
  position: absolute; z-index: 40; width: 264px;
  background: var(--float); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 10px 34px -12px var(--scrim);
  display: flex; flex-direction: column; overflow: hidden;
  backdrop-filter: blur(10px);
}
.cpop-h { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3) var(--sp-1); min-width: 0; font-size: var(--fs-body); }
.cpop-h b { font-size: var(--fs-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cpop-sp { flex: 1; }
.cpop-body { padding: 0 var(--sp-3) var(--sp-2); max-height: 280px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--scroll) transparent; }
.cpop-body .soc-reply { margin-top: var(--sp-2); }
.cpop-inputrow { display: flex; gap: var(--sp-2); align-items: flex-end; padding: var(--sp-2) var(--sp-3) var(--sp-3); border-top: 1px solid var(--line-soft); }
.cpop-inputrow .soc-input { flex: 1; }
.soc-mini.ic.show { opacity: .75; }
.soc-mini.ic.show:hover { opacity: 1; }

/* One-time tips: read once, dismiss forever. */
.tip {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  font-size: var(--fs-micro); color: var(--dim-2); line-height: 1.45;
  background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: var(--r-ctl); padding: var(--sp-2) var(--sp-2);
}
.tip-txt { flex: 1; min-width: 0; }
.tip-x { flex: none; width: 16px; height: 16px; border-radius: 4px; color: var(--dim-2); font-size: var(--fs-badge); line-height: 1; }
.tip-x:hover { background: var(--hover); color: var(--text); }
.pr-tools .tip, .modal-head .tip { max-width: 420px; padding: var(--sp-1) var(--sp-2); background: transparent; border: none; }

/* The speaker on preset/kit cards: preview without selecting. */
.prev-btn {
  width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: auto; background: var(--hover); color: var(--text);
  opacity: .55; cursor: pointer;
}
.prev-btn:hover { opacity: 1; background: var(--accent); color: #fff; }
.kit-card { position: relative; }
.kit-card .prev-btn { position: absolute; top: 8px; right: 8px; margin: 0; }

/* The arpeggiator row under the XY pad. */
.sarp { display: flex; gap: var(--sp-2); align-items: center; margin-top: var(--sp-3); flex-wrap: wrap; }
.sarp .sel { flex: 0 1 auto; min-width: 62px; }

/* --- automation mode -------------------------------------------------------- */

/* The mode bar floats over the top of the canvas while a curve is edited. */
.auto-bar {
  position: absolute; top: 34px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); z-index: 30;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 6px 24px rgba(0,0,0,.28);
  max-width: min(94%, 760px); white-space: nowrap;
}
.auto-bar-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.auto-bar-name { font-weight: var(--fw-title); font-size: var(--fs-body); }
.auto-bar-mode {
  font-size: var(--fs-badge); font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent); border: 1px solid var(--accent); border-radius: 4px; padding: 1px var(--sp-1);
}
/* --- the Sampler ---------------------------------------------------------- */
.smp-top { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-3); flex-wrap: wrap; }
.smp-wave { cursor: pointer; touch-action: none; }
.smp-hint { font-size: var(--fs-body); color: var(--dim); margin: var(--sp-2) 0 var(--sp-1); }
.smp-hint.dim { color: var(--dim-2); font-size: var(--fs-micro); }
.smp-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-3); min-height: 180px; border: 1.5px dashed var(--line);
  border-radius: 12px; padding: var(--sp-5); text-align: center;
}
.smp-drop.tall { min-height: 320px; margin: var(--sp-2); }
.smp-drop.over { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 7%, transparent); }
.smp-drop-big { font-size: 15px; font-weight: var(--fw-title); }
.smp-drop-sub { font-size: var(--fs-body); color: var(--dim); max-width: 420px; }
.smp-drop .btn { white-space: nowrap; }
.soc-mini.danger:hover { color: var(--bad); border-color: var(--bad); }

/* --- capture tab ----------------------------------------------------------- */
.cap-col { flex: 1; display: flex; flex-direction: column; gap: var(--sp-1); overflow-y: auto; padding: var(--sp-3) var(--sp-4); }
.cap-pane { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-1) var(--sp-1) var(--sp-2); }
.cap-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.cap-url {
  flex: 1; min-width: 220px; background: var(--well); color: var(--text);
  border: 1px solid var(--line); border-radius: 8px; padding: var(--sp-2) var(--sp-3); font-size: var(--fs-body);
}
.cap-url:focus { outline: none; border-color: var(--accent); }
.cap-player { border-radius: 10px; overflow: hidden; background: #000; }
.cap-player iframe { width: 100%; aspect-ratio: 16 / 9; border: 0; display: block; }
.cap-player.empty {
  background: var(--well); color: var(--dim); font-size: var(--fs-body);
  display: flex; flex-direction: column; gap: var(--sp-2); align-items: center; justify-content: center;
  min-height: 130px; padding: var(--sp-5); text-align: center;
}
.cap-time { font: var(--fw-title) var(--fs-body) ui-monospace, Menlo, monospace; color: var(--bad); }
.cap-meter {
  flex: 1; max-width: 260px; height: 8px; border-radius: 4px;
  background: var(--well); overflow: hidden;
}
.cap-meter i { display: block; height: 100%; width: 0; background: var(--good); transition: width 60ms linear; }
.cap-meter i.hot { background: var(--bad); }
.cap-take { display: flex; flex-direction: column; gap: var(--sp-2); }
.cap-wave { width: 100%; }
.cap-norm { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-body); color: var(--dim); }
.cap-err { color: var(--bad); font-size: var(--fs-body); }
.btn.rec-live { border-color: var(--bad); color: var(--bad); }

.auto-bar-sel { max-width: 190px; }
.auto-bar-rep { display: flex; align-items: center; gap: var(--sp-1); }
.auto-bar-rep > span { font-size: var(--fs-micro); color: var(--dim-2); }
.auto-bar-rep .auto-bar-sel { max-width: 86px; }
/* The hint is the first thing to give up room: with a stretch selected the
   bar carries two more buttons, and a button pushed off the edge is a feature
   nobody can reach. Buttons hold their size; the sentence shrinks. */
.auto-bar-hint {
  font-size: var(--fs-micro); color: var(--dim-2);
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.auto-bar > .btn, .auto-bar-dot, .auto-bar-mode { flex: 0 0 auto; }
.auto-bar-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* A parameter that carries a curve wears a small accent dot. */
.automated { position: relative; }
.automated::after {
  content: ''; position: absolute; top: 1px; right: 1px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); pointer-events: none;
}

/* The inspector's list of a sound's curves. */
.auto-row { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-1) 0; }
.auto-row-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
.auto-row-name { flex: 1; font-size: var(--fs-body); overflow: hidden; text-overflow: ellipsis; }

/* --- kits you build yourself ------------------------------------------------
   A saved kit reads as yours: same card, marked with the accent so it's
   distinguishable from the factory four at a glance. */
.kit-card.mine { border-color: color-mix(in srgb, var(--accent) 40%, var(--line-soft)); }
.kit-card.mine .kit-desc { color: var(--accent); opacity: .85; }
.kit-del {
  position: absolute; bottom: 7px; right: 7px; display: grid; place-items: center;
  width: 18px; height: 18px; border-radius: 4px; cursor: pointer;
  color: var(--dim-2); opacity: 0; transition: opacity .1s, color .1s;
}
.kit-card:hover .kit-del { opacity: 1; }
.kit-del:hover { color: var(--bad); background: var(--panel); }
.kit-save {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-radius: 8px; cursor: pointer; width: 100%;
  font-size: var(--fs-body); font-weight: var(--fw-title);
  color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
}
.kit-save:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); }
/* An unnamed pad states its number quietly — it isn't claiming to be a kick. */
.pad-name.unnamed { color: var(--dim-2); font-weight: var(--fw-value); }
.lane-cell.empty .lane-name { opacity: .5; font-style: italic; }

/* --- dropped-audio dialog + canvas drop state ------------------------------ */
.ad-card { width: 420px; height: auto; }
.ad-card > * { flex: 0 0 auto; }
.ad-body { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-1) var(--sp-4) var(--sp-4); }
.ad-file {
  font-size: var(--fs-body); color: var(--dim); margin-bottom: var(--sp-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ad-opt {
  display: flex; flex-direction: column; gap: var(--sp-1);
  width: 100%; text-align: left; cursor: pointer;
  padding: var(--sp-3) var(--sp-4); border-radius: 9px;
  background: var(--panel-2); border: 1px solid var(--line-soft); color: var(--text);
}
.ad-opt:hover { border-color: var(--accent); background: var(--card); }
.ad-opt.primary { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.ad-opt-title { font-size: var(--fs-title); font-weight: var(--fw-title); }
.ad-opt-desc { font-size: var(--fs-body); color: var(--dim); line-height: 1.45; }
.ad-opt-sub { font-size: var(--fs-micro); color: var(--accent); font-weight: var(--fw-value); }
/* The canvas invites the drop. */
#canvas-wrap.drop-audio::after {
  content: ''; position: absolute; inset: 6px; pointer-events: none;
  border: 2px dashed var(--accent); border-radius: 10px; opacity: .7;
}
.loop-rows { display: grid; gap: var(--sp-2); margin-top: var(--sp-2); }
.loop-read { font-size: var(--fs-micro); color: var(--dim); }

/* --- the mixer -------------------------------------------------------------
   Same posture as the typing keyboard: bottom-centre, floating, only as wide
   as its strips, horizontally scrollable beyond that. */
.mixer {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 12px;
  width: max-content; max-width: calc(100% - 24px);
  z-index: 15; display: flex; flex-direction: column;
  background: var(--float); border: 1px solid var(--line);
  border-radius: 12px; backdrop-filter: blur(10px);
  box-shadow: 0 10px 34px -14px var(--scrim);
}
.mx-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-4);
}
.mx-title { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-body); font-weight: var(--fw-title); letter-spacing: .4px; }
.modal-x.sm { width: 22px; height: 22px; }
.mx-scroll {
  display: flex; align-items: stretch; gap: var(--sp-2);
  padding: 0 var(--sp-3) var(--sp-3); overflow-x: auto; max-width: 100%;
}
.mx-empty { padding: var(--sp-4) var(--sp-5); font-size: var(--fs-body); color: var(--dim); }
.mx-strip {
  flex: none; width: 168px; display: flex; flex-direction: column; gap: var(--sp-2);
  background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: 9px; padding: var(--sp-2);
}
.mx-strip.silenced { opacity: .55; }
.mx-strip.master { background: color-mix(in srgb, var(--accent) 7%, var(--panel-2)); }
.mx-name {
  display: flex; align-items: center; gap: var(--sp-2); min-width: 0;
  background: none; border: 0; color: var(--text); cursor: pointer;
  font-size: var(--fs-body); font-weight: var(--fw-title); padding: var(--sp-1) var(--sp-1); text-align: left;
}
.mx-name.static { cursor: default; }
.mx-dot { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.mx-dot.master { background: var(--accent); }
.mx-name-txt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mx-mix { display: flex; gap: var(--sp-3); align-items: stretch; }
.mx-fader-col { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.mx-fader {
  position: relative; width: 26px; height: 118px; cursor: ns-resize;
  background: var(--panel); border: 1px solid var(--line-soft); border-radius: 6px;
  overflow: visible; touch-action: none;
}
.mx-fader-fill {
  position: absolute; left: 3px; right: 3px; bottom: 3px;
  border-radius: 4px; background: var(--accent); opacity: .35;
}
.mx-fader-grip {
  position: absolute; left: -2px; right: -2px; height: 10px;
  border-radius: 4px; background: var(--text); opacity: .9;
  box-shadow: 0 1px 5px rgba(0,0,0,.35);
}
.mx-db { font: var(--fw-value) var(--fs-badge) ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--dim); }
.mx-side { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); justify-content: flex-start; flex: 1; }
.mx-ms { display: flex; gap: var(--sp-1); }
.mx-fx { max-height: 190px; overflow-y: auto; overflow-x: hidden; }
.mx-fx.note { font-size: var(--fs-micro); color: var(--dim-2); padding: var(--sp-1) var(--sp-1); }
/* Spacing comes from .fx-list's gap — the mixer uses the same rack. */

/* A strip is 168px wide. An fx row laid out in a line — label, track, value —
   spends 56 on each end and leaves the slider about twenty pixels of track,
   which is a control you can see but can't set. In here the label sits ABOVE
   its control so the track gets the full width. Same rows, same components,
   read top to bottom instead of left to right. */
.mx-fx .row { flex-direction: column; align-items: stretch; gap: 0; min-height: 0; }
.mx-fx .row-l { width: auto; font-size: var(--fs-badge); color: var(--dim-2); }
.mx-fx .sl { width: 100%; }
.mx-fx .sl-track { min-width: 40px; }
.mx-fx .sl-val { width: 48px; }
/* Switches and pickers keep their line — they're already the right shape. */
.mx-fx .row.row-sw, .mx-fx .row:has(select.sel) {
  flex-direction: row; align-items: center; gap: var(--sp-2); min-height: var(--h-ctl);
}
.mx-fx .row:has(select.sel) .row-l { width: 44px; }
/* reorder affordance: the whole header is the handle now */
.fx-head { cursor: grab; user-select: none; }
.fx-card.dragging { opacity: .75; }
.fx-card.dragging .fx-head { cursor: grabbing; }
.fx-grip { display: inline-flex; color: var(--dim-2); }

/* --- mixer level meters ---------------------------------------------------- */
.mx-fader-row { display: flex; gap: var(--sp-1); align-items: stretch; }
.mx-meter {
  position: relative; width: 8px; height: 118px;
  background: var(--panel); border: 1px solid var(--line-soft); border-radius: 4px;
  overflow: hidden; display: flex; flex-direction: column; justify-content: flex-end;
}
.mx-meter-fill {
  width: 100%; height: 0%;
  background: linear-gradient(to top, #3d8a66, #52ac7f);
  transition: none;
}
.mx-meter-fill.hot { background: linear-gradient(to top, #3d8a66, #52ac7f 58%, #e8b04b); }
.mx-meter-clip {
  position: absolute; top: 0; left: 0; right: 0; height: 7px;
  background: transparent; border-bottom: 1px solid var(--line-soft);
}
.mx-meter-clip.on { background: var(--bad); box-shadow: 0 0 7px color-mix(in srgb, var(--bad) 70%, transparent); }

/* Following a collaborator: their avatar wears a ring. */
.avatar { cursor: pointer; }
.avatar.following { box-shadow: 0 0 0 2px var(--panel), 0 0 0 4px var(--accent); }

/* ---------- Resin ---------- */

.resin { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4) var(--sp-4) var(--sp-1); }

/* The table and the scope share the top: one shows what the sound is made of,
   the other what it currently is. */
.resin-top { display: flex; gap: var(--sp-3); align-items: stretch; }
.resin-wave-wrap {
  flex: 1; min-width: 0; position: relative;
  background: var(--sunken); border: 1px solid var(--line-soft);
  border-radius: var(--r-ctl); overflow: hidden;
}
.resin-wave { display: block; width: 100%; height: 220px; cursor: ew-resize; }
.resin-wave-name {
  position: absolute; left: 12px; top: 10px;
  font-size: var(--fs-micro); font-weight: var(--fw-value); color: var(--dim);
  pointer-events: none;
}
.resin-scope-wrap {
  width: 200px; flex: none; display: flex; flex-direction: column;
  background: var(--sunken); border: 1px solid var(--line-soft);
  border-radius: var(--r-ctl); overflow: hidden;
}
.resin-scope { display: block; width: 100%; height: 194px; }
.resin-scope-foot {
  border-top: 1px solid var(--line-soft); padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-micro); color: var(--dim-2);
}
.resin-voices { font-variant-numeric: tabular-nums; }

/* Macros: four knobs above the patch, because these are the ones you play.
   Each opens downward into its own wiring rather than into a shared panel —
   the links belong to the knob, and reading them side by side is how you tell
   two macros apart. */
.resin-macros {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-3);
  margin: var(--sp-3) 0 var(--sp-1); align-items: start;
}
.macro {
  background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: var(--r-ctl); padding: var(--sp-3) var(--sp-3) var(--sp-2);
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
}
.macro.open { border-color: var(--line); }
.macro-name {
  width: 100%; text-align: center; background: none; border: none;
  color: var(--text); font-size: var(--fs-body); font-weight: var(--fw-title);
  padding: var(--sp-1) var(--sp-1); border-radius: var(--r-mini);
}
.macro-name:hover { background: var(--hover); }
.macro-name:focus { outline: none; background: var(--sunken); }
.macro-links {
  font-size: var(--fs-micro); color: var(--dim-2);
  padding: var(--sp-1) var(--sp-2); border-radius: 999px; border: 1px solid var(--line-soft);
}
.macro-links:hover { color: var(--text); border-color: var(--line); }
.macro.open .macro-links { color: var(--accent); border-color: var(--accent-line); }
.macro-edit { width: 100%; display: flex; flex-direction: column; gap: var(--sp-1); margin-top: var(--sp-1); }
.macro-link { display: flex; align-items: center; gap: var(--sp-2); }
.macro-link-name {
  flex: 1; min-width: 0; font-size: var(--fs-micro); color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.macro-link .row { flex: none; width: 74px; }
.macro-link .row-l { display: none; }
.macro-edit .row { margin: 0; }

.resin-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--sp-3);
}
.resin-card {
  background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: var(--r-ctl); padding: var(--sp-3) var(--sp-3) var(--sp-3);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.resin-card.wide { margin: var(--sp-3) var(--sp-4) var(--sp-4); }
/* A switched-off section stays readable — it's still telling you what it would
   do — but stops competing with the ones that are actually sounding. */
.resin-card.off { opacity: .45; }
.resin-card.off:hover { opacity: .8; }
.resin-card-h {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-micro); font-weight: var(--fw-value); color: var(--dim);
}
.resin-card-sub { color: var(--dim-2); font-variant-numeric: tabular-nums; }
.resin-power {
  margin-left: auto; width: 26px; height: 15px; border-radius: 8px;
  background: var(--line); position: relative; transition: background .12s;
}
.resin-power::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--faint); transition: transform .12s, background .12s;
}
.resin-power.on { background: var(--accent-soft); }
.resin-power.on::after { transform: translateX(11px); background: var(--accent); }

.resin-curve {
  display: block; width: 100%; height: auto;
  background: var(--sunken); border-radius: var(--r-mini);
}
.resin-card .knob-row { display: flex; flex-wrap: wrap; gap: var(--sp-1); justify-content: flex-start; }

/* The matrix: one line per route, readable top to bottom. */
.resin-matrix { display: flex; flex-direction: column; gap: var(--sp-2); }
.resin-slot { display: flex; align-items: center; gap: var(--sp-2); }
.resin-slot .sel { flex: 1; min-width: 0; }
.resin-arrow { color: var(--dim-2); flex: none; }
.resin-amt {
  width: 110px; flex: none; height: var(--h-ctl); position: relative;
  background: var(--sunken); border-radius: var(--r-mini);
  cursor: ew-resize; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.resin-amt i {
  position: absolute; top: 0; bottom: 0; opacity: .35;
}
.resin-amt span {
  position: relative; font-size: var(--fs-micro); font-weight: var(--fw-value);
  font-variant-numeric: tabular-nums; color: var(--text);
}
/* The centre line is where "no modulation" lives, so it's marked. */
.resin-amt::after {
  content: ''; position: absolute; left: 50%; top: 4px; bottom: 4px;
  width: 1px; background: var(--line-hover);
}
.resin-empty {
  padding: var(--sp-4) var(--sp-1); font-size: var(--fs-body); color: var(--dim-2);
}
.resin-fx { padding: 0 var(--sp-4) var(--sp-4); }
/* Resin has a lot to show. The window takes the height it can get and the
   body scrolls, rather than the card growing past the screen. */
.modal-card.resin-card-root { height: min(820px, 92vh); width: min(1180px, 94vw); }
.resin-card-root .modal-body { overflow-y: auto; flex: 1; min-height: 0; }

/* The arpeggiator's row in the keyboard bar. Collapsed to one button until
   it's on — an arp you aren't using shouldn't cost four dropdowns of space. */
.kbar-arp { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.kbar-arp .sel { height: var(--h-mini); flex: none; width: auto; min-width: 74px; }
.kbar-arp .btn.sm.on { background: var(--accent-soft); color: var(--accent); }

/* Import / export live at the foot of the category rail — near the list they
   act on, out of the way of the cards. */
.cat-foot {
  margin-top: auto; padding: var(--sp-3) var(--sp-2) var(--sp-1); display: flex; flex-direction: column; gap: var(--sp-2);
  border-top: 1px solid var(--line-soft);
}
.cat-foot .btn.sm { width: 100%; }
.kit-card.mine .kit-name::after {
  content: 'saved'; margin-left: var(--sp-2); font-size: var(--fs-badge);
  color: var(--dim-2); font-weight: var(--fw-value); letter-spacing: .3px;
}
.kit-link {
  margin-left: auto; color: var(--dim-2); cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.kit-link + .kit-link { margin-left: var(--sp-3); }
.kit-link:hover { color: var(--text); }
.kit-link.kill:hover { color: var(--bad); }


/* --- Icon sizing, by the control an icon lives in -------------------------
 * Call sites pass a pixel size to `icon()`, which lands on the SVG's width and
 * height attributes. Those numbers were chosen when the buttons were 24px; the
 * type-scale pass grew them to 28 and every glyph inside quietly became
 * undersized. Sizing here instead means the icon follows its control, and one
 * rule fixes every button rather than thirty call sites drifting apart again.
 * `flex: none` for the same reason the logo needed it — a flex parent with
 * padding will otherwise take the difference out of the icon's width. */
.btn > svg { width: 14px; height: 14px; flex: none; }
.btn.sm > svg { width: 12px; height: 12px; }
.theme-btn > svg { width: 15px; height: 15px; flex: none; }
.mini > svg { width: 11px; height: 11px; flex: none; }
.tb-btn > svg { flex: none; }

/* Flip's two lanes. In the card it's a thumbnail you open — the cursor says
   "click me", not "draw here" — and only the big one takes a crosshair. */
.flip-cv {
  display: block; width: 100%; height: auto; aspect-ratio: 408 / 144;
  border-radius: var(--r-ctl); border: 1px solid var(--line-soft);
  cursor: pointer; touch-action: none;
}
.flip-cv:hover { border-color: var(--line); }
.flip-cv.big { cursor: crosshair; }

/* A bypassed effect is still legible — you need to read what it is to decide
   whether to switch it back on — but it recedes, and its power glyph lights. */
.fx-card.off .fx-body { opacity: .38; }
.fx-card.off .fx-name { color: var(--dim-2); }
.mini.fx-off.on { color: var(--accent); }

/* Flip's big editor: the same two lanes with room to draw them. */
.flip-root { align-items: center; justify-content: center; }
.flip-card {
  /* Overrides .modal-card's fixed height — this one is exactly as tall as two
     lanes and a row of controls, and dead space under a drawing surface reads
     as a broken layout. */
  width: min(1220px, 96vw); height: auto; max-height: 92vh;
}
.flip-big-body { padding: var(--sp-4) var(--sp-4) var(--sp-2); overflow-x: auto; }
.flip-cv.big { width: 100%; height: auto; aspect-ratio: 1160 / 454; }
.flip-big-foot {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4) var(--sp-4); border-top: 1px solid var(--line-soft);
}
.flip-shape { display: flex; align-items: center; gap: var(--sp-1); }
.flip-shape > span { font-size: var(--fs-micro); color: var(--dim-2); margin-right: var(--sp-1); }

/* ==========================================================================
   Motion — effect displays, folds, entrances
   ========================================================================== */

/* The floor for everything below. Feld had no reduced-motion handling at all
   before this, and every animation added here is decoration by definition, so
   honouring the preference is the price of adding any of it. The canvas pulse
   answers to the same preference through the Motion pref in main.js. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}
/* The Motion pref, for people who want it off without changing their OS. */
body.no-motion *, body.no-motion *::before, body.no-motion *::after {
  animation-duration: .01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: .01ms !important;
}

/* --- effect displays ------------------------------------------------------
 *
 * The rule, from NI's Choral / Phasis / Flair: never draw the LFO, draw the
 * structure it's moving. See js/ui/fxviz.js for what each one is and why.
 *
 * "Subtle" is a number, not a vibe. Measured against the field these sit on:
 * structure at 1.54–1.63:1 in light and 1.81–1.96:1 in dark, against 4.62:1
 * and 6.78:1 for the row labels in the same cards — about a third of the
 * label's contrast. Present as texture, never the thing you look at. Dark runs
 * slightly hotter on purpose: light-on-dark needs a little more to read as the
 * same weight.
 */
.fxv {
  position: relative; height: 34px; overflow: hidden;
  border-radius: var(--r-mini); background: var(--sunken);
  border: 1px solid var(--line-soft);
}
/* `.fxv-anim` is the marker the transport gate at the bottom of this block
   keys off — EVERY animated element in a display must carry it. will-change is
   applied separately, only to the few that transform: putting it on eight taps
   and forty-four grains would ask the compositor for fifty layers to no end. */
.fxv-chorus i, .fxv-phaser .ch, .fxv-flanger .comb { will-change: transform; }
.fxv-cv { display: block; width: 100%; height: 100%; }

/* CHORUS — two voices, either side of centre so they stay legible as TWO, and
   so the drift reads as them approaching and receding: the beating you hear. */
.fxv-chorus i {
  position: absolute; top: -30%; bottom: -30%; width: 30%;
  background: radial-gradient(ellipse at center,
    rgba(var(--fx-ink), .34) 0%, rgba(var(--fx-ink), .12) 42%, transparent 70%);
}
.fxv-chorus .v1 { left: 12%; animation: fxv-voice var(--p, 1.4s) ease-in-out infinite alternate; }
.fxv-chorus .v2 { right: 12%; animation: fxv-voice var(--p2, 1.1s) ease-in-out infinite alternate-reverse; }
@keyframes fxv-voice {
  from { transform: translateX(calc(var(--drift, 7px) * -1)); }
  to   { transform: translateX(var(--drift, 7px)); }
}

/* PHASER — six notches, the two channel halves sliding opposite ways. */
.fxv-phaser .ch {
  position: absolute; left: 0; right: 0; height: 50%;
  animation: fxv-notches var(--p, 2.5s) ease-in-out infinite alternate;
}
.fxv-phaser .l { top: 0; }
.fxv-phaser .r { bottom: 0; animation-direction: alternate-reverse; }
.fxv-phaser .ch span {
  position: absolute; top: 8%; bottom: 8%; width: 11px; margin-left: -5.5px;
  background: linear-gradient(90deg, transparent,
    rgba(var(--fx-ink), var(--sharp, .34)) 50%, transparent);
}
@keyframes fxv-notches {
  from { transform: translateX(calc(var(--sweep, 9px) * -1)); }
  to   { transform: translateX(var(--sweep, 9px)); }
}
/* The channel split — the only static mark in any of the modulation displays. */
.fxv-phaser::after {
  content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 1px;
  background: rgba(var(--fx-ink), .13);
}

/* FLANGER — a comb that stretches rather than slides. */
.fxv-flanger .comb {
  position: absolute; inset: 0; transform-origin: left center;
  background: repeating-linear-gradient(90deg,
    transparent 0 calc(26px - var(--tooth, 2px)),
    rgba(var(--fx-ink), var(--sharp, .30)) calc(26px - var(--tooth, 2px)) 26px);
  animation: fxv-comb var(--p, 5.5s) ease-in-out infinite alternate;
  /* Faded ends, so a row of teeth reads as texture rather than as a fence —
     and so the spacing change is visible without counting them. At the 13px
     spacing this started with there were 44 teeth across the card, which reads
     as a barcode and hides the one thing the display exists to show. */
  -webkit-mask-image: linear-gradient(transparent, #000 32%, #000 68%, transparent);
  mask-image: linear-gradient(transparent, #000 32%, #000 68%, transparent);
}
@keyframes fxv-comb {
  from { transform: scaleX(1); }
  to   { transform: scaleX(var(--stretch, 1.3)); }
}

/* DELAY — taps at the note division, each weighted feedback^n. */
.fxv-delay .taps { position: absolute; inset: 0; }
.fxv-delay .tap {
  position: absolute; top: 14%; bottom: 14%; width: 2.5px; margin-left: -1.25px;
  border-radius: 1px; background: rgba(var(--fx-ink), 1);
  animation: fxv-tap var(--cycle, 4s) linear infinite var(--d, 0s);
}
/* The pulse lifts the tap's own weight and lets it fall, so a nearly-inaudible
   eighth repeat gets a nearly-invisible flash rather than the same one as the
   first. */
@keyframes fxv-tap {
  0%   { opacity: calc(var(--w) * 1); transform: scaleY(1.14); }
  9%   { opacity: calc(var(--w) * .34); transform: scaleY(1); }
  100% { opacity: calc(var(--w) * .34); transform: scaleY(1); }
}
/* The dry hit the repeats come from. */
.fxv-delay .src {
  position: absolute; top: 6%; bottom: 6%; left: 3%; width: 2.5px;
  background: rgba(var(--fx-ink), .5);
}

/* REVERB — reflections radiating from the source. Taller than the others
   because a radial picture needs somewhere to radiate. */
.fxv-verb { height: 56px; }
.fxv-verb .ir { position: absolute; inset: 0; }
.fxv-verb .g {
  position: absolute; left: 50%; top: 50%; width: 3.5px; height: 3.5px;
  border-radius: 50%; background: rgba(var(--fx-ink), 1); opacity: 0;
  animation: fxv-radiate var(--rc, 3s) linear infinite var(--d, 0s);
}
@keyframes fxv-radiate {
  0%   { transform: translate(-50%, -50%) scale(.45); opacity: 0; }
  16%  { opacity: var(--w, .4); }
  100% { transform: translate(-50%, -50%) translate(var(--x), var(--y)) scale(1); opacity: 0; }
}
/* The source everything leaves from: the dry signal, before the room. */
.fxv-verb::before {
  content: ''; position: absolute; left: 50%; top: 50%; width: 5px; height: 5px;
  margin: -2.5px 0 0 -2.5px; border-radius: 50%;
  background: rgba(var(--fx-ink), .5);
}

/* DRIVE — the waveform that comes out, against the clean one as a ghost. */
.fxv-drive svg, .fxv-duck svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.fxv-drive path, .fxv-duck path { fill: none; vector-effect: non-scaling-stroke; }
/* The ghost has to be readable or the idea collapses — the gap between these
   two lines IS the distortion, and you can't see a gap with one edge. */
.fxv-drive .ghost { stroke: rgba(var(--fx-ink), .34); stroke-width: 1; stroke-dasharray: 2 2.5; }
.fxv-drive .wave { stroke: rgba(var(--fx-ink), .58); stroke-width: 1.6; stroke-linejoin: round; }
/* The distortion breathes rather than the wave travelling.
   Two waveforms — the Amount you set and a harder one — cross-faded in
   opposite phase, so the shoulders bend and relax in place. This replaced a
   left-to-right marquee, which implied motion the effect hasn't got: nothing
   in a waveshaper moves along the signal, it bends it where it stands. */
.fxv-drive .wave.a { animation: fxv-bend-a var(--fp, 3s) ease-in-out infinite alternate; }
.fxv-drive .wave.b { animation: fxv-bend-b var(--fp, 3s) ease-in-out infinite alternate; }
@keyframes fxv-bend-a { from { opacity: 1; } to { opacity: .12; } }
@keyframes fxv-bend-b { from { opacity: .12; } to { opacity: 1; } }

/* DUCK — the pump envelope, repeated at the trigger interval. */
.fxv-duck .env { stroke: rgba(var(--fx-ink), .55); stroke-width: 1.6; stroke-linejoin: round; }
.fxv-duck .unity { stroke: rgba(var(--fx-ink), .2); stroke-width: 1; stroke-dasharray: 2 3; }

/* DYNAMICS — the transfer curve, and the gap. Both canvas layers are static;
   the veil scaling in from the right is the only per-frame change, and it's a
   compositor transform. */
.fxv-dyn { height: 62px; }
.fxv-dyn canvas { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }
.fxv-dyn .veil {
  position: absolute; inset: 0; background: var(--sunken);
  transform-origin: right center; transform: scaleX(var(--v, 1));
  will-change: transform;
}
/* Top-left: the curve is monotonic bottom-left to top-right, so that corner is
   the one region nothing ever draws into. */
.fxv-dyn .read {
  position: absolute; left: 7px; top: 4px; font-size: var(--fs-badge);
  color: var(--dim); font-variant-numeric: tabular-nums;
  opacity: .4; transition: opacity .2s;
}
.fxv-dyn .read.on { opacity: 1; }

/* Nothing moves unless the transport does.
 *
 * This rule has to sit AFTER every display above, and it has to win on
 * specificity rather than on source order. Each display sets its animation with
 * the `animation:` shorthand, which silently resets animation-play-state to
 * `running` — an earlier `.fxv-anim { paused }` rule loses to it at equal
 * specificity, and the displays then animate with the transport stopped. That
 * is invisible unless you actually stop playback and look. */
body:not(.playing) .fxv .fxv-anim { animation-play-state: paused; }

/* --- B1 · the fold moves --------------------------------------------------
 *
 * grid-template-rows 1fr → 0fr animates to auto height without measuring
 * anything and without a fixed max-height to guess wrong. Needs the body
 * present in the DOM rather than absent, which is why fxCard and foldSection
 * both render theirs unconditionally now. */
.sect-body, .fx-body {
  display: grid; grid-template-rows: 1fr; opacity: 1;
  transition: grid-template-rows .2s ease, opacity .16s ease;
}
.sect-fold.folded .sect-body, .fx-card.collapsed .fx-body {
  grid-template-rows: 0fr; opacity: 0;
}
.sect-body-in, .fx-body-in {
  overflow: hidden; min-height: 0;
  display: flex; flex-direction: column; gap: var(--sp-2);
}
/* A folded card is head-only, so it loses the gap meant for a body. */
.fx-card.collapsed { gap: 0; padding-bottom: var(--sp-2); }

/* --- B3 · controls acknowledge the press ---------------------------------- */
.btn:active, .mini:active, .tb-btn:active, .sw:active, .chip:active { transform: scale(.96); }
.btn, .mini, .tb-btn, .sw, .chip { transition: transform .09s, background .12s, color .12s, border-color .12s; }
/* The grip lands rather than stopping dead. */
.sl-grip.settle { animation: sl-settle .22s cubic-bezier(.2, 1.4, .4, 1); }
@keyframes sl-settle { 0% { transform: scale(1.32); } 100% { transform: scale(1); } }

/* --- B4 · entrances, natively --------------------------------------------
 *
 * @starting-style gives the "before" state an entry transition starts from, so
 * a panel that appears fades and lifts in with no JS at all. Baseline since
 * August 2024 (Chrome 117+, Safari 17.5+, Firefox 129+); where it's missing the
 * panel simply appears, exactly as it does today.
 *
 * Entrance only, deliberately. These panels are shown and hidden by setting
 * `style.display` from JS, and an EXIT transition would need the hidden state
 * to carry opacity: 0 as well — which means either a class rename across a
 * dozen files or an attribute selector matching on inline style, and neither is
 * worth it for the half nobody notices. */
.modal-root, .mixer, .cpop, .history-root, .flipbig-root {
  transition: opacity .18s ease;
  opacity: 1;
}
@starting-style {
  .modal-root, .mixer, .cpop, .history-root, .flipbig-root { opacity: 0; }
}
.modal-card, .mixer-inner, .cpop-card {
  transition: transform .2s cubic-bezier(.2, .9, .3, 1);
  transform: none;
}
@starting-style {
  .modal-card, .mixer-inner, .cpop-card { transform: translateY(9px) scale(.985); }
}

/* --- B5 · the inspector cross-fades --------------------------------------
 *
 * Clicking along a row of clips rebuilds the right panel each time, which
 * flickers. A fade-IN on the new content reads as a cross-fade without the
 * risk: a fade-out-then-swap needs a delayed DOM mutation, and the panel
 * re-renders often enough (every pointerup, every structural change) that the
 * timeout would race with the next refresh. Only fires when the panel's
 * SUBJECT changes — refreshing the same clip must not flicker. */
.pnl-swap { animation: pnl-in .13s ease; }
@keyframes pnl-in { from { opacity: 0; } to { opacity: 1; } }

/* The Record doorway's switches carry full sentences — left-align so a wrapped
   line reads as a sentence, not a centred fragment. The name row splits label
   and field across the width the options column already has. */
.rec-opts .rec-check { justify-content: flex-start; text-align: left; }
.rec-check input[type=checkbox] { flex: none; }
.rec-name-row { justify-content: space-between; }
.rec-name-row .rec-name { flex: 1; width: auto; }

/* --- the compact Sound Picker ---------------------------------------------
   The category dots use the clip palette's hues (Orchid), not the browser's
   legacy preset rainbow — one colour system everywhere. */
   Double-click empty canvas. Fixed-position popover at the cursor: search,
   recents/starters, and the sources row that makes it complete by
   construction — see js/ui/picker.js. */
.spk {
  --spk-notes: #655ccc; --spk-pattern: #d13d7b;
  position: fixed; z-index: 70; width: 300px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 11px;
  box-shadow: 0 18px 44px rgba(10, 12, 18, .24);
  display: flex; flex-direction: column; overflow: hidden;
}
.spk-q {
  border: 0; border-bottom: 1px solid var(--line-soft); background: none;
  font: inherit; font-size: var(--fs-body); padding: var(--sp-2) var(--sp-3);
  outline: none; color: var(--text);
}
.spk-q::-webkit-search-cancel-button { -webkit-appearance: none; }
.spk-list { max-height: 236px; overflow-y: auto; padding: var(--sp-1); }
.spk-h {
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .7px;
  color: var(--dim); padding: var(--sp-1) var(--sp-2) 0;
}
.spk-row {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  padding: var(--sp-1) var(--sp-2); border-radius: var(--r-mini); font-size: var(--fs-body);
  color: var(--text); text-align: left;
}
.spk-row:hover, .spk-row.kb { background: var(--accent-soft); }
.spk-dot { width: 8px; height: 8px; border-radius: 2.5px; flex: none; background: var(--faint); }
.spk-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.spk-who { margin-left: auto; font-size: var(--fs-micro); color: var(--dim); flex: none; }
.spk-act { color: var(--accent); font-weight: var(--fw-label); }
.spk-glyph { flex: none; }
.spk-none { padding: var(--sp-2); font-size: var(--fs-body); color: var(--dim); }
.spk-src { display: flex; border-top: 1px solid var(--line-soft); }
.spk-srcbtn {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-1); font-size: var(--fs-micro); color: var(--dim);
  border-right: 1px solid var(--line-soft);
}
.spk-srcbtn:last-child { border-right: 0; }
.spk-srcbtn:hover { background: var(--hover); color: var(--text); }
.spk-srcbtn b { font-weight: var(--fw-label); line-height: 1; display: grid; place-items: center; height: 14px; }
.spk-drop {
  font-size: var(--fs-micro); color: var(--dim); text-align: center;
  padding: var(--sp-1) 0 var(--sp-2); border-top: 1px dashed var(--line); margin: 0 var(--sp-3);
}
.spk-foot {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--sp-2) var(--sp-3); border-top: 1px solid var(--line-soft);
  font-size: var(--fs-micro); color: var(--dim);
}
.spk-more { color: var(--accent); font-size: var(--fs-badge); }
