/* =============================================================================
   app.css — all styling for DXcompanion (single stylesheet, no preprocessor).

   Dark teal theme. Every colour/size token lives in the :root block below, so
   the palette can be retuned in one place. The rest of the file is grouped by
   region with dashed banner comments, roughly top-to-bottom of the UI:
     app bar -> buttons/selects -> pickers -> connection status -> layout ->
     groups & controls -> segmented switch / on-off switch -> Voice/Algo boxes ->
     operators -> modal -> toast.

   Layout note: the editor has a fixed minimum width and the page scrolls rather
   than reflowing, so the four operator columns always stay side by side.
   ============================================================================= */
:root {
  --bg: #14181c;
  --panel: #1b2127;
  --panel2: #222a31;
  --line: #313b44;
  --text: #dfe7ec;
  --muted: #8b98a3;
  --accent: #5ad1c5;
  --accent-dim: #1f6f66;
  --danger: #e06a6a;
  --ok: #6fd08c;
  --mono: "Cascadia Mono", "Consolas", ui-monospace, "DejaVu Sans Mono", monospace;
}
* { box-sizing: border-box; }
html { color-scheme: dark; }
html, body { height: 100%; }

/* ---- scrollbars (dark theme) ---- */
* {
  scrollbar-width: thin;                       /* Firefox */
  scrollbar-color: var(--line) var(--bg);
}
*::-webkit-scrollbar { width: 12px; height: 12px; }       /* Chrome / Edge */
*::-webkit-scrollbar-track { background: var(--bg); }
*::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: 6px;
  border: 3px solid var(--bg);
}
*::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); }
*::-webkit-scrollbar-corner { background: var(--bg); }
body {
  margin: 0;
  font: 13px/1.4 "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  display: flex;
  flex-direction: column;
}

/* ---- app bar ---- */
#appbar {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 10px; background: var(--panel); border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
/* Pin the brand to the TOP of the app bar. #appbar centers its items vertically,
   so without this the title re-centers downward once the toolbar grows taller
   (MIDI ports / library lists populate and the connection row wraps onto more
   lines a few ms after load) — making the title visibly drift down on reload. */
.brand-wrap { display: flex; flex-direction: column; line-height: 1.1; align-self: flex-start; }
.brand { font-size: 18px; font-weight: 700; letter-spacing: .5px; }
.brand .word { color: var(--accent); }
.brand-sub { font-size: 11px; color: var(--muted); font-weight: 500; letter-spacing: .3px; }
.brand-sub .rdx { font-size: 12.5px; font-weight: 700; }
.brand-sub .rdx-r { color: var(--accent); }
.brand-sub .rdx-d { color: #fff; }
/* the two "DX" wordmarks are italic (same font as the rest of the brand) */
.dx-script { font-style: italic; }
/* slider/knob style switch under the brand block */
.style-toggle { display: flex; align-items: center; gap: 6px; margin-top: 5px; }
.style-lbl { font-size: 10px; color: var(--muted); letter-spacing: .3px; transition: color .15s; }
.style-lbl.active { color: var(--accent); }
.app-version, .app-copyright { font-size: 10px; color: var(--muted); font-weight: 400; letter-spacing: .3px; }
.toolbar { display: flex; flex-direction: column; flex: 1; align-items: stretch; gap: 5px; }
.tb-row  { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

/* ---- buttons & selects ---- */
.btn {
  background: var(--panel2); color: var(--text); border: 1px solid var(--line);
  border-radius: 6px; padding: 3px 8px; cursor: pointer; font-size: 12px;
}
.btn:hover { border-color: var(--accent); }
.btn.primary { background: var(--accent-dim); border-color: var(--accent); color: #eafffb; }
.btn.off { opacity: .6; }
.btn.hist { font-size: 15px; line-height: 1; padding: 2px 8px; }
.btn:disabled { opacity: .4; cursor: default; }
select, input[type=text], input[type=search], input[type=number] {
  background: var(--panel2); color: var(--text); border: 1px solid var(--line);
  border-radius: 6px; padding: 3px 5px; font-size: 12px;
}
option { padding-block: 1px; }
.inline { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); }
.port-select { max-width: 150px; }
.ch-select { max-width: 64px; }
.slot-select { max-width: 100px; }

/* ---- library / voice pickers (title bar) ---- */
.lib-pickers { display: inline-flex; align-items: center; gap: 7px; }
/* No mono override here: the native dropdown list renders in the system font
   (macOS ignores option font-family), so keep the closed selects in the system
   font too — closed value and open list then match on every platform. */
.lib-select { max-width: 150px; }
.voice-select { max-width: 190px; }

/* ---- connection status ---- */
.conn-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.status { display: inline-flex; align-items: center; gap: 6px; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--muted); }
.status-dot.connected { background: var(--ok); box-shadow: 0 0 6px var(--ok); }
.status-dot.partial { background: #e0b86a; }
.status-dot.not-found { background: var(--danger); }
.status-text { color: var(--muted); }
.conn-msg { font-size: 12px; }
.conn-msg.ok { color: var(--ok); }
.conn-msg.err { color: var(--danger); }
.fileio { display: inline-flex; gap: 8px; }

/* ---- layout ---- */
/* Editor never shrinks below ~1640px; below that the page scrolls horizontally
   and the boxes keep their size. */
#layout { flex: 1; display: flex; min-height: 0; overflow: auto; }
.editor { flex: 1; min-width: 1320px; padding: 8px; }

/* ---- groups & controls ---- */
/* single horizontal strip of global blocks; boxes only as tall as their content */
.globals { display: flex; gap: 6px; flex-wrap: nowrap; align-items: flex-start; margin-bottom: 7px; }
.grp { border: 1px solid var(--line); border-radius: 8px; padding: 3px 5px 4px; background: var(--panel); margin: 0; min-width: 190px; }

/* Voice on top of Algo, stacked vertically; stretch makes them equal width */
.voice-algo-col { display: flex; flex-direction: column; gap: 6px; align-items: stretch; }

/* vertical stack of two boxes (Function+LFO, Effect 1+Effect 2). Wider than a
   lone box so the sliders inside run longer. */
.glob-col { display: flex; flex-direction: column; gap: 5px; min-width: 210px; }
/* boxes share their column height equally so all four line up (min-height:auto
   keeps them from clipping content if a column is short) */
.glob-col .grp { width: 100%; flex: 1 1 0; }
/* Function/LFO and Effect 1/2 columns stretch to a common height */
.glob-pair { display: flex; gap: 6px; align-items: stretch; }
/* Function+LFO column: constant width (does not stretch with the window) */
.func-lfo-col { flex: 0 0 335px; width: 335px; min-width: 335px; }

/* Effect groups: exactly 4 type buttons per row → 2 rows of 4 */
.fx-grp .seg { display: grid; grid-template-columns: repeat(4, auto); }

/* Pitch EG box fits its two slider columns (Levels left, Rates right) */
.peg-box { min-width: 230px; }
.grp legend { color: var(--accent); font-weight: 600; padding: 0 3px; font-size: 11px; display: flex; align-items: center; gap: 5px; width: 100%; font-family: ui-monospace, monospace; }
/* larger titles for the global-row boxes (Voice, Algorithm, Pitch EG, Function, LFO, Effect 1/2) */
.globals legend { font-size: 13px; }
/* per-section RESTORE+INIT cluster sits at the right edge of its legend */
.sec-btns { display: flex; gap: 4px; align-items: center; margin-left: auto; }
.grp-title { color: var(--accent); }
.grid { display: grid; gap: 3px 10px; }
.cols-2 { grid-template-columns: 1fr 1fr; }
.grp.cols-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 10px; }
.grp.cols-2 legend { grid-column: 1 / -1; }

/* per-section INIT button (lives in the legend / op header) */
.init-btn {
  background: var(--panel2); color: var(--muted); border: 1px solid var(--line);
  border-radius: 4px; padding: 1px 6px; font-size: 9.5px; letter-spacing: .5px;
  cursor: pointer; line-height: 1.5; font-family: ui-monospace, monospace;
}
.init-btn:hover { border-color: var(--accent); color: var(--accent); }

/* label on the LEFT of every control; tight spacing to save room */
.ctl { display: flex; flex-direction: row; align-items: center; gap: 3px; margin: 0; font-size: 11px; }
.ctl-label { color: var(--muted); width: 58px; flex-shrink: 0; }
.ctl-row { display: flex; align-items: center; gap: 5px; flex: 1; min-width: 0; }
.ctl-range { flex: 1; accent-color: var(--accent); height: 4px; min-width: 44px; }

/* round knob (alternative to .ctl-range; toggled via the toolbar "Knobs" button) */
.knob { flex: 0 0 auto; width: 30px; height: 30px; cursor: ns-resize; touch-action: none; display: block; }
.knob-svg { width: 100%; height: 100%; display: block; }
.knob-face { fill: #2b3138; stroke: var(--line); stroke-width: 1.5; }
.knob-ind { stroke: var(--accent); stroke-width: 2.5; stroke-linecap: round; }
.knob:hover .knob-face { stroke: var(--accent); }
.knob-bipolar .knob-face { fill: #1f2730; }
/* knob rows: indicator + readout sit left, leaving the row free of stretch */
.ctl-row:has(.knob) { gap: 6px; }

/* ===========================================================================
   Knob mode layout — when #editor carries .knobs-mode. Each box gets a compact,
   bespoke layout: parameter name above every knob, value below. All rules are
   scoped to .knobs-mode; slider mode is completely unaffected.
   =========================================================================== */
/* Plain control sections flow their cells left-to-right and wrap. The Algorithm
   box and the EG boxes (eg-cols / eg-line) own bespoke layouts, so they opt out. */
.knobs-mode .grp:not(.algo-grp):not(:has(.eg-cols)):not(:has(.eg-line)) {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 4px 6px; min-width: 0;
}
/* legend always spans the first line of a box */
.knobs-mode .grp > legend { flex: 0 0 100%; order: -5; }

/* --- knob cell: parameter label on top, knob in middle, value on the bottom --- */
.knobs-mode .ctl:has(.knob):not(.ks-row) {
  flex-direction: column; align-items: center; width: 46px; gap: 0;
}
.knobs-mode .ctl:has(.knob):not(.ks-row) > .ctl-label {
  order: 0; width: auto; max-width: 100%; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-bottom: -2px;
}
.knobs-mode .ctl:has(.knob):not(.ks-row) .ctl-row {
  order: 1; flex: none; flex-direction: column; align-items: center; gap: 0;
}
.knobs-mode .ctl:has(.knob):not(.ks-row) .knob { order: 0; }
.knobs-mode .ctl:has(.knob):not(.ks-row) .ctl-out { order: 1; width: auto; margin-top: -2px; }

/* --- segmented (no-knob) cell: label on top, buttons below --- */
.knobs-mode .grp:not(.algo-grp) .ctl:not(:has(.knob)):has(.seg) {
  flex-direction: column; align-items: center; width: auto; gap: 1px;
}
.knobs-mode .grp:not(.algo-grp) .ctl:not(:has(.knob)):has(.seg) > .ctl-label { width: auto; }

/* --- Operator EG: all eight knobs (L1 R1 L2 R2 …) grouped tightly on one line,
   so the whole operator box stays narrow. --- */
.eg-line { display: flex; align-items: flex-start; justify-content: center; gap: 0; }
.knobs-mode .eg-line .ctl { width: auto; }
.knobs-mode .eg-line .knob { width: 26px; height: 26px; }
.knobs-mode .eg-line .ctl-label,
.knobs-mode .eg-line .ctl-out { font-size: 10px; }

/* --- Pitch EG: Levels 1-4 on the first line, Rates 1-4 on the second (2×4),
   knobs grouped tightly so the box stays narrow. --- */
.knobs-mode .eg-cols { grid-template-columns: 1fr; gap: 2px; }
.knobs-mode .eg-col { flex-direction: row; flex-wrap: nowrap; justify-content: center; gap: 2px; }
.knobs-mode .eg-col .ctl { width: auto; }
.knobs-mode .eg-col .knob { width: 26px; height: 26px; }

/* --- Frequency: Mode (Ratio/Fixed) stacked vertically beside the knobs --- */
.knobs-mode .freq-grp .ctl:not(:has(.knob)):has(.seg) .seg { flex-direction: column; }
/* Ratio value between Coarse and Fine knobs; Hz value below Fixed knob (knob mode only) */
.freq-knob-val { display: none; }
.knobs-mode .freq-knob-val {
  display: flex; align-items: flex-end; justify-content: center; align-self: flex-end;
  width: 0; overflow: visible; margin-top: -2px;
  color: var(--accent); font-variant-numeric: tabular-nums; font-size: 10.5px; white-space: nowrap;
}
.knobs-mode .freq-grp .freq-val { display: none; }
.freq-fixed-out { display: none; }
.knobs-mode .freq-fixed-out {
  order: 1; display: block; margin-top: -2px;
  color: var(--accent); font-variant-numeric: tabular-nums; font-size: 10.5px; white-space: nowrap;
}

/* --- Output: FB Type buttons stacked vertically on the left, then the knobs --- */
.knobs-mode .out-grp .ctl:not(:has(.knob)):has(.seg) { order: -1; }
.knobs-mode .out-grp .ctl:not(:has(.knob)):has(.seg) .seg { flex-direction: column; }

/* --- Key Scaling: Rate knob, then each Level knob + its LIN/EXP buttons --- */
.knobs-mode .ks-grp .ks-row { flex-direction: column; align-items: center; width: auto; gap: 0; }
.knobs-mode .ks-grp .ks-row > .ctl-label { width: auto; margin-bottom: -2px; }
.knobs-mode .ks-grp .ks-row > .ks-knobrow { display: flex; align-items: flex-start; gap: 3px; }
.knobs-mode .ks-grp .ks-row > .ctl-out { width: auto; margin-top: -2px; }
.knobs-mode .ks-grp .ks-knobrow .seg { flex-direction: column; gap: 2px; }
/* Level knobs render identically to the Rate Sc. knob: same size, and the same
   face (drop the bipolar darker tint here so they don't look smaller/recessed). */
.knobs-mode .ks-grp .knob,
.knobs-mode .ks-grp .ks-knobrow .knob { width: 30px; height: 30px; }
.knobs-mode .ks-grp .knob-bipolar .knob-face { fill: #2b3138; }

/* --- Modulation: AMD + Vel Sens knobs, then PMD / PEG switches stacked and
   vertically aligned (fixed label column so the switches line up). --- */
.knobs-mode .mod-grp .sw-row { flex-direction: column; gap: 4px; margin-top: 0; align-items: flex-start; }
.knobs-mode .mod-grp .sw-cell { display: grid; grid-template-columns: 34px auto; align-items: center; }
.knobs-mode .mod-grp .sw-cell .ctl-label { width: auto; }

/* --- Function / LFO / Effect 1/2 split into a knob group and a button group
   that sit side by side (the split is built in JS, .kn-split). Knobs always sit
   next to each other horizontally; each box orders/arranges its groups its own
   way (see per-box rules below). Knob labels wrap rather than crop. --- */
.kn-split { display: flex; gap: 10px; align-items: flex-start; width: 100%; }
.knobs-mode .kn-knobs { display: flex; flex-direction: row; gap: 4px; flex: 0 0 auto; align-items: flex-start; }
.knobs-mode .kn-knobs .ctl:has(.knob) { width: 44px; }
.knobs-mode .kn-knobs .ctl:has(.knob) > .ctl-label {
  white-space: normal; overflow: visible; overflow-wrap: anywhere;
  font-size: 10px; line-height: 1.15;
}
/* Function knobs sit a bit wider apart so Transpose/Portamento/Pitchbend fit on
   one line without wrapping */
.knobs-mode .func-grp .kn-knobs .ctl:has(.knob) { width: 64px; }
.knobs-mode .func-grp .kn-knobs .ctl:has(.knob) > .ctl-label { white-space: nowrap; }
.knobs-mode .kn-btns { display: flex; flex: 0 0 auto; }
.knobs-mode .kn-btns .ctl > .ctl-label { text-align: center; }

/* Effects: the 8 buttons as a 2×4 block on the LEFT, the 2 knobs to the right */
.knobs-mode .fx-grp .kn-btns { order: 0; }
.knobs-mode .fx-grp .kn-knobs { order: 1; }
.knobs-mode .fx-grp .kn-btns .seg { display: grid; grid-template-columns: repeat(4, auto); gap: 3px; }

/* LFO: the 3 knobs on the LEFT, the 7 buttons (4 over 3) to the right */
.knobs-mode .lfo-grp .kn-knobs { order: 0; }
.knobs-mode .lfo-grp .kn-btns { order: 1; }
.knobs-mode .lfo-grp .kn-btns .seg { display: grid; grid-template-columns: repeat(4, auto); gap: 3px; }

/* Function: the 3 buttons stacked on the LEFT, the 3 knobs to the right */
.knobs-mode .func-grp .kn-btns { order: 0; }
.knobs-mode .func-grp .kn-knobs { order: 1; }
.knobs-mode .func-grp .kn-btns .seg { display: flex; flex-direction: column; gap: 3px; align-items: stretch; }

/* Box sizing: wide enough that each box's knob group + button group sit side by
   side without overflowing. Function and LFO share the same column so they stay
   identical in size. */
.knobs-mode.editor { min-width: 0; }
.knobs-mode .operators { grid-template-columns: repeat(4, 260px); }
.knobs-mode .func-lfo-col { flex: 0 0 auto; width: auto; }
.knobs-mode .glob-col { min-width: 285px; }
.knobs-mode .glob-col.func-lfo-col { min-width: 320px; }
/* Pitch EG holds all eight knobs on one line, like the operator EG: snug box and
   knob-width cells so the eight knobs sit as close together as in the op EG */
.knobs-mode .peg-box { min-width: 228px; }
.knobs-mode .peg-box .eg-line .ctl { width: 26px; }
.ctl-out { width: 28px; text-align: right; font-variant-numeric: tabular-nums; color: var(--accent); }
.ctl-value { flex: 1; font-variant-numeric: tabular-nums; color: var(--accent); }
.ctl-check { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }

/* segmented selector switch (FB Type, Mode, Curve, Poly/Mono, LFO Wave, FX type) */
.seg { display: inline-flex; flex-wrap: wrap; gap: 2px; }
.seg-btn {
  background: var(--panel2); color: var(--muted); border: 1px solid var(--line);
  border-radius: 4px; padding: 1px 5px; cursor: pointer; font-size: 10.5px; line-height: 1.4;
  font-family: ui-monospace, monospace; text-transform: uppercase;
}
.seg-btn:hover { border-color: var(--accent); }
.seg-btn.active { background: var(--accent-dim); color: #eafffb; border-color: var(--accent); }

/* on/off toggle switch */
.switch { position: relative; width: 34px; height: 18px; border-radius: 9px; border: 1px solid var(--line); background: #2b3138; cursor: pointer; padding: 0; transition: background .15s, border-color .15s; flex-shrink: 0; }
.switch-knob { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: #8b98a3; transition: transform .15s, background .15s; }
.switch.on { background: var(--accent-dim); border-color: var(--accent); }
.switch.on .switch-knob { transform: translateX(16px); background: #eafffb; }

/* inline frequency value shown after the Ratio/Fixed mode buttons */
.freq-val { color: var(--accent); font-variant-numeric: tabular-nums; font-size: 10.5px; margin-left: 5px; white-space: nowrap; }

/* ---- Voice + Algo boxes ---- */
.voice-box { min-width: 0; }
.name-field { display: flex; flex-direction: column; gap: 3px; }
/* name is capped at 10 chars — size the field to fit just that */
.voice-name-input { font-family: var(--mono); font-size: 16px; letter-spacing: 1px; width: 14ch; }
.algo-grp { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 164px; }
.algo-grp legend { align-self: flex-start; }
.algo-img { width: 112px; height: auto; image-rendering: auto; }
/* Algorithm 1..12 as a centered grid of selector buttons (no inline label) */
.algo-grp .ctl { width: 100%; flex-direction: column; align-items: stretch; gap: 4px; }
.algo-grp .ctl > .ctl-label { display: none; }
.algo-grp .seg { display: grid; grid-template-columns: repeat(6, 1fr); gap: 3px; }
.algo-grp .seg-btn { text-align: center; padding: 3px 0; }

/* ---- operators ---- */
/* Always four across; boxes never shrink below 360px (page scrolls instead). */
.operators { display: grid; grid-template-columns: repeat(4, 320px); gap: 8px; justify-content: start; }
.op { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 7px; }
/* OP header: title left, on/off switch centered, INIT right */
.op-head { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; margin-bottom: 3px; }
.op-head h3 { margin: 0; font-size: 14px; color: var(--accent); justify-self: start; font-family: ui-monospace, monospace; }
.op-head .switch { justify-self: center; }
.op-head .sec-btns { justify-self: end; margin-left: 0; }
.eg-canvas { width: 100%; height: 70px; background: #0f1316; border: 1px solid var(--line); border-radius: 6px; display: block; margin-bottom: 3px; touch-action: none; }
.op .grp { margin-top: 3px; background: var(--panel2); min-width: 0; }

/* Key Scaling: bipolar level slider + inline LIN/EXP toggle on one row */
.ks-row .ctl-out { width: 32px; }
.ks-row .seg { flex: 0 0 auto; margin-left: 4px; }

/* disabled sections: visibly inert but still interactive (no pointer-events:none) */
.op.op-off > :not(.op-head) { opacity: .42; filter: grayscale(.7); }
.grp.fx-off > :not(legend) { opacity: .5; filter: grayscale(.7); }

/* EG: Levels 1-4 on the left column, Rates 1-4 on the right column */
.eg-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 10px; }
.eg-col { display: flex; flex-direction: column; min-width: 0; }
.eg-col .ctl { gap: 1px; }
.eg-col .ctl-label { width: 18px; }
.eg-col .ctl-row { gap: 2px; }
.eg-col .ctl-range { min-width: 24px; max-width: 80%; }
.eg-col .knob { width: 24px; height: 24px; }
.eg-col .ctl-out { width: 22px; }

/* Modulation PMD / PEG switches, side by side */
.sw-row { display: flex; gap: 12px; margin-top: 2px; }
.sw-cell { display: flex; align-items: center; gap: 6px; font-size: 11px; }
.sw-cell .ctl-label { width: auto; }

/* ---- modal ---- */
.modal-back { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; z-index: 50; }
.modal { background: var(--panel); border: 1px solid var(--accent-dim); border-radius: 12px; padding: 22px 24px; min-width: 300px; max-width: 460px; display: flex; flex-direction: column; gap: 12px; box-shadow: 0 8px 32px rgba(0,0,0,.6); }
.modal h2 { margin: 0; font-size: 15px; color: var(--text); }
.modal p  { margin: 0; font-size: 13px; color: var(--muted); }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.modal-actions .btn { padding: 5px 16px; font-size: 13px; }
.btn.danger { background: #3d1a1a; border-color: var(--danger); color: #ffc8c8; }
.btn.danger:hover { background: #521f1f; border-color: #f08080; }

/* ---- toast ---- */
.toast-host { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; gap: 6px; z-index: 60; }
.toast { background: var(--panel2); border: 1px solid var(--line); border-radius: 8px; padding: 8px 14px; opacity: 0; transform: translateY(8px); transition: .25s; font-size: 12px; }
.toast.show { opacity: 1; transform: none; }
.toast.ok { border-color: var(--accent-dim); }
.toast.err { border-color: var(--danger); color: #ffd9d9; }
.muted { color: var(--muted); }
