/* ═══════════════════════════════════════════
   DAW UI  —  flat dark design
   ═══════════════════════════════════════════ */

/* ── Reset ── */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; background: #0a0d10; overflow: hidden; }
body { font-family: var(--font-sans); color: var(--fg); }
button { font-family: inherit; }
input, textarea { font-family: inherit; }

/* ── Root ── */
.daw {
  /* The rail's width, as a variable: the topbar's logo cell is sized from the
     same number, so the divider under the logo is the one the rail already
     draws, and the bar proper starts where the library does. */
  --rail-w: 66px;
  width: 100vw;
  height: 100vh;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.4;
  letter-spacing: -0.005em;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* reset any old .app grid */
  grid-template-columns: none !important;
  grid-template-rows: none !important;
}

/* ── Utilities ── */
.daw .uplabel {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.daw .num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.daw .hidden { display: none !important; }

/* ═══════════════════════════════════
   TOPBAR
   ═══════════════════════════════════ */
.daw-topbar {
  /* Was a flat 77px. The panel-toggle row under the composer needs a second
     line, and auto height means a translation that wraps grows the bar rather
     than being clipped. */
  min-height: 77px;
  flex-shrink: 0;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  /* One shorthand, not a shorthand after two longhands. padding-top and
     padding-bottom were set above and then reset to zero by `padding: 0 16px`
     a few lines later, so the bar had no vertical padding at all whatever the
     comment above claimed. Invisible while the composer was a fixed 50px
     inside a 77px bar, because align-items: center hid the gap; give the
     composer a second row and the box fills the bar edge to edge and touches
     the top of the window. */
  /* No left padding and no vertical padding of its own any more: the brand
     cell owns the left edge and runs the full height of the bar, so the
     vertical padding moved onto the composer stack beside it. */
  padding: 0 16px 0 0;
  gap: 14px;
}
.daw-topbar > .daw-composer-stack { padding: 10px 0; }

/* Logo cell.
 *
 * The top-left corner, exactly as wide as the rail below it and closed by the
 * same border, so the bar proper begins where the library does. It was the
 * full 390px of the library at first, which put 330px of empty bar beside the
 * logo and cost the composer the width it was short of. The rail's width is
 * the part that has something in it, and it does not move when the library
 * collapses, so the cell needs no transition of its own. */
.daw-brand {
  align-self: stretch;
  box-sizing: border-box;
  width: var(--rail-w);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  border-right: 1px solid var(--border);
  overflow: hidden;
}
/* Lined up with the rail buttons below it, which means lined up by the bars,
   not by the file. The symbol's artboard is not centred on its own artwork:
   the bars run x110-x452 of a 512 box, so their middle sits 25 units right of
   the middle of the image. Centring the image would therefore push the bars
   2.5px right of the rail's centre line, which is exactly what it looked like.
   4px is what puts the bars' middle on 32.5px, the centre of the 65px cell.
   52px, not the 40px of a rail button: that same padding means the bars
   themselves land at about 35px. */
/* The logo is the link, so the anchor has to be the flex item the cell
   centres, not an inline box that would add a line box's worth of height
   around a 52px image. */
.daw-brand a {
  display: flex;
  align-items: center;
  text-decoration: none;
}
.daw-brand img {
  width: 52px;
  height: 52px;
  margin-left: 4px;
  margin-right: auto;
  display: block;
  flex-shrink: 0;
}

/* ── Composer pill ── */
.daw-composer {
  flex: 1;
  min-width: 0;
  height: 50px;
  display: flex;
  align-items: stretch;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.02), 0 1px 8px rgba(0,0,0,0.3);
  overflow: hidden;
}

/* URL zone */
.daw-url-zone {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  flex: 1;
  min-width: 0;
  cursor: text;
}
/* Hide decorative SVG icons in the topbar */
.daw-url-zone > svg { display: none; }
.daw-process-btn > svg { display: none; }
.daw-url-zone svg { flex-shrink: 0; }

/* Keyed off the id, not the type. This input is type="text" so a search
   query is not marked invalid; an attribute selector here silently drops
   every rule the moment that changes. */
.daw-url-zone #url {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  outline: none;
  color: var(--fg-2);
  font-size: 14px;
  font-family: inherit;
  letter-spacing: -0.005em;
}
.daw-url-zone #url::placeholder { color: var(--muted); }

/* Upload button inside URL zone */
.daw-upload-btn {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--muted);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.daw-upload-btn:hover { background: var(--panel-2); color: var(--fg-2); border-color: var(--border-strong); }

/* File pill (shown when file selected) */
.file-pill {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--panel-2);
  color: var(--fg-2);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
}
.file-pill.hidden { display: none !important; }
/* The loaded-track pill reports state rather than staging input, so it reads a
   step quieter than the file pill it borrows its shape from. */
.track-pill { opacity: 0.82; }
/* A staged file is what the button will act on next, so it speaks for itself
   and the loaded track stands down. Keyed off the same has-file class the file
   pill uses, so clearing the file brings this back without any JS agreeing to
   it. */
.url-wrap.has-file .track-pill { display: none !important; }
.track-pill .file-name { max-width: 220px; }
.file-name { overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
.file-size { color: var(--muted); flex-shrink: 0; }
.file-clear {
  width: 16px; height: 16px;
  background: none; border: none;
  color: var(--muted); cursor: pointer;
  font-size: 14px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border-radius: 3px; flex-shrink: 0; padding: 0;
}
.file-clear:hover { color: var(--fg); background: var(--panel-3); }

/* url-wrap alias (JS uses .url-wrap for drag events) */
.daw-url-zone { position: relative; }
.daw-url-zone.drag-over {
  background: rgba(244,183,64,0.06);
  box-shadow: inset 0 0 0 1px rgba(244,183,64,0.3);
}
.daw-url-zone.has-file #url { display: none; }

/* Why a dropped file was refused, said in the box it was aimed at.
   The full error panel is for a job that failed: it takes a region and carries
   a retry button, and a file declined before anything started has nothing to
   retry. Sits where the input text does, and hides it, so the box says one
   thing at a time (#584). */
.url-drop-error {
  flex: 1;
  min-width: 0;
  color: var(--danger);
  font-size: 13px;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A plain class, toggled from JS alongside the message.
   `:has()` reads better here, and a sibling combinator cannot work at all
   because the message follows the input in the markup. But this ships in three
   webviews, and the oldest of them is the one that matters: WebKitGTK only
   grew :has() in 2.38, and Linux is where this bug was reported from. A class
   costs one line in the handler and works in every engine. */
.daw-url-zone.has-drop-error #url { display: none; }

/* Composer vertical divider */
.daw-composer-sep {
  width: 1px;
  background: var(--border);
  align-self: stretch;
  margin: 6px 0;
  flex-shrink: 0;
}

/* Stem section */
.daw-stem-section {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  flex-shrink: 0;
}
.daw-extract-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.daw-stem-chips { display: flex; gap: 4px; }

/* Stem choice buttons */
.stem-choice {
  height: 30px;
  padding: 0 11px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: none;
  color: var(--muted-2);
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: -0.005em;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.stem-choice[aria-pressed="true"] {
  border-color: color-mix(in srgb, var(--color) 60%, transparent);
  background: color-mix(in srgb, var(--color) 14%, transparent);
  color: var(--color);
}
/* Automatic song-structure detection. It sits at the end of the composer next
   to Split stems and matches its footprint, so the pair reads as two segments
   of one bar rather than a chip loose beside a button. Colour still separates
   them: the timeline blue, never the amber the primary action owns. */
.structure-toggle {
  --color: #4a7fff;
  min-width: var(--composer-action-w);
  height: auto;
  align-self: stretch;
  margin-left: 0;
  justify-content: center;
  border-radius: 0;
  border: none;
  border-left: 1px solid var(--border-strong);
  flex-direction: column;
  gap: 2px;
  line-height: 1.15;
}
.structure-toggle-main { display: inline-flex; align-items: center; gap: 6px; }
/* Says what the feature is, quietly and where the choice is actually made,
   rather than as a badge on the results after the fact. */
.structure-toggle-note {
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.62;
}

/* "All" toggle */
.stem-choice-all {
  border-color: var(--border-strong);
  color: var(--fg-2);
  font-size: 12px;
}
.stem-choice-all[aria-pressed="true"] {
  border-color: var(--accent);
  background: rgba(244,183,64,0.12);
  color: var(--accent);
}

/* Vocals: Combined / Lead + Backing (on-demand split, #275) */
.vocal-mode-toggle {
  display: inline-flex;
  height: 30px;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
.vocal-mode-btn {
  height: 100%;
  padding: 0 10px;
  border: none;
  background: none;
  color: var(--muted-2);
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.vocal-mode-btn + .vocal-mode-btn { border-left: 1px solid var(--border); }
.vocal-mode-btn[aria-pressed="true"] {
  background: color-mix(in srgb, var(--vocals) 16%, transparent);
  color: var(--vocals);
}

/* Process button */
.daw-process-btn {
  height: auto;
  /* Shared with .structure-toggle so the two end segments are the same width.
     A min rather than a fixed width, because a longer translation must be
     allowed to grow instead of being clipped. */
  min-width: var(--composer-action-w);
  justify-content: center;
  padding: 0 20px;
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  border: none;
  border-left: 1px solid #b97f1c;
  color: #1a1206;
  font-weight: 600;
  font-size: 14px;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.22);
  flex-shrink: 0;
  transition: background var(--t-fast);
}
.daw-process-btn:hover { background: linear-gradient(180deg, #f8c054, #e0a032); }
.daw-process-btn:active { transform: translateY(1px); }
.daw-process-btn:disabled {
  opacity: 0.8;
  cursor: progress;
  filter: saturate(0.85);
}

/* Loading spinner on process btn */
.daw-process-btn.loading > svg { display: none; }
.daw-process-btn.loading { filter: saturate(0.85); }
.daw-process-btn.loading::before {
  content: "";
  width: 14px; height: 14px; flex: 0 0 14px;
  border-radius: 50%;
  background: conic-gradient(currentColor 0deg 270deg, transparent 270deg 360deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 0);
  mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 0);
  animation: daw-spin 0.85s linear infinite;
}
@keyframes daw-spin { to { transform: rotate(360deg); } }

/* Icon button */
.daw-iconbtn {
  width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  color: var(--fg-2);
  border-radius: 8px;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
  flex-shrink: 0;
}
.daw-iconbtn:hover { background: var(--panel-2); border-color: var(--border); color: var(--fg); }
.daw-iconbtn:active { transform: scale(0.96); }

/* Notification panel */
/* The wrapper is still the badge's anchor; the panel is not anchored to it.
   .sidebar is overflow:hidden and narrows to the rail's 66px when the library
   is collapsed, so an absolutely positioned panel would be clipped to nothing
   in that state. Fixed escapes the clip, and placeNotifPanel in ui-chrome.js
   sets left/top from the bell's rect, the same way the search dropdown and the
   metronome popover are placed. No coordinates here on purpose: the only code
   that shows this panel is the code that positions it. */
.daw-notif-wrap { position: relative; }
.daw-notif-panel {
  display: none;
  position: fixed;
  width: 280px;
  background: var(--panel-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
  z-index: 200;
  overflow: hidden;
}
.daw-notif-wrap.open .daw-notif-panel { display: block; }
.daw-notif-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--border);
}
.daw-notif-close {
  width: 22px; height: 22px;
  background: none; border: none;
  color: var(--muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  border-radius: 5px;
}
.daw-notif-close:hover { background: var(--panel-3); color: var(--fg); }
/* Sits between the label and the close button; the header is space-between, so
   push it right and leave a gap before the X. */
.daw-notif-clear {
  margin-left: auto; margin-right: 8px;
  background: none; border: none; padding: 0;
  color: var(--muted); cursor: pointer;
  font-family: inherit; font-size: 11px;
}
.daw-notif-clear:hover { color: var(--fg); text-decoration: underline; }
.daw-notif-list { padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.daw-notif-card {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.daw-notif-release { border-color: rgba(244,183,64,0.25); }
.daw-notif-card-icon {
  width: 28px; height: 28px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(244,183,64,0.12);
  border-radius: 6px;
  color: var(--accent);
}
/* A failure, not an announcement. The amber tint lives in the base icon rule
   rather than in .daw-notif-release, so both properties need overriding. */
.daw-notif-error { border-color: rgba(214,90,74,0.35); cursor: pointer; }
.daw-notif-error:hover { background: var(--panel-2); }
.daw-notif-error .daw-notif-card-icon {
  background: rgba(214,90,74,0.14);
  color: var(--danger);
}
.daw-notif-error:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.daw-notif-card-body { flex: 1; min-width: 0; }
.daw-notif-card-title { font-size: 12px; font-weight: 600; color: var(--fg); }
.daw-notif-card-desc { font-size: 11px; color: var(--muted); margin-top: 2px; }
.daw-notif-badge {
  position: absolute; top: 4px; right: 4px;
  width: 7px; height: 7px;
  background: #f4b740; border-radius: 50%;
  pointer-events: none;
}
.daw-notif-dismiss {
  flex-shrink: 0; align-self: flex-start;
  width: 18px; height: 18px;
  background: none; border: none; cursor: pointer;
  color: var(--muted); display: flex; align-items: center; justify-content: center;
  border-radius: 4px; padding: 0;
}
.daw-notif-dismiss:hover { background: var(--panel-3); color: var(--fg); }
.daw-notif-empty {
  font-size: 12px; color: var(--muted);
  text-align: center; padding: 16px 0; margin: 0;
}

/* Hidden collapsed strip (JS compat, not shown in new design) */
.appbar-icon-strip.hidden { display: none !important; }

/* ═══════════════════════════════════
   BODY LAYOUT
   ═══════════════════════════════════ */
.daw-body {
  flex: 1;
  display: flex;
  min-height: 0;
}

/* ═══════════════════════════════════
   SIDEBAR / CATALOG
   ═══════════════════════════════════ */
.sidebar {
  width: 390px;
  flex-shrink: 0;
  background: var(--bg-2);
  border-right: 1px solid var(--border);
  display: flex;
  overflow: hidden;
  transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Rail */
.sidebar-rail {
  width: var(--rail-w);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 0;
  gap: 4px;
  flex-shrink: 0;
}

.rail-btn {
  width: 40px; height: 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border-radius: 8px;
  color: var(--muted);
  font-size: 9px;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
  border: none;
  background: none;
  /* remove old pseudo-element strip */
}
.rail-btn::before { display: none !important; }
.rail-btn:hover { background: var(--panel); color: var(--fg-2); }
.rail-btn.active { color: var(--accent); background: var(--panel); }
.rail-btn svg { flex-shrink: 0; }
/* max-width + ellipsis: a translated label (e.g. Polish "Ustawienia") can run
   longer than English at this 9px size inside the fixed 40px button. The
   full text is still reachable via the button's title/aria-label; this just
   guarantees the label itself can never visually overflow the rail column. */
.rail-btn > span { white-space: nowrap; max-width: 56px; overflow: hidden; text-overflow: ellipsis; }

/* Queue rail button. Only present while something is importing, so the rail
   stays as it is today for anyone not using the queue. */
.rail-queue { position: relative; }
.rail-badge {
  position: absolute; top: 2px; right: 2px;
  min-width: 15px; height: 15px; padding: 0 4px;
  border-radius: 8px;
  background: var(--accent); color: #1a1a1a;
  font-size: 9px; font-weight: 700; line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
/* "We Recommend" is wider than the other one-word rail labels, so it stacks onto
   two centered lines; let the button grow so the second line + icon aren't clipped. */
.rail-btn.rail-recommend { height: auto; min-height: 40px; padding: 3px 0; }
.rail-btn.rail-recommend span { white-space: normal; text-align: center; line-height: 1.1; }

/* Sidebar body */
.sidebar-body {
  flex: 1;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  overflow: hidden;
  opacity: 1;
  transition: opacity 0.15s ease;
}

/* Search */
.daw-search {
  height: 32px;
  border: 1px solid var(--border);
  background: var(--panel);
  border-radius: 8px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  color: var(--muted);
  font-size: 12px;
  flex-shrink: 0;
  position: relative;
}
.daw-search svg { flex-shrink: 0; }
.daw-search input {
  flex: 1; min-width: 0;
  border: none; outline: none;
  background: transparent;
  color: var(--fg);
  font: inherit;
}
.daw-search input::placeholder { color: var(--muted); }
.search-kbd {
  font-size: 10px;
  color: var(--muted-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 5px;
  flex-shrink: 0;
}

/* Lib header row */
.daw-lib-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 4px;
  min-height: 22px;
  flex-shrink: 0;
}
.daw-lib-label {
  display: flex;
  align-items: center;
  gap: 4px;
}
.daw-lib-count { color: var(--muted-2); font-weight: 500; margin-left: 4px; }
.new-folder-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px 3px 7px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--muted);
  font-size: 11px; font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.new-folder-btn:hover {
  background: var(--panel);
  color: var(--fg-2);
  border-color: var(--border-2, rgba(255,255,255,0.15));
}

/* Lib list (JS populates with .lib-item, .folder, .cat-item) */
.daw-lib-list {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Thin auto-hide scrollbar shared by the library list, the mixer column, the
   footer's control row and the waveform panel (both axes) — hidden until the
   area is hovered/focused, reserves no visible gutter. `.daw .wave-scroll`
   overrides the gold bar from waves.css. */
.daw-lib-list,
.mixer-column,
.footer-row-controls,
.daw .wave-scroll {
  /* Firefox: thin overlay-style bar, fades to transparent track */
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
.daw-lib-list:hover,
.daw-lib-list:focus-within,
.mixer-column:hover,
.mixer-column:focus-within,
.footer-row-controls:hover,
.footer-row-controls:focus-within,
.daw .wave-scroll:hover,
.daw .wave-scroll:focus-within {
  scrollbar-color: rgba(148, 163, 184, 0.4) transparent;
}
/* WebKit (Chrome/Safari/WKWebView): thin thumb, hidden until hover/scroll */
.daw-lib-list::-webkit-scrollbar,
.mixer-column::-webkit-scrollbar,
.footer-row-controls::-webkit-scrollbar,
.daw .wave-scroll::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
.daw-lib-list::-webkit-scrollbar-track,
.mixer-column::-webkit-scrollbar-track,
.footer-row-controls::-webkit-scrollbar-track,
.daw .wave-scroll::-webkit-scrollbar-track {
  background: transparent;
}
.daw-lib-list::-webkit-scrollbar-thumb,
.mixer-column::-webkit-scrollbar-thumb,
.footer-row-controls::-webkit-scrollbar-thumb,
.daw .wave-scroll::-webkit-scrollbar-thumb {
  background: transparent;
  border: 2px solid transparent;
  background-clip: padding-box;
  border-radius: 999px;
}
.daw-lib-list:hover::-webkit-scrollbar-thumb,
.daw-lib-list:focus-within::-webkit-scrollbar-thumb,
.mixer-column:hover::-webkit-scrollbar-thumb,
.mixer-column:focus-within::-webkit-scrollbar-thumb,
.footer-row-controls:hover::-webkit-scrollbar-thumb,
.footer-row-controls:focus-within::-webkit-scrollbar-thumb,
.daw .wave-scroll:hover::-webkit-scrollbar-thumb,
.daw .wave-scroll:focus-within::-webkit-scrollbar-thumb {
  background: rgba(148, 163, 184, 0.4);
  background-clip: padding-box;
}
.daw-lib-list::-webkit-scrollbar-thumb:hover,
.mixer-column::-webkit-scrollbar-thumb:hover,
.footer-row-controls::-webkit-scrollbar-thumb:hover,
.daw .wave-scroll::-webkit-scrollbar-thumb:hover {
  background: rgba(148, 163, 184, 0.6);
  background-clip: padding-box;
}

/* Library item */
.lib-item {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 8px;
  border-radius: 8px;
  cursor: pointer;
  position: relative;
  transition: background var(--t-fast);
}
.lib-item:hover { background: var(--panel); }
.lib-item.active {
  background: linear-gradient(90deg, rgba(244,183,64,0.10) 0%, var(--panel-2) 60%);
}
.lib-item.active::before {
  content: '';
  position: absolute; left: -2px; top: 6px; bottom: 6px;
  width: 3px; border-radius: 2px;
  background: var(--accent);
}
.lib-cover {
  width: 36px; height: 36px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 700; letter-spacing: 0.1em;
  color: rgba(255,255,255,0.85);
  flex-shrink: 0;
}
.lib-meta { flex: 1; min-width: 0; }
.lib-meta .t { font-size: 13px; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lib-meta .s { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lib-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }

/* cat-item (legacy catalog items JS creates) */
.cat-item {
  display: flex;
  gap: 9px;
  align-items: center;
  padding: 7px 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast);
}
.cat-item:hover { background: var(--panel-2); border-color: var(--border); }
.cat-item.active { background: var(--panel-2); border-color: rgba(244,183,64,0.3); }
.cat-thumb {
  width: 36px; height: 36px;
  border-radius: 6px; flex-shrink: 0;
  overflow: hidden; border: 1px solid var(--border);
  background: var(--panel);
  display: flex; align-items: center; justify-content: center;
  color: var(--muted);
}
.cat-thumb svg, .cat-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cat-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.cat-title { color: var(--fg); font-size: 12px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cat-sub { color: var(--muted); font-size: 10.5px; display: flex; gap: 5px; white-space: nowrap; overflow: hidden; }
/* Shown only when it has something to say (#636). A finished import is the
   resting state of every row in the library, so a green dot on all of them
   marked nothing out: it was one more thing to read past on the way to the
   title. The states that still carry information keep the dot, which is also
   what makes them visible now that they are the only rows wearing one. */
.cat-status { display: none; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.cat-status.processing { display: block; background: var(--accent); animation: cat-pulse 1.4s infinite; }
.cat-status.unavailable { display: block; background: #666; }
.cat-item.unavailable { cursor: pointer; }
.cat-item.unavailable .cat-title { opacity: 0.55; }
.cat-unavailable-warning { color: var(--accent); font-weight: 600; opacity: 1; }
@keyframes cat-pulse { 0%,100%{opacity:1;} 50%{opacity:0.3;} }

/* Import queue: a waiting row and a running row must be told apart at a
   glance, so they differ in three ways -- wording, the presence of a progress
   bar, and a hollow versus filled status dot. */
.cat-queue-label { color: var(--accent); font-variant-numeric: tabular-nums; }
.cat-item.queue-waiting .cat-queue-label { color: var(--muted); }
/* Waiting rows are not loadable yet, so they read as pending rather than
   inviting a click. */
.cat-item.queue-waiting { cursor: default; }
.cat-item.queue-waiting .cat-thumb { opacity: 0.55; }
.cat-item.queue-waiting .cat-title { color: var(--muted); }
.cat-item.queue-waiting .cat-status {
  /* Explicit, not inherited from .processing: a waiting row is only styled
     through its parent, so it must turn the dot on for itself. */
  display: block;
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--muted);
  animation: none;
}
.cat-progress {
  height: 3px; border-radius: 2px; margin-top: 4px;
  background: rgba(255, 255, 255, 0.09); overflow: hidden;
}
.cat-progress-fill {
  height: 100%; width: 0; border-radius: 2px;
  background: var(--accent);
  transition: width 260ms linear;
}

/* Queue view rows. Unlike a library row these are not loadable -- there is
   nothing to load yet -- so they get no hover affordance, just a cancel. */
.queue-row { cursor: default; }
.queue-row:hover { background: var(--panel); }
.queue-row .cat-meta { gap: 3px; }
.queue-cancel {
  width: 22px; height: 22px; border: 0; border-radius: 5px;
  background: transparent; color: var(--muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; flex-shrink: 0; opacity: 0.65;
  transition: opacity var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.queue-row:hover .queue-cancel { opacity: 1; }
.queue-cancel:hover { background: rgba(217, 83, 79, 0.16); color: #e06c68; }
.queue-cancel:disabled { opacity: 0.3; cursor: default; }
.queue-section .lib-section-head { margin-bottom: 6px; }

/* Restored-from-last-session banner. Opening the app never starts separating
   on its own, so this is the only way that queue moves. */
.queue-paused-banner {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 8px; padding: 8px 10px;
  border: 1px solid rgba(232, 168, 56, 0.28);
  border-radius: 8px;
  background: rgba(232, 168, 56, 0.08);
}
.queue-paused-text { flex: 1; font-size: 11px; color: var(--fg-2); line-height: 1.4; }
.queue-start-btn {
  flex-shrink: 0; min-height: 26px; padding: 0 12px;
  border-radius: 6px; cursor: pointer;
  border: 1px solid rgba(232, 168, 56, 0.45);
  background: rgba(232, 168, 56, 0.18);
  color: var(--accent);
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
}
.queue-start-btn:hover { background: rgba(232, 168, 56, 0.28); }
.queue-start-btn:disabled { opacity: 0.5; cursor: default; }

/* Reordering. Position in a serial queue is the difference between "ready now"
   and "ready in half an hour", so waiting rows drag. */
.queue-row.queue-waiting { cursor: grab; }
.queue-row.dragging { opacity: 0.4; cursor: grabbing; }
.queue-row.drop-below { box-shadow: 0 2px 0 0 var(--accent); }
.queue-grip {
  display: flex; align-items: center; flex-shrink: 0;
  margin-right: -2px; color: var(--muted); opacity: 0;
  transition: opacity var(--t-fast);
}
.queue-row:hover .queue-grip { opacity: 0.7; }
.queue-top {
  width: 22px; height: 22px; border: 0; border-radius: 5px;
  background: transparent; color: var(--muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; flex-shrink: 0; opacity: 0;
  transition: opacity var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.queue-row:hover .queue-top { opacity: 0.65; }
.queue-top:hover { background: rgba(232, 168, 56, 0.16); color: var(--accent); opacity: 1; }
.queue-top:disabled { opacity: 0.3; cursor: default; }
.cat-del {
  width: 20px; height: 20px; border: 0; border-radius: 5px;
  background: transparent; color: var(--muted); cursor: pointer;
  opacity: 0; display: flex; align-items: center; justify-content: center;
  padding: 0; flex-shrink: 0; transition: opacity var(--t-fast), background var(--t-fast);
}
.cat-item:hover .cat-del { opacity: 1; }
.cat-del:hover { background: rgba(21,31,39,0.8); color: var(--danger); }

/* Folder items (legacy catalog) */
.folder { display: flex; flex-direction: column; }
.folder-head {
  --folder-color: var(--accent);
  display: flex; align-items: center; gap: 7px;
  padding: 6px 8px; border-radius: 7px;
  cursor: pointer; color: var(--muted);
  transition: background var(--t-fast), color var(--t-fast);
  user-select: none;
}
.folder-head:hover { background: var(--panel); color: var(--fg); }
.folder-head .f-chevron { width: 11px; height: 11px; transition: transform 180ms ease; flex-shrink: 0; color: var(--muted); }
.folder.collapsed .folder-head .f-chevron { transform: rotate(-90deg); }
.folder-head .f-icon { width: 13px; height: 13px; flex-shrink: 0; color: var(--folder-color); }
.folder-head .f-name {
  font-size: 12px; font-weight: 500; color: inherit;
  flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: transparent; border: none; padding: 0; font-family: inherit; outline: none; cursor: inherit;
}
.folder-head .f-count { font-size: 10.5px; color: var(--muted); }
.folder-color-dot {
  width: 13px; height: 13px; border: 1px solid rgba(255,255,255,0.22);
  border-radius: 999px; background: var(--folder-color); cursor: pointer; padding: 0;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.22);
}
.folder-head .f-del,
.folder-head .f-subfolder {
  width: 18px; height: 18px; border: none; background: transparent;
  color: var(--muted); cursor: pointer; opacity: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 4px; padding: 0; transition: opacity var(--t-fast), color var(--t-fast);
  flex-shrink: 0;
}
.folder-head:hover .f-del,
.folder-head:hover .f-subfolder { opacity: 1; }
.folder-head .f-del:hover { background: rgba(21,31,39,0.8); color: var(--danger); }
.folder-head .f-subfolder:hover { background: rgba(21,31,39,0.8); color: var(--fg-2); }
/* Drag grip */
.f-grip {
  display: none; align-items: center; justify-content: center;
  cursor: grab; color: var(--muted); flex-shrink: 0;
  padding: 2px; border-radius: 3px;
  transition: color var(--t-fast);
}
.f-grip:active { cursor: grabbing; }
.folder-head:hover .f-grip { display: flex; }
/* Drop indicators for folder reorder / nest */
.folder.drop-before > .folder-head { box-shadow: 0 -2px 0 var(--accent); border-radius: 7px 7px 0 0; }
.folder.drop-after > .folder-head { box-shadow: 0 2px 0 var(--accent); border-radius: 0 0 7px 7px; }
.folder.drop-into > .folder-head { box-shadow: 0 0 0 1px var(--accent); background: var(--panel); }
/* Folder being dragged */
.folder.folder-dragging { opacity: 0.35; pointer-events: none; }
/* Subfolder indentation */
.folder.subfolder { margin-left: 12px; }
.folder-body { display: flex; flex-direction: column; gap: 3px; padding: 3px 0 3px 14px; margin-left: 7px; border-left: 1px solid rgba(157,170,182,0.1); }
.folder.collapsed .folder-body { display: none; }
.folder-empty { color: var(--muted); font-size: 11px; padding: 5px 8px; font-style: italic; }

/* Folder editor modal */
.folder-editor-backdrop {
  position: fixed; inset: 0; z-index: 80;
  display: grid; place-items: center;
  background: rgba(3,8,13,0.42); backdrop-filter: blur(2px);
}
.folder-editor {
  width: min(300px, calc(100vw - 32px));
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(28,39,48,0.98), rgba(13,21,29,0.98));
  box-shadow: var(--shadow-panel);
  padding: 12px; color: var(--fg); font-family: var(--font-mono);
}
.folder-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; font-size: 13px; font-weight: 600; }
.folder-editor-close { width: 24px; height: 24px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--muted); cursor: pointer; display: grid; place-items: center; padding: 0; }
.folder-editor-close:hover { background: rgba(21,31,39,0.8); color: var(--fg); }
.folder-editor-field { display: grid; gap: 7px; margin-bottom: 12px; color: var(--muted); font-size: 10.5px; }
.folder-editor-name { width: 100%; min-height: 34px; border: 1px solid var(--border); border-radius: 7px; background: rgba(10,17,24,0.78); color: var(--fg); font: inherit; font-size: 12px; padding: 0 10px; outline: none; }
.folder-editor-name:focus { border-color: rgba(244,183,64,0.5); box-shadow: 0 0 0 2px rgba(244,183,64,0.12); }
.folder-editor-colors { display: flex; align-items: center; gap: 9px; }
.folder-editor-colors .folder-color-dot { width: 22px; height: 22px; }
.folder-editor-msg { color: var(--danger); font-size: 11px; margin-top: 8px; }
.folder-editor-msg:empty { display: none; }
.folder-editor-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.folder-editor-actions button { min-height: 30px; border-radius: 7px; font-family: var(--font-mono); font-size: 11px; cursor: pointer; padding: 0 11px; }
.folder-editor-cancel { border: 1px solid var(--border); background: rgba(21,31,39,0.52); color: var(--muted); }
.folder-editor-save { border: 1px solid rgba(244,183,64,0.3); background: rgba(244,183,64,0.16); color: var(--accent); }

/* Edit Library modal (Settings → opens this centered window) */
.library-editor-backdrop {
  position: fixed; inset: 0; z-index: 80;
  display: grid; place-items: center;
  background: rgba(3,8,13,0.42); backdrop-filter: blur(2px);
}
.library-editor {
  width: min(560px, calc(100vw - 32px));
  /* Fixed height (not max-height) so the dialog stays one size across all tabs
     -- each pane fills it via flex:1, so switching tabs never resizes it. */
  height: min(70vh, 540px);
  display: flex; flex-direction: column;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(28,39,48,0.98), rgba(13,21,29,0.98));
  box-shadow: var(--shadow-panel);
  padding: 12px; color: var(--fg); font-family: var(--font-mono);
}
.library-editor-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; font-size: 13px; font-weight: 600; }
.library-editor-close { width: 24px; height: 24px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--muted); cursor: pointer; display: grid; place-items: center; padding: 0; }
.library-editor-close:hover { background: rgba(21,31,39,0.8); color: var(--fg); }
.library-editor-table-wrap { flex: 1; min-height: 0; overflow-y: auto; border: 1px solid var(--border); border-radius: 7px; background: rgba(10,17,24,0.5); }
.library-editor-table { width: 100%; border-collapse: collapse; font-size: 11.5px; }
.library-editor-table thead th {
  position: sticky; top: 0;
  text-align: left; font-weight: 600; font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); background: rgba(13,21,29,0.96);
  padding: 8px 10px; border-bottom: 1px solid var(--border);
}
.library-editor-table tbody td { padding: 7px 10px; border-bottom: 1px solid rgba(148,163,184,0.07); color: var(--fg-2); vertical-align: middle; }
.library-editor-table tbody tr:last-child td { border-bottom: none; }
.library-editor-table .le-name { color: var(--fg); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.library-editor-table .le-loc { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.library-editor-table tr.unavailable .le-name { color: var(--danger); }
.le-badge { margin-left: 7px; padding: 1px 6px; border-radius: 5px; font-size: 9px; text-transform: uppercase; letter-spacing: 0.04em; background: rgba(214,90,74,0.16); color: var(--danger); }
.library-editor-empty { color: var(--muted); text-align: center; padding: 22px 10px !important; }
/* Settings → tabs */
.settings-tabs { display: flex; gap: 2px; margin-bottom: 12px; border-bottom: 1px solid var(--border); }
.settings-tab { background: none; border: none; border-bottom: 2px solid transparent; color: var(--muted); font-family: var(--font-mono); font-size: 12px; font-weight: 600; padding: 5px 12px 9px; cursor: pointer; margin-bottom: -1px; }
.settings-tab:hover { color: var(--fg-2); }
.settings-tab.active { color: var(--fg); border-bottom-color: var(--accent); }
/* Every pane fills the fixed dialog body so all tabs are the same size, and
   every pane scrolls inside it.
   Only General used to scroll, which was a bet that no other tab would ever
   grow past the dialog's 540px. Network did, once it gained the secure-origin
   warning and one QR card per network interface: the content simply ran on
   underneath the footer, with the Done button sitting on top of the Port
   setting. A pane that cannot scroll does not clip, it overlaps. */
.settings-pane {
  display: flex; flex-direction: column; min-height: 0; flex: 1;
  overflow-y: auto;
  /* Reserve a gutter for the scrollbar so it never overlaps the right-aligned
     controls (Compute device, Out of sync tracks). padding-right insets the
     content; the equal negative margin lets that gutter sit in the card's own
     12px padding, so scrolling content stays aligned with the fixed
     header/tabs/footer. Handles overlay scrollbars (macOS/WebKit, which draw
     over the padding box) and reserves space for classic scrollbars
     (Windows/Linux). */
  scrollbar-gutter: stable;
  padding-right: 12px; margin-right: -12px;
}
.settings-pane.hidden { display: none; }
.settings-pane[data-pane="general"] .library-editor-table-wrap { flex: none; max-height: 240px; margin-bottom: 2px; }
.settings-empty { color: var(--muted); font-size: 12px; text-align: center; padding: 28px 10px; }
/* flex-shrink: 0 so a tall pane squeezes itself, never the footer. */
.settings-foot {
  display: flex; justify-content: flex-end; flex-shrink: 0;
  margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--border);
}
.settings-done { min-height: 32px; border-radius: 7px; border: 1px solid rgba(244,183,64,0.35); background: rgba(244,183,64,0.16); color: var(--accent); font-family: var(--font-mono); font-size: 12px; font-weight: 600; padding: 0 22px; cursor: pointer; }
.settings-done:hover { background: rgba(244,183,64,0.24); }
/* Right-aligned form controls share a fixed width so they line up down the column. */
.settings-num-input, .settings-select { flex-shrink: 0; width: 84px; background: rgba(10,17,24,0.6); border: 1px solid var(--border-strong); border-radius: 7px; color: var(--fg); font-family: var(--font-mono); font-size: 12px; padding: 6px 9px; }
.settings-num-input { text-align: right; }
.settings-select { cursor: pointer; }
/* Wider variant for options whose text doesn't fit the shared 84px column --
   currently just the language picker (flag + native name, up to "Bahasa Indonesia"). */
.settings-select-wide { width: 190px; }
.settings-num-input:focus, .settings-select:focus { outline: none; border-color: rgba(244,183,64,0.5); }

/* Settings → network access section */
.settings-section { margin-bottom: 12px; }
.settings-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
.settings-row-text { min-width: 0; }
.settings-row-title { font-size: 12.5px; font-weight: 600; color: var(--fg); }
.settings-row-desc { font-size: 10.5px; color: var(--muted); margin-top: 3px; line-height: 1.45; }
.settings-lock-note { font-style: italic; opacity: 0.85; margin-top: 5px; }
.settings-switch { position: relative; flex-shrink: 0; width: 42px; height: 24px; cursor: pointer; }
.settings-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.settings-switch-track { position: absolute; inset: 0; border-radius: 999px; background: rgba(148,163,184,0.22); border: 1px solid var(--border-strong); transition: background 0.15s ease; }
.settings-switch-thumb { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: var(--fg); transition: transform 0.15s ease; }
.settings-switch input:checked + .settings-switch-track { background: rgba(244,183,64,0.55); border-color: rgba(244,183,64,0.5); }
.settings-switch input:checked + .settings-switch-track .settings-switch-thumb { transform: translateX(18px); background: var(--accent); }
.settings-switch.disabled { cursor: default; opacity: 0.7; }
.settings-switch.disabled input { cursor: default; }
/* A row whose setting only applies while another one is on. Dimmed and inert
   rather than removed, so the value stays readable: someone deciding whether to
   switch automatic deletion on can see how long tracks would be kept before
   committing to it, instead of having to turn the destructive setting on to
   find out. Note that `hidden` would not work here anyway -- base.css is not
   loaded by index.html and daw.css scopes its rule to `.daw`, while the
   settings overlay is appended to document.body. */
.settings-row.disabled { opacity: 0.45; }
.settings-row.disabled input { cursor: not-allowed; }
.settings-net { margin-top: 10px; font-size: 11px; color: var(--muted); }
.settings-net.hidden { display: none; }
.settings-net-empty { color: var(--muted); }
/* Transpose-over-the-network warning. Red because the consequence is a control
   that does nothing, or a browser page that says the connection is not
   private: both look like the app is broken, and both are worth reading before
   the user hands the address to their phone. */
.settings-net-warn {
  margin: 8px 0 10px;
  padding: 8px 10px;
  border-left: 3px solid var(--danger);
  border-radius: 4px;
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
  font-size: 11px;
  line-height: 1.5;
}
.settings-net-warn.hidden { display: none; }
.settings-net-qr { display: flex; flex-direction: column; gap: 14px; margin-top: 6px; }
.qr-hint { font-size: 10px; color: var(--muted); margin: 0; line-height: 1.4; }
.qr-cards-row { display: flex; flex-wrap: wrap; gap: 28px; }
.qr-card { display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; }
.qr-img-wrap { width: 130px; height: 130px; border-radius: 6px; border: 3px solid var(--accent); overflow: hidden; box-sizing: border-box; }
.qr-card img { width: 100%; height: 100%; display: block; transition: filter 0.25s ease, scale 0.25s ease; }
.qr-card.qr-blurred img { filter: blur(10px); scale: 1.12; }
.qr-label { font-size: 9.5px; color: var(--muted); text-align: center; max-width: 130px; word-break: break-all; }
.settings-server-note { font-size: 10.5px; color: var(--muted); margin: 0 0 12px; line-height: 1.5; }
.settings-subhead { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 600; margin: 4px 0 7px; }

/* Settings → registry (read-only viewer) */
.settings-registry-refresh { flex-shrink: 0; min-height: 28px; border-radius: 6px; border: 1px solid var(--border-strong); background: rgba(10,17,24,0.6); color: var(--fg-2); font-family: var(--font-mono); font-size: 11px; padding: 0 12px; cursor: pointer; }
.settings-registry-refresh:hover { color: var(--fg); border-color: rgba(148,163,184,0.4); }
.settings-registry-view {
  flex: 1; min-height: 0; margin-top: 10px; resize: none;
  background: rgba(10,17,24,0.5); border: 1px solid var(--border); border-radius: 7px;
  color: var(--fg-2); font-family: var(--font-mono); font-size: 11px; line-height: 1.5;
  padding: 10px 12px; white-space: pre; overflow: auto;
}
.settings-registry-view:focus { outline: none; }

/* Settings → General → danger zone (reset app data) */
.settings-danger-zone { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.settings-danger-subhead { color: var(--danger); }
.settings-reset-btn {
  flex-shrink: 0; min-height: 30px; border-radius: 7px;
  border: 1px solid rgba(214,90,74,0.35); background: rgba(214,90,74,0.12);
  color: var(--danger); font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  padding: 0 12px; cursor: pointer;
}
.settings-reset-btn:hover { background: rgba(214,90,74,0.2); }

/* Reset app data — confirm dialog (nested above the Settings dialog) */
.reset-confirm-backdrop {
  position: fixed; inset: 0; z-index: 150;
  display: grid; place-items: center;
  background: rgba(3,8,13,0.55); backdrop-filter: blur(2px);
}
.reset-confirm-card {
  width: min(340px, calc(100vw - 32px));
  border: 1px solid rgba(214,90,74,0.4);
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(28,39,48,0.98), rgba(13,21,29,0.98));
  box-shadow: var(--shadow-panel);
  padding: 16px; color: var(--fg); font-family: var(--font-mono);
}
.reset-confirm-title { font-size: 13px; font-weight: 700; color: var(--danger); margin-bottom: 8px; }
.reset-confirm-body { font-size: 11.5px; color: var(--fg-2); line-height: 1.5; margin: 0 0 10px; }
.reset-confirm-hint { font-size: 10.5px; color: var(--muted); margin: 0 0 7px; }
.reset-confirm-hint strong { color: var(--fg); letter-spacing: 0.04em; }
.reset-confirm-input {
  width: 100%; min-height: 34px; border: 1px solid var(--border-strong); border-radius: 7px;
  background: rgba(10,17,24,0.78); color: var(--fg); font: inherit; font-size: 12px;
  padding: 0 10px; outline: none; box-sizing: border-box;
}
.reset-confirm-input:focus { border-color: rgba(214,90,74,0.5); box-shadow: 0 0 0 2px rgba(214,90,74,0.14); }
.reset-confirm-msg { color: var(--danger); font-size: 11px; margin-top: 8px; min-height: 1em; }
.reset-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.reset-confirm-actions button { min-height: 30px; border-radius: 7px; font-family: var(--font-mono); font-size: 11px; cursor: pointer; padding: 0 12px; }
.reset-confirm-cancel { border: 1px solid var(--border); background: rgba(21,31,39,0.52); color: var(--muted); }
.reset-confirm-go { border: 1px solid rgba(214,90,74,0.4); background: rgba(214,90,74,0.18); color: var(--danger); font-weight: 600; }
.reset-confirm-go:disabled { opacity: 0.4; cursor: not-allowed; }
/* Playlist import reuses the confirm shell, but it is a normal action rather
   than a destructive one, so it drops the danger colouring. */
.playlist-confirm-title { color: var(--fg); }
.playlist-confirm .playlist-name { color: var(--accent); }
.playlist-confirm-go {
  min-height: 30px; border-radius: 7px; padding: 0 12px; cursor: pointer;
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  border: 1px solid rgba(232, 168, 56, 0.45);
  background: rgba(232, 168, 56, 0.16);
  color: var(--accent);
}
.playlist-confirm-go:disabled { opacity: 0.4; cursor: not-allowed; }

.library-editor-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 11px; }
.library-editor-status { font-size: 10.5px; color: var(--muted); }
.library-editor-status.out-of-sync { color: var(--danger); font-weight: 600; }
.library-editor-sync { min-height: 30px; border-radius: 7px; border: 1px solid rgba(244,183,64,0.3); background: rgba(244,183,64,0.16); color: var(--accent); font-family: var(--font-mono); font-size: 11px; padding: 0 13px; cursor: pointer; }
.library-editor-sync:disabled { opacity: 0.55; cursor: default; }

/* Color picker popover */
.color-picker-popover {
  position: absolute; z-index: 90;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  border-radius: 8px; padding: 8px; display: flex; gap: 6px;
  box-shadow: var(--shadow-panel);
}

/* Collapsed sidebar strip */
.cat-collapsed-strip { display: none; }
.daw.cat-collapsed .sidebar { width: var(--rail-w); }
.daw.cat-collapsed .sidebar-body { opacity: 0; pointer-events: none; visibility: hidden; }
.daw.cat-collapsed .cat-collapsed-strip { display: flex; flex-direction: column; gap: 8px; align-items: center; padding: 8px 0; overflow-y: auto; }

/* ═══════════════════════════════════
   MAIN AREA (#lanes)
   ═══════════════════════════════════ */
.daw-main-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.daw-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

/* ── Job progress overlay ── */
.job {
  position: absolute;
  inset: 0;
  z-index: 20;
  background: rgb(7, 13, 19);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 32px;
}
/* Gold ambient glow -- matches the wave-loading-overlay aesthetic */
.job::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 44%, rgba(216, 168, 74, 0.13), transparent 55%);
  pointer-events: none;
}
.job > * { position: relative; z-index: 1; }
.job.hidden { display: none !important; }
.job .title { font-size: 15px; font-weight: 600; color: var(--fg); text-align: center; }
.job .stage { font-size: 12px; color: var(--muted); text-align: center; }
.job progress {
  width: 280px; max-width: 100%;
  height: 4px;
  border-radius: 2px;
  appearance: none; border: none;
  background: var(--panel-3);
  overflow: hidden;
}
.job progress::-webkit-progress-bar { background: var(--panel-3); border-radius: 2px; }
.job progress::-webkit-progress-value { background: var(--accent); border-radius: 2px; }
.job progress::-moz-progress-bar { background: var(--accent); border-radius: 2px; }
.job .job-detail { font-size: 11px; color: var(--muted-2); text-align: center; }
.cancel-btn {
  height: 30px; padding: 0 16px;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  color: var(--fg-2); border-radius: 8px; font-size: 12px;
  font-family: inherit; cursor: pointer;
  transition: background var(--t-fast);
}
.cancel-btn:hover { background: var(--panel-3); color: var(--fg); }
.cancel-btn.hidden { display: none !important; }

/* Error banner */
.error {
  position: absolute;
  top: 64px; left: 260px; right: 0;
  z-index: 15;
  padding: 12px 20px;
  background: rgba(214,90,74,0.12);
  border-bottom: 1px solid rgba(214,90,74,0.3);
  color: #ff9090;
  font-size: 13px;
}
.error.hidden { display: none !important; }

/* ── Track header ── */
.daw-track-header {
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
  flex-shrink: 0;
}

/* Row 1. A grid rather than a flex row so its column edges land on exactly
   the same pixels as the presence grid below it -- flex and grid round
   fractional track widths differently, which left the two rows a pixel out of
   step at most window widths. */
.daw-info-row {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  border-bottom: 1px solid var(--border);
}

/* Track card */
.daw-track-card {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 4px 16px;
  flex-shrink: 0;
  width: 340px;
  border-right: 1px solid var(--border);
}

/* Metadata cards.
   Two lines, not three: the qualifier (Natural Minor, Peak 3.5 dB, High) sits
   on the value's own line rather than under it. Six stems only fit on a
   1366x768 laptop if the panels above the mixer stop taking a third of the
   window (#480), and a third text row here cost more height than it carried
   information. */
.daw-meta-card {
  /* Horizontal padding shrinks with the window. Seven columns at 950px are
     ~135px each, and a fixed 16px a side spent a quarter of that on nothing. */
  padding: 4px clamp(8px, 1vw, 16px);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-width: 0;
}
.daw-meta-card:last-child { border-right: none; }
.meta-card-label {
  /* Never two lines. The card centres its content, so a wrapped "DYNAMIC
     RANGE" did not push the value down, it painted over it. Ellipsis is the
     floor here; the fluid size is what keeps it from being reached. */
  font-size: clamp(9px, 0.72vw, 10px);
  line-height: 1;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meta-card-main {
  display: flex;
  align-items: baseline;
  gap: clamp(4px, 0.5vw, 8px);
  min-width: 0;
}
.meta-card-value {
  /* "Natural Minor" at a fixed 17px is 133px of mono, wider than the card it
     sits in below ~1250px, and flex-shrink:0 let it paint straight over the
     next card's value. It shrinks now, and clips as a last resort. */
  font-size: clamp(13px, 1.15vw, 17px);
  font-weight: 700;
  color: var(--fg);
  line-height: 1;
  font-family: var(--font-mono);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meta-card-value.accent { color: var(--accent); }
.meta-card-value.stability-high { color: #4caf7d; }
.meta-card-sub {
  font-size: clamp(9px, 0.75vw, 11px);
  line-height: 1;
  color: var(--muted);
  /* Shrinks four times as fast as the value beside it: the qualifier is the
     part worth losing first, the number is the reason the card exists. */
  flex-shrink: 4;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.daw-cover {
  width: 64px; height: 64px;
  border-radius: 8px;
  background: var(--panel-2);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; color: var(--muted);
  position: relative; overflow: hidden;
}
.np-art-placeholder { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
.np-art-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: none;
}
.np-art-img[src]:not([src=""]) { display: block; }

.daw-track-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.daw-track-title { font-size: 16px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* One compact line -- duration · stems · source · quality · extracted date --
   instead of a duration/stems row plus three further label:value rows below
   it. The four-line version was still the pre-redesign layout; this row was
   the one part of the footer rebuild (#269 follow-up) I'd described matching
   the reference but never actually rebuilt. */
.daw-track-meta-line {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 11px; color: var(--muted);
  min-width: 0; overflow: hidden;
}
.daw-track-meta-line .num { color: var(--fg-2); }
.daw-track-sep { color: var(--border); font-size: 12px; flex-shrink: 0; }
#t-stems-chip {
  display: inline-flex; align-items: center; gap: 4px;
}
#t-stems-chip::before {
  content: '';
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #4caf7d;
  flex-shrink: 0;
}
.daw-fav-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--muted);
  padding: 2px;
  display: flex;
  align-items: center;
  transition: color 150ms;
}
.daw-fav-btn:hover { color: var(--fg); }
.daw-fav-btn.active { color: #e54e4e; }
.daw-fav-btn.active svg { fill: #e54e4e; stroke: #e54e4e; }
.daw-chip {
  display: inline-flex; align-items: center;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--panel-2);
  font-size: 11px;
  color: var(--fg-2);
  font-family: var(--font-mono);
}

/* Energy panel */
.daw-energy-panel {
  padding: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.daw-energy-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.daw-playhead-label { color: var(--accent); display: flex; align-items: center; gap: 4px; }
.daw-energy-bars { flex: 1; display: flex; flex-direction: column; justify-content: space-between; gap: 4px; }

/* Energy rows (JS-driven) */
.energy-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.energy-row span {
  font-size: 11px;
  font-weight: 500;
  width: 44px;
  flex-shrink: 0;
}
.energy-row.vocals span { color: var(--vocals); }
.energy-row.drums  span { color: var(--drums); }
.energy-row.bass   span { color: var(--bass); }
.energy-row.guitar span { color: var(--guitar); }
.energy-row.piano  span { color: var(--piano); }
.energy-row.other  span { color: var(--other); }
.energy-row.original span { color: var(--fg-2); }

.energy-row b {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--bg);
  overflow: hidden;
  position: relative;
  display: block;
}
.energy-row b::after {
  content: '';
  position: absolute; inset: 0;
  width: var(--v, 0%);
  border-radius: 3px;
  transition: width 160ms;
}
.energy-row.vocals b::after { background: var(--vocals); }
.energy-row.drums  b::after { background: var(--drums); }
.energy-row.bass   b::after { background: var(--bass); }
.energy-row.guitar b::after { background: var(--guitar); }
.energy-row.piano  b::after { background: var(--piano); }
.energy-row.other  b::after { background: var(--other); opacity: 0.5; }
.energy-row.original b::after { background: var(--fg-2); }

.energy-row em {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-2);
  width: 36px;
  text-align: right;
  flex-shrink: 0;
}

/* Stem presence cards — row 2. One line each: the label and its percentage
   read as a pair, and stacking them doubled the row's height for no gain
   (#480). */
.stem-presence-panel {
  display: grid;
  /* Seven, matching the seven metadata cards above, so the two rows share
     their dividers instead of interleaving them. Six presence stems fill
     seven columns because Vocals takes two; a completed lead/backing split
     replaces that one card with two and the row becomes a plain 7x1. */
  grid-template-columns: repeat(7, minmax(0, 1fr));
}
.stem-card[data-stem="vocals"] { grid-column: span 2; }
.stem-card {
  padding: 8px clamp(6px, 0.8vw, 12px);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: clamp(4px, 0.5vw, 8px);
  min-width: 0;
}
.stem-card:last-child { border-right: none; }
.stem-card-label {
  /* Fluid for the same reason as the labels above: at 950px these truncated to
     "LE..." and "BA...", which name nothing. */
  font-size: clamp(8px, 0.66vw, 10px);
  line-height: 1;
  font-weight: 600;
  /* Tighter than the metadata labels above: on a side-by-side row these
     compete with the percentage for width, and 0.07em truncated
     "VOCAL PRESENCE" at 1366px. */
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.stem-card-pct {
  font-size: clamp(14px, 1.2vw, 18px);
  font-weight: 700;
  font-family: var(--font-mono);
  line-height: 1;
  color: var(--fg);
  transition: color 200ms;
  flex-shrink: 0;
}
.stem-card:not(.inactive)[data-stem="vocals"] .stem-card-pct { color: var(--vocals); }
.stem-card:not(.inactive)[data-stem="drums"]  .stem-card-pct { color: var(--drums); }
.stem-card:not(.inactive)[data-stem="bass"]   .stem-card-pct { color: var(--bass); }
.stem-card:not(.inactive)[data-stem="guitar"] .stem-card-pct { color: var(--guitar); }
.stem-card:not(.inactive)[data-stem="piano"]  .stem-card-pct { color: var(--piano); }
.stem-card:not(.inactive)[data-stem="other"]  .stem-card-pct { color: var(--other); }
.stem-card.inactive .stem-card-pct { color: var(--muted); }

/* Analysis panel */
.daw-analysis-panel {
  padding: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  gap: 16px;
  flex-shrink: 0;
}
.daw-analysis-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 80px;
}
.daw-analysis-val { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
.daw-analysis-num {
  font-size: 22px;
  font-weight: 600;
  line-height: 1.1;
  color: var(--fg);
}
.daw-analysis-num.accent { color: var(--accent); }
.daw-analysis-sub { font-size: 10px; color: var(--muted); margin-top: 2px; }
.daw-analysis-divider { width: 1px; background: var(--border); align-self: stretch; }
.daw-camelot-ring {
  width: 20px; height: 20px; border-radius: 50%;
  border: 1.5px solid var(--accent);
  display: flex; align-items: center; justify-content: center;
  align-self: center;
  flex-shrink: 0;
  font-size: 9px; font-weight: 700; color: var(--accent);
  background: rgba(244,183,64,0.08);
  font-family: var(--font-mono);
}

/* ── Section ribbon ── */
.daw-section-ribbon {
  display: flex;
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
}
.daw-mixer-label {
  width: 300px;
  flex-shrink: 0;
  background: var(--bg-2);
  border-right: 1px solid var(--border);
  padding: 6px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.daw-label-title { font-size: 12px; font-weight: 600; }
.daw-label-sub { font-size: 9px; }
.daw-sections-header { justify-content: space-between; }
.daw-sections-title-group { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
/* Clear and Add belong together. The header is space-between, so without this
   group each control drifts to its own corner and Clear ends up marooned. */
.sections-header-actions { display: inline-flex; align-items: center; gap: 6px; }
.daw-sections-area {
  flex: 1;
  position: relative;
  height: 36px;
  background: var(--bg-2);
  /* clip, not hidden. `hidden` is a scroll container, and the inner track now
     overflows it, so anything that focuses an element off to the right -- the
     rename input on a block past the viewport is the easy one -- makes the
     browser scroll this box to reveal it. The ribbon is positioned by
     transform, nothing ever resets scrollLeft, and the strip is then offset
     from the waveform for good. `clip` clips identically and cannot scroll.
     .daw-ruler-area is clip for the same reason. */
  overflow: clip;
  user-select: none;
}
/* Carries the section blocks at the zoomed timeline's width, so a block placed
   at a percentage of it lines up with the same moment on the waveform below.
   Without this the ribbon stayed the width of the window while the waveform
   grew, and every boundary drifted: at 2.7x the first one sat 775px from the
   audio it marked, and the drag maths, which measures this element, moved a
   block 2.7x too far for the same gesture (#573).

   Left-anchored so width drives the size, exactly like .lanes-ruler-time.
   transport.js translates both by the same scrollLeft; .daw-sections-area
   already clips the spill. */
.daw-sections-track {
  position: absolute;
  inset: 0;
  right: auto;
  width: calc(100% * var(--zoom, 1));
}

/* Section blocks (sections.js populates) */
.section-block {
  position: absolute;
  top: 4px; bottom: 4px;
  box-sizing: border-box;
  border: 1px solid var(--sc, #4a7fff);
  border-radius: 4px;
  display: flex;
  align-items: center;
  overflow: hidden;
  cursor: grab;
  transition: filter 80ms;
}
.section-block:hover { filter: brightness(1.15); }
.section-block.sec-dragging { cursor: grabbing; filter: brightness(1.2); z-index: 10; }
.section-block.sec-resizing { filter: brightness(1.2); z-index: 10; }

.section-label {
  flex: 1;
  padding: 0 6px 0 10px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--sc, #4a7fff);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: auto;
}

.section-del {
  display: none;
  flex-shrink: 0;
  width: 16px; height: 16px;
  margin-right: 4px;
  border: none; background: none;
  color: var(--sc, #4a7fff);
  font-size: 13px; line-height: 1;
  cursor: pointer; border-radius: 3px;
  align-items: center; justify-content: center;
  opacity: 0.7;
}
.section-del:hover { opacity: 1; background: rgba(0,0,0,0.3); }
.section-block:hover .section-del { display: flex; }

.section-lock {
  display: none;
  flex-shrink: 0;
  width: 16px; height: 16px;
  margin-right: 2px;
  border: none; background: none;
  color: var(--sc, #4a7fff);
  cursor: pointer; border-radius: 3px;
  align-items: center; justify-content: center;
  opacity: 0.7;
  padding: 0;
}
.section-lock:hover { opacity: 1; background: rgba(0,0,0,0.3); }
.section-block:hover .section-lock { display: flex; }
/* A locked section shows its padlock without being hovered, and in red. Hiding
   it would make locked and unlocked look identical at rest, and the only way to
   find a locked one would be to try to drag it. Red because the padlock is the
   one control that is saying no to the others. */
.section-block.sec-locked .section-lock {
  display: flex;
  opacity: 1;
  color: var(--danger);
}
.section-block.sec-locked .section-lock:hover { opacity: 1; }

/* The grab and col-resize cursors promise a drag that cannot happen. */
.section-block.sec-locked,
.section-block.sec-locked .section-handle { cursor: default; }
.section-block.sec-locked .section-handle:hover { background: none; }

.section-handle {
  position: absolute;
  top: 0; bottom: 0;
  width: 6px;
  cursor: col-resize;
  z-index: 2;
  flex-shrink: 0;
}
.section-handle-l { left: 0; }
.section-handle-r { right: 0; }
.section-handle:hover { background: rgba(255,255,255,0.12); }

.section-rename-input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  font: inherit;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--sc, #4a7fff);
  padding: 0 6px 0 10px;
  caret-color: var(--sc, #4a7fff);
}

/* Sections save indicator */
.sections-save-indicator {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
  color: var(--muted);
  white-space: nowrap;
  transition: opacity 400ms;
}
.sections-save-indicator.hidden { display: none; }
.sections-save-indicator.saved { color: #4caf7d; }
/* A refusal, not a failure of something already in flight. It borrows this
   element because it belongs beside the button that was pressed. */
.sections-save-indicator.notice { color: var(--warn, #e6a23c); white-space: normal; }
.sections-save-indicator::before {
  content: "";
  display: block;
  width: 8px; height: 8px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  border-top-color: transparent;
  animation: sections-spin 600ms linear infinite;
}
.sections-save-indicator.notice::before {
  animation: none;
  border: none;
  width: auto; height: auto;
  content: "!";
  font-weight: 700;
  font-size: 11px;
}
.sections-save-indicator.saved::before {
  animation: none;
  border: none;
  width: auto; height: auto;
  content: "✓";
  font-size: 11px;
}
@keyframes sections-spin {
  to { transform: rotate(360deg); }
}

/* Add section button — lives in the Sections label column */
.sections-add-btn-label {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px 3px 6px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--muted);
  font-size: 10px; font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.sections-add-btn-label:hover {
  background: var(--panel-2);
  color: var(--fg);
  border-color: rgba(255,255,255,0.18);
}

/* Clear-all sections. Neutral until it is armed, then it states plainly that
   the next click destroys something, because nothing here can be undone. */
.sections-clear-btn[data-armed="1"] {
  border-color: color-mix(in srgb, var(--vocals) 55%, transparent);
  background: color-mix(in srgb, var(--vocals) 14%, transparent);
  color: var(--vocals);
}

/* Wave label (left column in wave header — Mixer label) */
.daw-wave-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
}

/* ── Panel toggles (#480) ── */
/* A second row under the composer, aligned to its right-hand end so it reads as
   belonging to the two controls above it. Collapse to nothing, not to a stub:
   the whole point is the height back, and a stub tall enough to hold a control
   is most of what the smaller panels are worth. That is only possible because
   the controls live up here, where the way back is always in the same place.

   This row costs the topbar about 19px, against up to 202px it can return. */
.daw-composer-stack {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.daw-composer-stack > .daw-composer { flex: none; }
/* Exactly as wide as the two controls above it, and sharing their right edge:
   the same --composer-action-w they are each sized from, so a longer
   translation widens all three together instead of leaving this one ragged.
   margin-left:auto does the alignment, since the stack is the composer's width. */
.daw-panel-toggles {
  width: calc(var(--composer-action-w) * 2);
  margin-left: auto;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2px;
  padding: 2px 7px;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  min-width: 0;
}
/* The rule separates the row's label from its controls. It sits after the
   label, so "All" reads as the first of four buttons rather than as part of
   the words describing them. */
.daw-panel-toggles-label {
  margin-right: 2px;
  padding-right: 6px;
  border-right: 1px solid var(--border-strong);
}
.daw-panel-toggles-label,
.daw-panel-toggles-sep {
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1;
  white-space: nowrap;
}
.daw-panel-toggle {
  padding: 2px 3px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--fg-2);
  font-family: inherit;
  font-size: 8.5px; font-weight: 600; letter-spacing: 0.03em;
  text-transform: uppercase;
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--t-fast), background var(--t-fast), opacity var(--t-fast);
}
.daw-panel-toggle:hover { background: var(--panel-2); }
/* Legible means the panel is there, struck through means it is put away. The
   other way round -- highlighting the hidden ones -- inverts what a pressed
   toggle normally looks like, and makes the default state the loud one. */
.daw-panel-toggle[aria-pressed="true"] { color: var(--fg-2); }
.daw-panel-toggle[aria-pressed="false"] {
  color: var(--muted);
  opacity: 0.6;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.app.panel-analysis-off .daw-track-header { display: none; }
.app.panel-sections-off .daw-section-ribbon { display: none; }
.app.panel-timeline-off .footer-wave-region { display: none; }

/* ── Waveform header ── */
.daw-wave-header {
  display: flex;
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
}
.daw-wave-label {
  width: var(--daw-col-w);
  flex-shrink: 0;
  background: var(--bg-2);
  border-right: 1px solid var(--border);
  min-height: 32px;
}
.daw-ruler-area {
  flex: 1;
  background: var(--bg-2);
  min-height: 32px;
  /* Clip the horizontally-scrolled ruler spill. overflow-x: clip (not hidden)
     keeps overflow-y visible so the vertical playhead line still extends down
     over the waveform lanes. */
  overflow-x: clip;
}

/* Ruler */
.lanes-ruler { position: relative; height: 18px; }
.lanes-ruler-time {
  position: absolute; inset: 0;
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--muted);
}
/* Match the zoomed waveform width so ticks/playhead stay aligned; JS translates
   it by scrollLeft. Left-anchored (right: auto) so width drives the size. */
.daw .lanes-ruler-time {
  right: auto;
  width: calc(100% * var(--zoom, 1));
}
.playhead-marker {
  position: absolute;
  top: -1px;
  transform: translateX(-50%);
  pointer-events: none;
}

/* ── Content area (mixer + waveform) ── */
.daw-content {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
}

/* ── Stems panel (the studio's left column) ── */
.daw-stems-panel {
  width: var(--daw-col-w);
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg-2);
}

/* Stem list rows — kept in DOM for JS hook (.stem-mute/.stem-solo toggling, hidden visually) */
.stem-list {
  position: absolute;
  width: 0; height: 0; overflow: hidden;
  visibility: hidden; pointer-events: none;
  opacity: 0;
}
/* Also update the --lane-h var to match waveform lane height */
:root { --lane-h: 72px; } /* 70px wave + 2px separator */
.stem-icon { width: 14px; height: 14px; flex-shrink: 0; }
.stem-mute, .stem-solo { display: none; }
.stem-monitor { display: none; }
.drag-handle { display: none; }
.mini-meter { display: none; }

/* ── Mixer column (JS-built horizontal lanes) ── */
.mixer-column {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}

/* Each mixer row height is driven by --lane-h (set dynamically in JS to fill available space) */
.lane-header.mx-row {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 0 10px;
  height: var(--lane-h, 72px);
  min-height: 72px;
  max-height: none;
  border-bottom: 2px solid var(--border);
  background: var(--bg-2);
  flex-shrink: 0;
  position: relative;
  transition: opacity var(--t-base);
  box-sizing: border-box;
}
.lane-header.mx-row.muted { opacity: 0.45; }
.lane-header.mx-row.hidden { display: none !important; }

.lane-stripe { display: none; }

/* Name + VU stacked vertically */
/* The name and its meter share a fixed column, and the fader takes whatever is
   left of the row. So this width is the fader's length: every pixel taken off
   here is a pixel the fader gains. 76px left the fader badly compressed once
   the key stepper joined the row. */
.lane-name-vu {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--lane-name-vu-gap, 5px);
  width: 58px;
  flex-shrink: 0;
  /* Centres the NAME on the row, not the name-and-meter pair. The row centres
     its children, so a stacked pair puts the name above the centre line by half
     the meter plus the gap -- and the waveform beside it is centred on that
     line, so the label reads as sitting too high. A top margin is centred with
     the item, so this offsets the pair by exactly half of it and the name comes
     level with its own waveform. Cheap and reversible: the meter simply moves
     down with it. */
  margin-top: calc(var(--lane-vu-h, 10px) + var(--lane-name-vu-gap, 5px));
}

/* Stem icon — hidden per user preference */
.mx-icon { display: none; }

/* Stem name */
.mx-name {
  font-size: 13px; font-weight: 500;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Fader (horizontal, native range input) */
.lane-knob.mx-fader {
  flex: 1;
  display: flex;
  align-items: center;
  min-width: 0;
}
.lane-knob-indicator { display: none; }
.mx-fader-input {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 22px;
  background: transparent;
  cursor: ew-resize;
  outline: none;
  margin: 0;
}
.mx-fader-input::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(
    to right,
    var(--fader-color, #888) 0%,
    var(--fader-color, #888) calc(var(--lane-pos, 0.5) * 100%),
    rgba(255,255,255,0.08) calc(var(--lane-pos, 0.5) * 100%)
  );
}
.mx-fader-input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--fader-color, #888);
  margin-top: -5px;
  border: 2px solid rgba(0,0,0,0.35);
  box-shadow: 0 0 0 1px rgba(255,255,255,0.18);
  cursor: ew-resize;
}
.mx-fader-input::-moz-range-track {
  height: 4px; border-radius: 2px;
  background: rgba(255,255,255,0.08);
}
.mx-fader-input::-moz-range-thumb {
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--fader-color, #888);
  border: 2px solid rgba(0,0,0,0.35);
  cursor: ew-resize;
}

/* VU meter — sits below stem name in .lane-left-col, spans full column width */
/* 5px read as a hairline rather than a meter: at that height the gradient had
   nowhere to show and a moving level was hard to see at a glance. */
/* Height shared with .lane-name-vu's centring offset, so the two cannot drift. */
:root { --lane-vu-h: 10px; }
.lane-vu.mx-meter {
  position: relative;
  height: var(--lane-vu-h, 10px);
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 3px;
  overflow: hidden;
  --vu-level: 0%;
  --vu-peak: 0%;
}
.lane-vu-bar {
  display: none;
}
.lane-vu-bar.mx-meter-fill {
  display: block;
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--vu-level, 0%);
  background: linear-gradient(90deg, #22c55e 0%, #86efac 80%, #f97316 95%, #ef4444 100%);
  border-radius: 2px;
  transition: width 40ms linear;
}

/* dB value */
.mx-val {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  color: var(--fg);
  width: 36px;
  text-align: right;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* M/S buttons (JS-built) */
.lane-icon-toggle.mx-btn,
.ms-btn.mx-btn,
.lane-dl.mx-btn {
  width: 22px; height: 20px;
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  color: var(--muted);
  font-size: 10px; font-weight: 700;
  font-family: var(--font-mono);
  letter-spacing: 0.05em;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
  flex-shrink: 0;
  text-decoration: none;
  padding: 0;
  opacity: 1;
}
.lane-icon-toggle.mx-btn:hover,
.ms-btn.mx-btn:hover { color: var(--fg); border-color: #3a4550; }

/* Mute button: "active" = NOT muted (normal). Engaged (muted) lights up blue,
   mirroring the solo button's own lit-up treatment below. */
.lane-icon-toggle.mx-btn.mute.active { color: var(--fg-2); border-color: var(--border-strong); }
.lane-icon-toggle.mx-btn.mute:not(.active) {
  background: rgba(59,130,246,0.18);
  color: #60a5fa;
  border-color: rgba(59,130,246,0.4);
}

/* Solo button: "active" = soloed */
.ms-btn.mx-btn.active { background: rgba(244,183,64,0.18); color: var(--accent); border-color: rgba(244,183,64,0.4); }

/* Download button */
.lane-dl.mx-btn {
  color: var(--muted);
  border-color: transparent;
}
.lane-dl.mx-btn:hover { color: var(--fg); border-color: var(--border); }
.lane-dl.mx-btn svg { width: 13px; height: 13px; }

/* Unavailable stems — gray entire mixer row and waveform row */
.lane-header.unavailable { opacity: 0.25; pointer-events: none; }
.stem-waveform-row.unavailable { opacity: 0.2; pointer-events: none; }
.mx-fader-input { touch-action: pan-y; }

/* ── Waveform panel (right) ── */
/* Layout only — waves.css handles all visual styling inside (.wave-scroll bg,
   golden scrollbar, .loop-region, .waves-grid, .stem-waveform-layer etc.) */
.daw-wave-panel {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
}

/* waves.css gives .wave-editor padding + border-radius for the old rounded panel
   style. In the flat DAW layout those are unwanted — strip them. */
.daw .wave-editor,
.app:not(.is-import) .daw-wave-panel.wave-editor {
  padding: 0 !important;
  min-height: 0 !important;
  border-radius: 0 !important;
}

/* Our zoom toolbar sits outside .wave-editor (in .daw-wave-header), so
   the wave-scroll should fill the full panel height instead of calc(100% - 32px).
   Also remove the rounded corners waves.css adds in active state. */
.daw .wave-scroll {
  flex: 1 !important;
  height: auto !important;
  border-radius: 0 !important;
  border: none !important;
  overflow-y: auto;
  /* waves.css sets scroll-behavior: smooth. In the studio the mixer column and
     the wave lanes mirror each other's scrollTop (wireLaneScrollSync), and a
     smooth programmatic scroll turns that mirror into a feedback loop: each
     echo re-targets the in-flight animation, so a vertical wheel-scroll to the
     middle oscillates instead of settling, i.e. the lanes shake (#182). Instant
     scrolling makes the mirror a no-op once both panes match. */
  scroll-behavior: auto;
}

/* Canvas fills the panel when lanes fit, but grows with the track stack when
   they overflow (#159) so .wave-scroll can scroll the extra lanes vertically. */
.daw .wave-canvas { min-height: 100%; height: auto; }

/* In the original layout a 48px stem-icon strip lived INSIDE the wave panel,
   offsetting waves-column/multitrack/waveform-layer by 48px. In our layout the
   mixer is a separate 300px panel, so all those 48px offsets must be zero. */
.daw .waves-column,
.app:not(.is-import) .daw-wave-panel .waves-column {
  padding-left: 0 !important;
  --wave-gutter: 0px !important;
}
/* Show WaveSurfer native bar rendering; hide the SVG overlay layer */
.daw #multitrack-container {
  opacity: 1 !important;
  pointer-events: all !important;
  position: relative !important;  /* in flow → waves-column height driven by WaveSurfer */
  inset: 0 !important;             /* waves.css sets inset: 0 0 0 48px; zero left offset */
}
/* The wavesurfer-multitrack bundle wraps its tracks in its own
   `<div style="overflow-x: scroll">`, which renders a permanently visible
   horizontal scrollbar — an empty light bar when the content fits (the "white
   bar" bug). We fit the bundle to its container and let the outer .wave-scroll
   own horizontal scrolling, so suppress the bundle's wrapper scrollbar. */
.daw #multitrack-container > div {
  overflow-x: hidden !important;
}
/* Zero waves.css 48px left offsets that assumed an icon strip inside the wave panel */
.daw .waves-grid { left: 0 !important; }
.daw .lanes-ruler { margin-left: 0 !important; }
.daw .stem-waveform-layer {
  display: none !important;
  /* waves.css offsets this layer by left:48px for the legacy icon-strip layout.
     The DAW layout has no in-panel strip, so it must start at 0 like .waves-grid
     and #multitrack-container above; otherwise the overview waveform is shifted
     48px right of the ruler/playhead, so t=0 in the audio lands ~48px before the
     drawn waveform begins (a constant gap that scales with track length, #189). */
  left: 0 !important;
  right: 0 !important;
}
/* When the Web Audio engine owns playback, the multitrack is mounted with null
   URLs and never decodes audio, so the WaveSurfer canvas is empty. Show the SVG
   overview layer (rendered from peaks.json) as the visual source instead. */
.daw.engine-waveforms .stem-waveform-layer {
  display: flex !important;
}
/* The column is exactly the stack _applyLaneHeight computed, never whatever the
   multitrack happens to be. Letting the multitrack size it looks reasonable and
   is where the misalignment came from: its lane height is fixed when the tracks
   are created, so after a resize the column keeps the old size while the mixer
   rows beside it follow the new one, and the two walk apart down the stack.
   Measured at 1600x768 after a resize from 900: the column stayed 498px against
   a 432px stack, so waveform rows sat 83px apart against the mixer's 72 and the
   last pair was 55px out of line.

   The mixer stack is count * --lane-h and this is count * --lane-h, so they
   agree by construction rather than by coincidence. */
.daw .waves-column {
  height: var(--wave-widget-track-stack-h, auto) !important;
  min-height: var(--wave-widget-track-stack-h, 0) !important;
}

.loop-region.hidden { display: none !important; }


/* Wave loading overlay */
.wave-loading-overlay {
  position: absolute; inset: 0; z-index: 10;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 16px;
  background: rgb(11, 15, 18);
}
.wave-loading-overlay.hidden { display: none !important; }
.wave-loading-glow {
  width: 80px; height: 80px; border-radius: 50%;
  background: radial-gradient(circle, rgba(244,183,64,0.2) 0%, transparent 70%);
  animation: daw-pulse 2s ease-in-out infinite;
}
@keyframes daw-pulse { 0%,100%{opacity:0.5;transform:scale(0.9);} 50%{opacity:1;transform:scale(1.1);} }
.wave-loading-lines {
  display: flex; gap: 3px; align-items: center;
}
.wave-loading-lines i {
  display: block; width: 3px; height: 20px;
  background: var(--accent); border-radius: 2px;
  animation: daw-bar 1.2s ease-in-out infinite;
}
.wave-loading-lines i:nth-child(2) { animation-delay: 0.1s; }
.wave-loading-lines i:nth-child(3) { animation-delay: 0.2s; }
.wave-loading-lines i:nth-child(4) { animation-delay: 0.3s; }
.wave-loading-lines i:nth-child(5) { animation-delay: 0.4s; }
.wave-loading-lines i:nth-child(6) { animation-delay: 0.5s; }
@keyframes daw-bar { 0%,100%{transform:scaleY(0.4);opacity:0.5;} 50%{transform:scaleY(1.4);opacity:1;} }
.wave-loading-msg { font-size: 12px; color: var(--muted); }

/* Presence panel (hidden, JS compat) */
.presence-panel { display: none; }

/* ── Transport footer ── */

.daw-footer {
  /* No height of its own: the two tiers inside decide it, and every pixel not
     spent here goes to the mixer, which is the panel actually short of room
     (#480). This used to carry min-height: 200px, from when the click-track
     cluster could wrap onto a second line and a rigid height would have
     clipped it. The controls row no longer wraps, so the floor only padded the
     footer past its content -- 30px of it, which pooled as a gap wherever the
     tiers were not pushed. */
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  background: var(--bg-2);
  /* Two columns on the studio's own grid: the track identity sits under the
     stems/mixer panel and carries its width, everything time-related sits
     under the lanes and starts exactly where the lane waveforms start, so a
     position reads at the same x in both. The top border still spans the full
     width -- that edge belongs to the footer/lanes boundary, not the grid. */
  display: flex !important;
  flex-direction: row;
  align-items: stretch;
}

.daw-footer-left {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 140px;
  flex: 1;
}
.daw-time-big {
  font-size: 20px; font-weight: 600; letter-spacing: -0.01em;
}
.daw-time-total { font-size: 13px; color: var(--muted); }
.daw-footer-right {
  display: flex; align-items: center; gap: 8px;
  min-width: 140px; justify-content: flex-end;
  flex: 1;
}
.daw-master-fader { accent-color: var(--accent); cursor: pointer; }
.daw-btn {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  color: var(--fg); border-radius: 8px; font-size: 12px;
  font-family: inherit; cursor: pointer;
  transition: background var(--t-fast);
}
.daw-btn:hover { background: var(--panel-3); }

/* Transport controls */
.daw-transport-controls {
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.daw-play-btn {
  background: var(--accent);
  color: #1a1206;
  width: 56px; height: 56px;
  border-radius: 50%; border: none;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(244,183,64,0.35), inset 0 1px 0 rgba(255,255,255,0.18);
  transition: transform var(--t-fast), box-shadow var(--t-fast), background var(--t-fast);
  flex-shrink: 0;
}
.daw-play-btn:hover { box-shadow: 0 2px 16px rgba(244,183,64,0.5), inset 0 1px 0 rgba(255,255,255,0.22); }
.daw-play-btn:active { transform: scale(0.96); }
/* Playing = green (go); idle/ready stays gold. */
.daw-play-btn.playing {
  background: #4caf7d;
  box-shadow: 0 2px 12px rgba(76,175,125,0.45), inset 0 1px 0 rgba(255,255,255,0.2);
}
.daw-play-btn.playing:hover { box-shadow: 0 2px 18px rgba(76,175,125,0.6), inset 0 1px 0 rgba(255,255,255,0.24); }

/* JS toggles these to show play vs pause icon */
.btn-transport.playing .pause-icon { display: block !important; }
.btn-transport.playing > svg:first-child { display: none; }

/* Stop/loop buttons inside the footer transport row */
.daw-footer .daw-iconbtn.btn-transport {
  width: 40px; height: 40px;
  border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
}

/* Loop active state */
.daw-iconbtn.loop.active,
.btn-transport.loop.active { color: var(--accent); }

/* Export buttons in footer */
.daw-footer .footer-btn {
  padding: 0 14px;
  height: 36px;
  border-radius: 8px;
  gap: 6px;
  white-space: nowrap;
}
.daw-footer .footer-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ── Footer redesign ── */

/* ── Footer layout: three tiers (design 1b) ── */
/* Tier 1 -- what is loaded and what leaves the app (track identity, favourite,
   Export Mix). Tier 2 -- every playback and click control, in labelled clusters
   separated by hairline dividers. Tier 3 -- the timeline those clusters act on.
   Each tier owns its own full-width budget instead of everything competing for
   room in one line. */

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

/* ── Track identity, in the footer's timeline gutter ── */
.track-panel-head {
  display: flex; align-items: flex-start; gap: 10px; min-width: 0;
}
/* Favourite belongs with what it marks, so it rides the title line rather than
   sitting with Export Mix. Boxed, because a bare icon beside the cover art
   reads as decoration. */
.track-panel-head .daw-fav-btn {
  flex: none; margin-left: auto;
  width: 28px; height: 28px; padding: 0;
  justify-content: center;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  border-radius: 8px;
  transition: background var(--t-fast), color var(--t-fast);
}
.track-panel-head .daw-fav-btn:hover { background: var(--panel-3); }
/* Hairline cluster separator: fades out at both ends so it reads as a seam
   between groups rather than a hard rule across the footer. Every one of these
   separates something now that .footer-row-controls never wraps -- the
   is-orphan state that used to hide a divider stranded at the end of a wrapped
   line went with the wrapping. */
.footer-divider {
  width: 1px; flex: none;
  background: linear-gradient(
    180deg, transparent, var(--border-strong) 22%, var(--border-strong) 78%, transparent
  );
}
.footer-row-controls .footer-divider { height: 44px; }
/* Starts at the footer's own left edge. The track identity that used to sit in
   front of it now lives in the library sidebar, and leaving its 300px gutter
   behind pushed every control into the right of the window with a void beside
   it.

   One line, always. Wrapping rearranged the band into a different shape at
   every window width -- clusters piling onto a second row, the click track
   splitting its own options in half -- so the footer never looked like itself
   twice. Nothing here shrinks either (.footer-group is flex: none), so when
   the window is too narrow the row scrolls sideways rather than reflowing. */
.footer-row-controls {
  display: flex; align-items: flex-start; flex-wrap: nowrap;
  gap: 16px;
  padding: 12px 18px 14px;
}
/* The clusters, and only the clusters, scroll. Favourite and Export Mix sit
   outside this box so a window too narrow for the row cannot carry them off
   the right-hand edge. */
.footer-clusters {
  flex: 1; min-width: 0;
  display: flex; align-items: flex-start; flex-wrap: nowrap;
  overflow-x: auto;
  gap: 16px;
}
/* Acts on the track, not on playback, so it is pinned opposite the clusters
   rather than filed among them. Nudged down to sit on the controls' own line
   instead of the labels above them. */
.footer-track-actions {
  flex: none;
  display: flex; align-items: center; gap: 8px;
  margin-top: 18px;
}
.footer-track-actions .track-chip { height: 32px; }
/* Opens toward the middle of the window: anchored left, a 260px menu on the
   footer's right edge would hang off it. */
.footer-track-actions .track-chip-panel { left: auto; right: 0; }
/* Natural width, never squeezed: a cluster that shrinks re-wraps its own
   controls, which is the pile-up the row-level nowrap exists to prevent. */
.footer-group {
  display: flex; flex-direction: column; gap: 6px;
  flex: none;
}
.footer-group-label {
  display: flex; align-items: center; gap: 7px;
  font-size: 10px; font-weight: 600; letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--muted);
}
/* One control height across every cluster, so the row reads as a single band
   of controls rather than a ragged stack. */
.footer-group-body {
  display: flex; align-items: center; flex-wrap: nowrap; gap: 8px;
  min-height: 34px;
}
/* Tier 3: the timeline. Full-bleed rather than an inset rounded panel -- its
   left edge has to land exactly on the lane waveforms' left edge, and a side
   border would offset the canvas by its own width. Top and bottom rules only,
   the way the ruler area above the lanes is drawn. */
/* Bottom tier: the gutter, then the timeline. Two columns rather than one, so
   the timeline's left edge lands exactly on the lane waveforms' left edge and
   a position reads at the same x in both -- while the 300px that alignment
   costs carries Export Mix instead of standing empty. */
.footer-wave-region {
  display: flex; align-items: stretch;
  padding: 0 18px 14px 0; flex-shrink: 0;
}
/* Exactly the mixer column's width: that offset is what puts the timeline
   beside it on the lanes' x, so the alignment and the block that fills the
   gutter are one fact rather than two numbers that have to agree. */
.footer-track-slot {
  width: var(--daw-col-w); flex: none;
  box-sizing: border-box;
  padding: 0 18px 0 18px;
  display: flex; flex-direction: column; justify-content: center; gap: 5px;
  min-width: 0;
  overflow: hidden;
}
.footer-wave-col {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
}
.footer-wave-panel {
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  /* Closes the strip's open left edge and lands on the same x as the mixer
     panel's right border, so that seam runs on down the page. Drawn as an
     outset shadow rather than a border: a border would sit inside the box and
     push the canvas a pixel off the lane waveforms it has to line up with. */
  box-shadow: -1px 0 0 var(--border);
  background: var(--panel);
  overflow: hidden;
}
/* Ticks at the same times as the lanes ruler (buildFooterWaveTicks shares its
   step), positioned by percentage so both rulers mark a time at the same x. */
.footer-wave-ticks {
  height: 18px; position: relative;
}
/* No track, no timeline to label -- an empty ruler strip above the placeholder
   waveform would be measuring nothing. */
.footer-wave-ticks:empty { display: none; }
.footer-wave-ticks .tick {
  position: absolute; top: 0; bottom: 0;
  border-left: 1px solid var(--border-strong);
  padding-left: 4px;
  display: flex; align-items: flex-start;
  pointer-events: none;
}
.footer-wave-ticks .tick-label {
  font-family: var(--font-mono); font-size: 9px; line-height: 1.6;
  color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}
.footer-wave-bar {
  height: 31px; flex-shrink: 0;
  position: relative; overflow: hidden;
}
#footer-waveform {
  position: absolute; inset: 0;
  width: 100%; height: 100%; display: block;
}
.footer-scrub {
  position: absolute; inset: 0;
  cursor: pointer; z-index: 2;
  background: transparent;
}
.footer-scrub-fill { display: none; }

.track-panel-info {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 3px;
}
/* Two lines, then clipped: a column this narrow would otherwise cut most
   titles after three or four words. */
.track-panel-title {
  min-width: 0;
  font-size: 13px; font-weight: 600; line-height: 1.35;
  /* Overrides the nowrap/ellipsis on .daw-track-title, which is written for
     single-line contexts. */
  white-space: normal;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
  color: var(--fg);
}
.track-panel-cover {
  width: 40px !important; height: 40px !important;
  border-radius: 7px;
  flex-shrink: 0;
}
/* Wraps freely here -- the gutter is narrow and the block is stacked, so the
   meta reads as a short paragraph rather than a clipped line. */
.footer-track-slot .daw-track-meta-line { row-gap: 2px; line-height: 1.35; }
.footer-art {
  width: 44px; height: 44px; border-radius: 6px;
  background: var(--panel-3); flex-shrink: 0; overflow: hidden;
  display: none;
}
.footer-art.has-art { display: block; }
.footer-thumb-img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 0.2s;
}
.footer-thumb-img.loaded { opacity: 1; }
.footer-info {
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.footer-title {
  font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--fg);
}
.footer-meta {
  font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Position group: elapsed / total time, then the loop controls that act on
   that position (design 1b groups them together, not with the transport). */
/* The loop column is three rows tall while the rest of this group is one, so
   centring it left the two bounds hanging below the elapsed/total readout they
   belong to. Aligning to the bottom puts the fields on the readout's line and
   lets the button and its hint stack up out of the way. */
.footer-group-position .footer-group-body { gap: 4px; align-items: flex-end; }
.footer-group-position .footer-elapsed,
.footer-group-position .footer-total,
.footer-group-position .footer-time-sep {
  /* Match the fields' box height so the baselines land together, rather than
     the text bottom meeting the border bottom. */
  line-height: 22px;
}
/* Both readouts hold the width of mm:ss whatever they are showing.
 *
 * They start at 0:00 and become 05:09 the moment a track loads, which is one
 * character wider. The Position cluster grew 17px with it and every cluster to
 * its right, Speed, Global key and Click track, slid across by the same
 * amount. Clicking a track should not move the buttons beside it.
 *
 * Reserved rather than zero-padded, because the format is the readout's own
 * business: a track under ten minutes reads 5:09 and should keep doing so. A
 * track over an hour still grows, and should. */
.footer-elapsed {
  font-size: 18px; font-weight: 500; letter-spacing: -0.02em; color: var(--fg);
  display: inline-block;
  min-width: 5ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.footer-total {
  font-size: 12px; font-weight: 400; color: var(--muted-2);
  display: inline-block;
  min-width: 5ch;
  font-variant-numeric: tabular-nums;
}
.footer-time-sep { font-size: 12px; color: var(--muted-2); }

/* Exact loop start/end inputs (inline, right of the loop button) */
/* The hint stacks over the two fields rather than beside them: the footer row
   is already full, and a note about the fields belongs with the fields. Sized
   to sit inside the width the two boxes already take, so it does not widen the
   Position group. */
.footer-loop-times-wrap {
  display: flex; flex-direction: column; align-items: stretch; gap: 2px;
  margin-left: 6px;
}
.footer-loop-hint {
  font-size: 7.5px; font-weight: 600; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--muted);
  line-height: 1; white-space: nowrap; text-align: center; user-select: none;
}
.footer-loop-times {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  cursor: default; user-select: none;
}
.loop-times-sep { font-size: 12px; color: var(--muted); flex-shrink: 0; }
.loop-time-input {
  /* 7ch holds "105.100" -- the widest value a track under 1000 s produces now
     that the minutes field is gone. */
  width: 7ch; box-sizing: content-box;
  padding: 2px 6px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 5px; outline: none;
  color: var(--fg); font-family: inherit; font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums; text-align: center;
}
.loop-time-input { cursor: ns-resize; }
.loop-time-input:focus { cursor: text; border-color: var(--accent); }
.loop-time-input:disabled { opacity: 0.45; cursor: not-allowed; }

/* Transport group: stop square + a play key wide enough to be the obvious
   primary, both on the shared 34px control height. */
.footer-group-transport .daw-iconbtn.btn-transport {
  width: 34px; height: 34px; border-radius: 8px;
  background: var(--panel-2);
  border-color: var(--border-strong);
  color: var(--fg-2);
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.footer-group-transport .daw-iconbtn.btn-transport:hover {
  background: var(--panel-3);
  color: var(--fg);
}
.footer-group-transport .daw-play-btn {
  width: 44px; height: 34px; border-radius: 8px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
}
.footer-group-transport .daw-play-btn:hover {
  box-shadow: 0 2px 12px rgba(244,183,64,0.35), inset 0 1px 0 rgba(255,255,255,0.22);
}
.footer-group-transport .daw-play-btn.playing:hover {
  box-shadow: 0 2px 12px rgba(76,175,125,0.45), inset 0 1px 0 rgba(255,255,255,0.24);
}
/* Active transport states: stop = red while stopped, loop = blue while looping. */
.footer-group-transport .btn-transport.stopped,
.footer-group-transport .btn-transport.stopped:hover {
  background: rgba(214,90,74,0.16);
  border-color: rgba(214,90,74,0.55);
  color: var(--danger);
}
/* Loop sits in the Position group and reads as a modifier on the readout next
   to it, so it is a short labelled pill rather than a full-height control. */
.footer-group-position .daw-iconbtn.btn-transport.loop {
  /* A tall narrow pill beside the fields, not a bar over them. align-self
     overrides the group's flex-end so it fills the two rows the hint and the
     bounds take, which is what keeps the footer one line high. */
  flex-direction: column; justify-content: center;
  width: 46px; height: auto; align-self: stretch;
  padding: 3px 4px; gap: 1px; margin-left: 4px;
  border-radius: 7px;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  color: var(--fg-2);
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.footer-group-position .btn-transport.loop:hover {
  background: var(--panel-3); color: var(--fg);
}
.loop-btn-label { font-size: 9.5px; font-weight: 500; white-space: nowrap; line-height: 1; }
.footer-group-position .btn-transport.loop.active,
.footer-group-position .btn-transport.loop.active:hover {
  background: rgba(74,140,255,0.16);
  border-color: rgba(74,140,255,0.55);
  color: #4a8cff;
}

/* ── Segmented controls (Speed, click rate) ── */
/* One joined track rather than separate pills: the options are exclusive, and
   butting them together says so (design 1b). */
.footer-seg {
  display: flex; height: 34px; overflow: hidden;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 8px;
}
.footer-seg > button {
  border: 0; background: transparent;
  padding: 0 11px; cursor: pointer;
  color: var(--muted);
  font-family: var(--font-mono); font-size: 11px; font-weight: 500; line-height: 1;
  white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast);
}
.footer-seg > button:hover { background: var(--panel-3); color: var(--fg); }
.footer-seg > button + button { border-left: 1px solid var(--border); }
.speed-btn.active,
.speed-btn.active:hover {
  background: rgba(232,200,64,0.16); color: var(--accent);
}
/* Transpose stepper (#245). Sits in a .footer-seg so it inherits the frame and
   hover behaviour of the speed and click controls beside it; only the readout
   between the two buttons is new. */
.pitch-value {
  display: flex; align-items: center; justify-content: center;
  min-width: 34px; padding: 0 6px;
  border-left: 1px solid var(--border); border-right: 1px solid var(--border);
  color: var(--muted);
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;  /* so +10 and -6 do not shift the buttons */
  transition: color var(--t-fast), background var(--t-fast);
}
/* Lit only when transposed, so "this track is not in its original key" is
   visible at a glance rather than needing the number to be read. */
.pitch-value.active {
  background: rgba(232,200,64,0.16); color: var(--accent);
}
.pitch-btn:disabled {
  opacity: 0.35; cursor: default;
}
.pitch-btn:disabled:hover { background: transparent; color: var(--muted); }

/* Reset. Outside the stepper's frame, because it does something to the whole
   mix rather than one more step: it returns every mixer lane to the key the
   recording is in, which the stepper alone cannot do once lanes have been moved
   individually. */
.pitch-reset {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  color: var(--muted);
  font-size: 15px; line-height: 1;
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.pitch-reset:hover:not(:disabled) {
  background: var(--panel-3); color: var(--fg); border-color: var(--border);
}
.pitch-reset:disabled { opacity: 0.35; cursor: default; }

.metro-mult-btn.active,
.metro-mult-btn.active:hover {
  background: rgba(74,140,255,0.16); color: #4a8cff;
}

/* Click track on/off toggle. Lit gold while the click is running, so its
   state is visible without any other affordance. */
.click-toggle-btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 10px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 8px; color: var(--fg-2); cursor: pointer;
  font-family: inherit; font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.click-toggle-btn:hover:not(:disabled) { background: var(--panel-3); color: var(--fg); }
.click-toggle-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.click-toggle-btn.active,
.click-toggle-btn.active:hover {
  background: rgba(232,200,64,0.18); border-color: rgba(232,200,64,0.55); color: var(--accent);
}
.click-metro-icon { flex-shrink: 0; }
/* The on/off toggle is the icon alone (#681), square like the volume button
   beside it. The Grid button shares the class above and keeps its label. */
.click-toggle-btn.click-toggle-icon {
  width: 34px;
  padding: 0;
  justify-content: center;
}

/* Beat grid editor: canvas overlay + toolbar */
.beatgrid-canvas {
  position: absolute; inset: 0; z-index: 12;
  pointer-events: none; /* enabled only while editing */
}
.beatgrid-canvas.hidden { display: none; }

.beatgrid-toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 10px; margin: 0 0 6px;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  border-radius: 7px;
}
.beatgrid-toolbar.hidden { display: none; }
.bg-tool-label {
  font-size: 10px; font-weight: 700; letter-spacing: 0.08em; color: var(--muted);
}
.bg-tools { display: flex; gap: 3px; }
.bg-btn {
  padding: 3px 9px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 5px; color: var(--fg-2); cursor: pointer;
  font-family: inherit; font-size: 11px; font-weight: 600;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.bg-btn:hover:not(:disabled) { background: var(--panel-3); color: var(--fg); }
.bg-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.bg-btn.active {
  background: rgba(74,140,255,0.16); border-color: rgba(74,140,255,0.55); color: #4a8cff;
}
.bg-btn.bg-primary {
  background: rgba(232,200,64,0.16); border-color: rgba(232,200,64,0.5); color: var(--accent);
}
.bg-btn.bg-danger:hover { border-color: rgba(214,90,74,0.55); color: var(--danger); }
.bg-check {
  display: flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 600; color: var(--fg-2); cursor: pointer; user-select: none;
}
.bg-check input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; }
.bg-meter input[type="number"] {
  width: 4ch; padding: 2px 4px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 4px; color: var(--fg);
  font-family: inherit; font-size: 11px; font-weight: 600; text-align: center;
}
.bg-spacer { flex: 1; min-width: 8px; }
.bg-hint { font-size: 10.5px; color: var(--muted); flex-basis: 100%; }

/* Click track options: inline in the Click track cluster, always visible once
   a track has a beat grid (#269 follow-up -- no popover, no click to reveal
   them). Everything shares the 34px control height of the clusters beside it;
   .daw-footer is min-height (not a fixed height) so a wrap never clips. */
.footer-metro-panel {
  /* display:contents removes this element's own box and promotes its
     children (the count-in switch, accent select, grid button, rate segment,
     volume) to be direct flex items of .footer-group-body, as peers of the
     on/off pill -- one flat wrap group the browser can line-break wherever
     the available width actually allows, instead of the pill being forced
     alone onto its own row and everything else confined to a second,
     artificially narrow one (#269 follow-up). .hidden below still works: it
     carries !important, which overrides this when the panel has no grid to
     show options for. */
  display: contents;
}
.footer-metro-panel.hidden { display: none; }

/* First answer to a row that has run out of width: the options stay exactly
   where #269 put them and stay visible, they just stop being one long line.

   The width is set by footerFit.js, not written here, and that is the point.
   A constant cannot be right: the row is short by a different number of pixels
   at every window size and with the library open or shut, so a fixed cap
   either leaves the options in a tall column when a shallow wrap would have
   done, or frees far more width than the row needed and leaves the surplus
   standing as a hole between the last cluster and Export Mix. The fallback
   here is only for the frame before the first measurement lands.

   `display: contents` above promotes the options to be peers of the on/off
   pill, which is what makes them one wide line. Giving the panel its own box
   back is the whole mechanism: it becomes a single flex item of the measured
   width, and its contents wrap inside it.

   .daw-footer is min-height rather than a fixed height precisely so a taller
   row is accommodated instead of clipped. */
.footer-clusters.collapse-wrap .footer-metro-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  width: var(--metro-wrap-w, 380px);
}
/* Nothing to wrap when the track has no beat grid. `.hidden` carries
   !important, so it already wins; this is belt and braces against that
   changing. */
.footer-clusters.collapse-wrap .footer-metro-panel.hidden { display: none !important; }

/* Narrow windows only, and only once wrapping to half the panel's inline width
   is still too wide. See footerFit.js for why the threshold is measured rather
   than written as a breakpoint.

   This used to cap the panel at 190px and let it wrap in place. That kept
   every option on screen without a click, which is what #269 asked for, but on
   a 15" laptop with the library open it wrapped into a five-row column: six
   controls stacked under a one-line footer, roughly 150px tall, with the rest
   of the row sitting beside a wall of them. Wrapping is the right answer for a
   couple of controls and the wrong one for six.

   So at this size only, the options move into a popover, opened from a
   disclosure beside the on/off pill.
   Nothing is removed and nothing changes behaviour -- the same panel, one
   click away, and back inline the moment the row has room for it again.

   Fixed, not absolute: .footer-clusters is overflow-x: auto, which computes
   overflow-y to auto as well, so an absolutely positioned panel would be
   clipped by the strip it belongs to. Fixed escapes that (no ancestor here
   creates a containing block), and transport.js sets left/top from the
   trigger's rect -- the same shape as .search-panel below. */
.footer-clusters.collapse-click .footer-metro-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
  position: fixed;
  z-index: 300;
  /* 260px takes the count-in and meter selects side by side on the first row,
     so the popover is two short rows rather than one control per line. */
  width: 260px;
  padding: 10px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,0.42);
}
/* Shut until the trigger opens it. `.hidden` (no beat grid at all) carries
   !important and still wins over both. */
.footer-clusters.collapse-click .footer-metro-panel:not(.open) { display: none; }
.footer-clusters.collapse-click .footer-metro-panel.hidden { display: none !important; }

/* The popover frees more width than the row was short -- the click cluster
   goes from about 450px to about 140px in one step, and there is no smaller
   step to take. Left-aligned, the surplus pools as a hole between the last
   cluster and Export Mix. Spread, it reads as a band of controls across the
   footer instead. Only at this level: with the options inline the clusters
   already fill the row, and an overflowing flex row falls back to flex-start
   on its own, so the scrolling case is untouched. */
.footer-clusters.collapse-click { justify-content: space-between; }

/* The trigger itself only exists at this size: inline, the options are already
   on screen and a disclosure for them would be a button that does nothing. */
.click-more-btn { display: none; }
.footer-clusters.collapse-click .click-more-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 34px; padding: 0;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 8px; color: var(--fg-2); cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.footer-clusters.collapse-click .click-more-btn:hover { background: var(--panel-3); color: var(--fg); }
.footer-clusters.collapse-click .click-more-btn[aria-expanded="true"] {
  border-color: var(--accent); color: var(--accent);
}
.footer-clusters.collapse-click .click-more-btn svg { transition: transform var(--t-fast); }
.footer-clusters.collapse-click .click-more-btn[aria-expanded="true"] svg { transform: rotate(180deg); }
/* No grid, no options, nothing to disclose. */
.footer-clusters.collapse-click .footer-metro-panel.hidden + .click-more-btn { display: none; }

/* Last resort before the strip would scroll: close up the spacing. Nothing is
   hidden or moved, so it costs the user nothing but density. */
/* Only the gaps between clusters. Reaching into .footer-group-body as well was
   worth a few pixels and cost more than it saved: at (0,3,0) it outranked
   `.footer-group-position .footer-group-body { gap: 4px }` at (0,2,0), so the
   widest cluster on the row got 2px roomier at the exact moment this is trying
   to reclaim width. Eight gaps at 8px instead of 16px is the win here. */
.footer-clusters.collapse-tight { gap: 8px; }

/* Click volume: a pill like everything else on the row, opening a vertical
   fader. It was a 116px slider with a percentage after it -- the only control
   on a row of 34px pills that was neither, so nothing lined up with it, and
   the widest single item in the cluster that runs out of room first. A fader
   is also the shape a level actually has.

   It sits beside the on/off pill rather than among the options, so it stays on
   the row at the widths where the rest goes behind the disclosure. */
.metro-vol-wrap { display: inline-flex; flex: none; }
.metro-vol-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 8px; color: var(--fg-2); cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.metro-vol-btn:hover { background: var(--panel-3); color: var(--fg); }
.metro-vol-btn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
.metro-vol-icon { flex-shrink: 0; }

/* Fixed rather than absolute for the same reason the options popover is:
   .footer-clusters is overflow-x: auto, which computes overflow-y to auto as
   well, so an absolutely positioned panel would be clipped by the strip its
   button sits in. transport.js sets left/top from that button's rect. */
.metro-vol-panel {
  position: fixed; z-index: 320;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 10px 8px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,0.42);
}
.metro-vol-panel.hidden { display: none !important; }
.metro-vol-slider {
  /* The standard vertical range (Chrome 121, Safari 17.4, Firefox 129) with
     the older WebKit/Blink keyword behind it. On a browser that honours
     neither, the input stays a horizontal slider inside the popover: smaller
     than it should be, but still a working volume control. */
  -webkit-appearance: slider-vertical;
  writing-mode: vertical-rl;
  direction: rtl;
  width: 20px; height: 92px;
  accent-color: var(--accent);
  cursor: pointer;
}
.metro-val {
  font-family: var(--font-mono); font-size: 11px; color: var(--muted);
  width: 4ch; text-align: right;
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}
/* Native select, restyled to the same pill as the buttons around it: the
   caret is drawn by the wrapper so no appearance-dependent arrow shows. */
.metro-select-wrap { position: relative; display: inline-flex; }
.metro-select-wrap::after {
  content: ""; position: absolute; right: 10px; top: 50%;
  margin-top: -2px; pointer-events: none;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid var(--muted);
}
.metro-select {
  appearance: none; -webkit-appearance: none;
  height: 34px; padding: 0 26px 0 10px; max-width: 168px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 8px; color: var(--fg); cursor: pointer;
  font-family: inherit; font-size: 12px; font-weight: 500; outline: none;
}
.metro-select:hover { background: var(--panel-3); }
.metro-select:focus { border-color: var(--accent); }

/* An armed count-in tints its select the way the toggle button it replaced
   used to tint itself (#587), so the panel still reads at a glance. */
.metro-select-wrap.active .metro-select {
  border-color: var(--accent);
  color: var(--accent);
}

/* Free-entry meter, revealed by the "Custom..." option. Sized to three digits
   so it sits next to the select rather than stretching the footer row. */
.metro-custom-beats {
  width: 58px; height: 34px; padding: 0 6px 0 8px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 8px; color: var(--fg);
  font-family: inherit; font-size: 12px; font-weight: 500; outline: none;
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}
.metro-custom-beats:hover { background: var(--panel-3); }
.metro-custom-beats:focus { border-color: var(--accent); }

/* Beat grouping for the current meter, e.g. 3+2+2 (#595). Shown only when the
   meter has more than one sensible reading. */
.metro-group {
  width: 74px; height: 34px; padding: 0 8px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 8px; color: var(--fg);
  font-family: inherit; font-size: 12px; font-weight: 500; outline: none;
  font-variant-numeric: tabular-nums; flex-shrink: 0; text-align: center;
}
.metro-group:hover { background: var(--panel-3); }
.metro-group:focus { border-color: var(--accent); }
.metro-group::placeholder { color: var(--muted); }
/* A refused grouping: the box snapping back on its own reads as a bug unless
   something says the numbers did not add up. */
.metro-group.invalid { border-color: var(--danger); color: var(--danger); }
/* Detection confidence / unavailability note: its own line under the
   waveform, where it reads as a caption on the timeline it describes (design
   1b). Hidden until it has text, clipped to one line with the full text as a
   native tooltip. */
.metro-note {
  /* Starts where the controls it describes start.
     It used to inherit its left edge from .footer-wave-region, which insets
     the gutter the now-playing block filled. That block is in the composer now
     and the gutter with it, so the note was left against the bare edge of the
     footer, 18px to the left of Transport and lined up with nothing. */
  margin-top: 8px;
  margin-left: 18px;
  font-size: 11px; line-height: 1.3; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Holds its line even while it has nothing to say.
 *
 * This text arrives with the beat analysis, a second or so after a track opens,
 * and it used to arrive as a layout change: `display: none` while empty, then
 * 14px of text plus its margin, which took 22px off the wave panel underneath.
 *
 * That matters because the lane heights are decided from the panel's height
 * and then baked into the multitrack when its tracks are built. On the first
 * track of a session the lanes are built before this line appears, so they
 * keep the taller row while the mixer, sized in CSS from --lane-h, follows the
 * shorter one. The two columns then drift by 3px a row, all the way down the
 * stack, and a stem's name ends up on a different line from its own waveform.
 * Every later track is built after this line already exists, which is exactly
 * why the fault only ever showed on the first one.
 *
 * Reserving the line removes the change rather than reacting to it. The
 * alternative was re-measuring after the fact, and that does not work: a
 * lane's height reaches the DOM through the multitrack's own render, and
 * setOptions on the wavesurfer afterwards does not move the row it built.
 */
.metro-note:empty {
  display: block;
  visibility: hidden;
}
.metro-note:empty::before { content: "\00a0"; }
.metro-note.warn { color: var(--danger); }

/* Export chip button */
.track-chip-wrap {
  position: relative; flex-shrink: 0;
  display: flex; align-items: center;
}
.track-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  color: var(--fg); border-radius: 8px; font-size: 12px; font-weight: 500;
  font-family: inherit; cursor: pointer; padding: 0 12px; height: 36px;
  white-space: nowrap;
  transition: background var(--t-fast);
}
.track-chip:hover { background: var(--panel-3); }
.track-chip-primary {
  background: var(--accent); color: #1a1206;
  border-color: transparent;
}
.track-chip-primary:hover { background: color-mix(in srgb, var(--accent) 85%, white); }
.track-chip-primary:disabled {
  background: var(--panel-2); color: var(--fg-muted);
  border-color: var(--border-strong); cursor: not-allowed; opacity: 0.5;
}

.track-chip-panel {
  /* Opens up and to the right: the button now lives in the left column, and a
     right-aligned panel would hang past the footer's left edge over the
     sidebar. */
  position: absolute; bottom: calc(100% + 6px); left: 0; z-index: 30;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  border-radius: 10px; padding: 4px;
  min-width: 120px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.track-chip-panel.hidden { display: none !important; }

.chip-panel-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 7px 12px;
  background: none; border: none; border-radius: 7px;
  color: var(--fg); font-size: 13px; font-family: inherit;
  cursor: pointer; text-align: left; white-space: nowrap;
}
.chip-panel-item:hover { background: var(--panel-3); }
.chip-panel-item.active { color: var(--accent); font-weight: 600; }

/* ── Export split-button dropdown ── */
/* The whole button toggles the menu, so the caret is purely a decorative
   open/close indicator (no separate hit area). */
.export-caret { opacity: 0.8; }
/* Primary button busy state: swap the download icon for a spinner. */
.export-spinner { display: none; }
.track-chip.is-busy .export-dl-icon { display: none; }
.track-chip.is-busy .export-spinner { display: inline-block; animation: sections-spin 700ms linear infinite; }

.chip-panel-export { min-width: 260px; padding: 6px; }

/* WAV / MP3 segmented toggle in the panel header */
.export-format-toggle {
  display: flex; gap: 3px; padding: 3px;
  margin-bottom: 4px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 8px;
}
.export-fmt {
  flex: 1; padding: 5px 0; border: none; border-radius: 6px;
  background: transparent; color: var(--muted);
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  letter-spacing: 0.04em; cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.export-fmt:hover { color: var(--fg-2); }
.export-fmt.active { background: var(--panel-3); color: var(--accent); }

/* Action rows — two-line: icon + (title/desc) + trailing check/count */
.export-item {
  align-items: center; gap: 11px;
  padding: 9px 10px; white-space: normal;
}
.export-item .chip-item-icon {
  flex-shrink: 0; width: 22px;
  display: flex; align-items: center; justify-content: center;
  color: var(--muted);
}
.export-item .chip-item-text {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
}
.chip-item-title { font-size: 13px; font-weight: 500; color: var(--fg); }
.chip-item-desc { font-size: 11px; color: var(--muted); }
.chip-item-check { color: var(--accent); flex-shrink: 0; }
.export-item:hover:not([aria-disabled="true"]) { background: var(--panel-3); }
.export-item[aria-disabled="true"] { opacity: 0.4; pointer-events: none; }

/* MP4 is offered as an export format only when the job has a preserved video
   track (mp4 upload or YouTube). player.js toggles .has-video on the wrap. */
/* Opt-in rather than automatic: baking a permanent click into a bounce you
   meant to be clean is not obvious until you play it back. Applies to every
   format, audio and video alike. */
.export-click-opt {
  display: flex; align-items: center; gap: 7px;
  margin: 2px 4px 4px; padding: 5px 6px;
  border-radius: 6px; cursor: pointer; user-select: none;
  font-size: 11.5px; font-weight: 600; color: var(--fg-2);
}
.export-click-opt:hover { background: var(--panel-3); color: var(--fg); }
.export-click-opt input[type="checkbox"] { accent-color: var(--accent); cursor: pointer; }
.export-click-opt.disabled { opacity: 0.4; cursor: not-allowed; }
.export-click-opt.disabled:hover { background: none; color: var(--fg-2); }

/* ── Live search dropdown (topbar) ────────────────────────────────
   Anchored to .url-wrap, which is position:relative already for the drag
   overlay. contain:layout paint keeps a long result list from forcing the
   composer above it to re-layout while rows stream in. */
/* Lives on <body>, not in .url-wrap: the composer clips its children
   (overflow:hidden for the rounded pill), which made an absolutely positioned
   panel invisible. JS sets left/top/width from the composer's rect. */
.search-panel {
  position: fixed; z-index: 300;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,0.42);
  overflow: hidden; contain: layout paint;
}
.search-panel.hidden { display: none; }
.search-tabs { display: flex; gap: 2px; padding: 5px; border-bottom: 1px solid var(--border); }
.search-tab {
  flex: 1; border: 0; background: transparent; cursor: pointer;
  padding: 5px 8px; border-radius: 6px; color: var(--muted);
  font-size: 11px; font-weight: 600; white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast);
}
.search-tab:hover { background: var(--panel-3); color: var(--fg); }
.search-tab.active { background: var(--panel-3); color: var(--accent); }
.search-status { padding: 12px 12px; font-size: 11.5px; color: var(--muted); }
.search-status.hidden { display: none; }
.search-list {
  list-style: none; margin: 0; padding: 4px;
  max-height: 340px; overflow-y: auto; overscroll-behavior: contain;
}
.search-row {
  display: flex; flex-direction: column;
  padding: 6px; border-radius: 7px; cursor: pointer;
}
.search-row-main { display: flex; align-items: center; gap: 9px; }
.search-row:hover, .search-row.active { background: var(--panel-3); }
.search-thumb {
  flex-shrink: 0; width: 64px; height: 36px; object-fit: cover;
  border-radius: 4px; background: var(--panel-2);
}
.search-thumb-empty { display: block; }
.search-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.search-title {
  font-size: 12px; font-weight: 600; color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.search-meta {
  font-size: 10.5px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Over the duration limit the pipeline refuses the job, so the row is not
   selectable at all rather than merely warning about it. */
.search-row.too-long { cursor: not-allowed; opacity: 0.55; }
.search-row.too-long:hover { background: none; }
.search-warn {
  flex-shrink: 0; font-size: 10px; font-weight: 600; color: var(--danger);
  white-space: nowrap;
}

/* Audition controls. Hidden until the row is hovered or playing, so a list of
   eight results is not eight competing buttons. */
/* A labelled pill, not a bare glyph. An unlabelled circle on a search result
   reads as "play this result", which is the one thing it does not do: it
   auditions, it does not select. Always visible for the same reason -- a
   control you have to hover to discover is not a control most people find. */
.search-preview-btn {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: 5px;
  height: 26px; padding: 0 10px 0 8px;
  border: 1px solid var(--border-strong); border-radius: 13px;
  background: var(--panel-2); color: var(--fg-2); cursor: pointer;
  font-family: inherit; font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.01em; white-space: nowrap;
  opacity: 0.75;
  transition: opacity var(--t-fast), color var(--t-fast),
              background var(--t-fast), border-color var(--t-fast);
}
.search-row:hover .search-preview-btn,
.search-row.active .search-preview-btn,
.search-preview-btn:focus-visible { opacity: 1; }
.search-preview-btn:hover { background: var(--panel-3); color: var(--fg); border-color: var(--fg-2); }
.search-preview-btn.playing {
  opacity: 1; color: var(--accent); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.search-player {
  display: flex; align-items: center; gap: 9px;
  margin: 7px 2px 1px; padding-left: 73px; cursor: default;
}
.search-player.loading .search-seek { opacity: 0.4; }
.search-player.failed .search-clock { color: var(--danger); }
.search-play {
  flex-shrink: 0; width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: var(--accent); color: #0b1118; cursor: pointer;
}
.search-seek {
  flex: 1; min-width: 0; height: 4px; accent-color: var(--accent);
  cursor: pointer;
}
.search-clock {
  flex-shrink: 0; font-family: var(--font-mono); font-size: 10px;
  color: var(--muted); font-variant-numeric: tabular-nums;
}

.export-fmt-video { display: none; }
#footer-export-wrap.has-video .export-fmt-video { display: block; flex: 1; }

/* Shown only when the video fetch actually failed, never when the source
   simply has no video stream. An absent MP4 button is normal; a broken one
   the user was never told about is not (#436). */
.export-video-failed { display: none; }
#footer-export-wrap.video-failed .export-video-failed {
  display: block;
  margin: 2px 4px 4px; padding: 5px 6px;
  font-size: 11px; line-height: 1.4; color: var(--muted);
}

/* In MP4 mode only "Export Mix" applies — the audio-only Stems/Region rows
   are hidden. main.js toggles .fmt-mp4 on the panel. */
#t-export-panel.fmt-mp4 #t-export-stems,
#t-export-panel.fmt-mp4 #t-export-region { display: none; }

/* ── About dialog ── */
.about-backdrop {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  background: rgba(3,8,13,0.6); backdrop-filter: blur(6px);
}
.about-backdrop.hidden { display: none !important; }
.about-card {
  width: min(300px, calc(100vw - 32px));
  background: var(--panel-2); border: 1px solid var(--border-strong);
  border-radius: 18px; box-shadow: 0 24px 64px rgba(0,0,0,0.6);
  padding: 28px 24px 24px; text-align: center;
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 0;
}
.about-close {
  position: absolute; top: 12px; right: 12px;
  width: 26px; height: 26px;
  background: transparent; border: 1px solid var(--border);
  border-radius: 6px; color: var(--muted);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.about-close:hover { background: var(--panel-3); color: var(--fg); }
.about-logo {
  width: 56px; height: 56px; border-radius: 14px;
  background: linear-gradient(135deg, rgba(139,92,246,0.2), rgba(244,183,64,0.15));
  border: 1px solid rgba(139,92,246,0.25);
  display: flex; align-items: center; justify-content: center;
  color: var(--accent); margin-bottom: 14px;
}
.about-card h2 { margin: 0 0 4px; font-size: 18px; font-family: var(--font-mono); font-weight: 700; }
.about-tagline { margin: 0 0 12px; font-size: 12px; color: var(--muted); }
.about-version-badge {
  display: inline-block; font-size: 11px; font-family: var(--font-mono);
  color: var(--muted); background: var(--panel); border: 1px solid var(--border);
  border-radius: 20px; padding: 2px 10px; margin-bottom: 20px;
}
.about-primary-links {
  display: flex; gap: 8px; width: 100%; margin-bottom: 16px;
}
.about-link {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 10px; border-radius: 8px; font-size: 12px;
  text-decoration: none; transition: background 0.15s;
}
.about-link-primary {
  background: var(--panel); border: 1px solid var(--border);
  color: var(--fg-2); font-weight: 600;
}
.about-link-primary:hover {
  background: var(--accent); border-color: var(--accent); color: #000;
}
.about-link-secondary {
  background: var(--panel); border: 1px solid var(--border);
  color: var(--fg-2);
}
.about-link-secondary:hover { background: var(--accent); border-color: var(--accent); color: #000; }
/* The in-app update pill: sits beside the plain Download link and is the
   recommended action, so it carries the accent fill by default rather than
   only on hover. */
.about-link-accent {
  background: var(--accent); border: 1px solid var(--accent);
  color: #000; font-weight: 600; cursor: pointer;
}
.about-link-accent:hover { filter: brightness(1.08); }
.about-link-accent:disabled { opacity: 0.55; cursor: default; filter: none; }
.about-divider {
  width: 100%; height: 1px; background: var(--border); margin-bottom: 16px;
}
.about-socials {
  display: flex; gap: 10px; align-items: center; justify-content: center;
}
.about-social-btn {
  width: 36px; height: 36px; border-radius: 8px;
  background: var(--panel); border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); text-decoration: none; transition: all 0.15s;
}
.about-social-btn:hover { background: var(--accent); border-color: var(--accent); color: #000; }

/* ── Release notes dialog ── */
.release-card {
  width: min(520px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  display: flex;
  flex-direction: column;
  align-items: center;
}
.release-notes {
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  text-align: left;
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  color: var(--fg-2);
  font-size: 12px;
  line-height: 1.5;
}
.release-notes h4 {
  margin: 12px 0 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--fg);
}
.release-notes h4:first-child { margin-top: 0; }
.release-notes p { margin: 0 0 8px; }
.release-notes ul { margin: 0 0 8px; padding-left: 18px; }
.release-notes li { margin: 2px 0; }
.release-notes a { color: var(--accent); text-decoration: none; overflow-wrap: anywhere; }
.release-notes a:hover { text-decoration: underline; }
.release-notes code {
  font-family: var(--font-mono);
  font-size: 11px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 4px;
}
.release-notes pre {
  margin: 0 0 8px;
  padding: 8px 10px;
  overflow-x: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.release-notes pre code { border: 0; background: none; padding: 0; }
.release-notes blockquote {
  margin: 0 0 8px;
  padding: 8px 12px;
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: rgba(139, 92, 246, 0.08);
}
.release-notes blockquote > :last-child { margin-bottom: 0; }
.release-callout {
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.06em;
  margin: 0 0 6px !important;
}
/* Server/Docker mode: image-pull guidance in place of a desktop download. */
.release-docker {
  width: 100%;
  text-align: left;
  margin: 0 0 16px;
}
.release-docker-label {
  margin: 0 0 6px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent);
}
.release-docker-text { margin: 0 0 8px; font-size: 12px; color: var(--fg-2); }
.release-docker-cmd {
  margin: 0 0 8px;
  padding: 8px 10px;
  overflow-x: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg);
}
.release-docker-note { margin: 0; font-size: 11px; color: var(--muted); }

/* Windows desktop: in-app download + apply, in place of a browser download. */
.release-inapp { width: 100%; text-align: left; margin: 0 0 16px; }
.release-inapp-progress-bar {
  height: 3px; border-radius: 2px; margin-bottom: 6px;
  background: rgba(255, 255, 255, 0.09); overflow: hidden;
}
.release-inapp-progress-fill {
  height: 100%; width: 0; border-radius: 2px;
  background: var(--accent);
  transition: width 260ms linear;
}
/* Indeterminate: the updater cannot listen to the Rust progress event from this
   origin (see catalog.js), so the bar shows activity rather than a percentage. */
.release-inapp-progress.indeterminate .release-inapp-progress-fill {
  width: 40%;
  animation: release-inapp-slide 1.1s ease-in-out infinite;
}
@keyframes release-inapp-slide {
  0%   { margin-left: -40%; }
  100% { margin-left: 100%; }
}
.release-inapp-progress-text { margin: 0; font-size: 11px; color: var(--muted); }
.release-inapp-error { margin: 0; font-size: 12px; color: #e54e4e; }

/* The release dialog lives outside `.daw`, so the scoped `.daw .hidden` rule
   does not reach its inner elements and base.css's global `.hidden` is not
   loaded on this page. Hide the mode-specific parts explicitly (same approach
   as `.about-backdrop.hidden`) so desktop hides the docker block and server
   hides the download button. */
.release-docker.hidden,
.release-inapp.hidden,
.release-inapp-progress.hidden,
.release-inapp-error.hidden,
.release-card .about-link.hidden {
  display: none !important;
}

/* The "New release available" card opens the dialog on click. */
.daw-notif-release { cursor: pointer; }

/* ── Failure dialog ── */
/* Same shell as the release dialog, in the danger colour, with the technical
   block the report will carry shown verbatim -- nothing is sent that the user
   has not been able to read first. */
.failure-card {
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  display: flex;
  flex-direction: column;
  align-items: center;
}
.failure-logo { color: var(--danger); }
.failure-when { color: var(--muted); }
.failure-message {
  margin: 12px 0 0;
  font-size: 13px; line-height: 1.5; color: var(--fg-2);
  white-space: pre-line; text-align: center;
}
.failure-hint {
  margin: 10px 0 0;
  font-size: 11px; line-height: 1.5; color: var(--muted);
  text-align: center;
}
.failure-tech {
  width: 100%; flex: 1 1 auto; min-height: 0;
  margin: 12px 0 0; padding: 10px 12px;
  overflow: auto; text-align: left;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
}
.failure-tech code {
  font-family: var(--font-mono); font-size: 11px; line-height: 1.55;
  color: var(--fg-2); white-space: pre-wrap; overflow-wrap: anywhere;
}
/* Opt-in, not automatic: this pulls extra log content into what's about to be
   copied into a public report, so it gets its own deliberate click rather
   than firing every time the dialog opens (matches .library-editor-sync's
   amber "extra action" treatment). */
.failure-logs-btn {
  align-self: flex-start;
  min-height: 28px; margin-top: 8px; padding: 0 12px;
  border-radius: 7px; border: 1px solid rgba(244, 183, 64, 0.3);
  background: rgba(244, 183, 64, 0.16); color: var(--accent);
  font-family: var(--font-mono); font-size: 11px; cursor: pointer;
}
.failure-logs-btn:disabled { opacity: 0.55; cursor: default; }
/* Reporting is the reason this dialog exists, so it gets the app's primary
   button treatment (as Export Mix does) rather than the muted link style the
   About/release dialogs use for their several equal-weight links. */
.failure-card .about-link-primary {
  background: var(--accent); border-color: transparent; color: #1a1206;
}
.failure-card .about-link-primary:hover {
  background: color-mix(in srgb, var(--accent) 85%, white);
  color: #1a1206;
}

/* ── Library sections (Recent · Stem Collections · Tags) ── */
.lib-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: 14px;
}
.lib-section + .lib-section {
  border-top: 1px solid var(--border);
}
.lib-section-head {
  padding: 0 4px 6px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Tag chips */
/* ── Tag autocomplete dropdown ── */
.tag-suggest {
  position: absolute;
  top: calc(100% + 4px);
  left: 0; right: 0;
  z-index: 50;
  background: var(--panel-2);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  padding: 4px;
  list-style: none;
  margin: 0;
  box-shadow: var(--shadow-panel);
}
.tag-suggest:empty { display: none; }
.tag-suggest-item {
  padding: 7px 10px;
  border-radius: 5px;
  font-size: 13px;
  color: var(--fg-2);
  cursor: pointer;
}
.tag-suggest-item:hover,
.tag-suggest-item.focused {
  background: var(--panel-3);
  color: var(--fg);
}

.lib-tags-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 2px 0 8px;
}
.lib-tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 11px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 20px;
  font-size: 12px;
  color: var(--fg-2);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
  white-space: nowrap;
}
.lib-tag-chip:hover {
  background: var(--panel-2);
  color: var(--fg);
  border-color: rgba(255,255,255,0.15);
}
.lib-tag-chip.active {
  background: rgba(244,183,64,0.12);
  color: var(--accent);
  border-color: rgba(244,183,64,0.3);
}
.lib-tag-count { color: var(--muted); font-size: 11px; }

/* Supporters ("We Recommend" dialog): category sections, each a small uppercase
   accent label with a rule, over a responsive grid of partner cards. */
/* Wide enough for two columns of three cards. The single column it replaced
   was 420px and always scrolled: five sections stacked is taller than any
   normal window, so half the people on the list were never seen. */
/* Wide enough that a four-person category is one row rather than two, which is
   what was pushing the list past the dialog and putting a scrollbar on it. */
.friends-card { width: min(1020px, calc(100vw - 32px)); }
.friends-card .lib-friends-grid { width: 100%; margin-top: 4px; }
.lib-friends-grid {
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  gap: 0 18px;
  align-items: start;
  text-align: left;
  padding: 2px 2px 0 0;
  /* Nothing here is meant to be selected or dragged: these are link tiles, and
     a stray drag across two of them selects half the dialog and leaves it
     highlighted. */
  user-select: none;
  -webkit-user-select: none;
  /* Kept as a floor, not as the normal case: the list is meant to fit, but a
     very short window still has to reach the bottom of it somehow. */
  max-height: min(88vh, 900px);
  overflow-y: auto;
}
.lib-friends-grid img {
  -webkit-user-drag: none;
  user-select: none;
  pointer-events: none;
}
.lib-friends-col { min-width: 0; }
.lib-friends-split { align-self: stretch; background: var(--border); }
/* One column again once two would squeeze the cards below three per row. */
@media (max-width: 860px) {
  .lib-friends-grid { grid-template-columns: 1fr; }
  .lib-friends-split { display: none; }
}
.lib-friends-cat {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px 0 7px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
}
.lib-friends-col > .lib-friends-cat:first-child { margin-top: 2px; }
/* Thin rule filling the width left over by the label. */
.lib-friends-cat::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}
.lib-friends-row {
  display: grid;
  /* ~3 per row in the card's default width, dropping to 2 as it narrows. */
  grid-template-columns: repeat(auto-fit, minmax(108px, 1fr));
  gap: 6px;
}
.lib-friend {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  min-width: 0;
  padding: 10px 6px 9px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--fg-2);
  text-decoration: none;
  text-align: center;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.lib-friend:hover {
  background: var(--panel-2);
  color: var(--fg);
  border-color: rgba(255,255,255,0.15);
}
/* Fixed-height slot so names line up across a row whichever of the three
   shapes an entry ends up with: round avatar, wordmark, or monogram. */
.lib-friend-media {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 44px;
}
/* Wordmark logos are transparent and not square: letterbox them into the slot. */
.lib-friend-logo {
  max-width: 100%;
  max-height: 40px;
  object-fit: contain;
}
/* Profile photos are square: crop to a circle. */
.lib-friend-avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--border);
}
/* Monogram fallback when a card has no image (or it fails to load): matches the
   round avatar size, in the accent colour, so the grid stays on-brand. */
.lib-friend-monogram {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 600;
  color: var(--accent);
  background: var(--panel-3);
  border: 1px solid var(--border);
}
/* Small link glyph at the foot of the card. margin-top:auto pins it to the
   bottom so it lines up across cards with different role lengths. */
.lib-friend-ig,
.lib-friend-link {
  /* block + no-shrink avoids the WebKit baseline clip on small inline SVGs */
  display: block;
  flex: 0 0 auto;
  width: 13px;
  height: 13px;
  margin-top: auto;
  padding-top: 3px;
  opacity: 0.6;
}
.lib-friend-ig { fill: currentColor; }
.lib-friend-link {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.lib-friend:hover .lib-friend-ig,
.lib-friend:hover .lib-friend-link { opacity: 1; }
.lib-friend-name {
  width: 100%;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--fg);
  /* full name across up to 2 lines, then ellipsis */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.lib-friend-role {
  width: 100%;
  font-size: 9.5px;
  line-height: 1.3;
  color: var(--muted);
  /* show the full role; the card grows to fit it */
}

/* ── Clear bin bar (trash view only) ── */
.clear-bin-bar { display: none; padding: 6px 10px 4px; }
.sidebar.trash-view .clear-bin-bar { display: block; }
.sidebar.trash-view .lib-section-head .new-folder-btn { display: none; }
.sidebar.favorites-view .lib-section-head .new-folder-btn { display: none; }
.clear-bin-btn {
  display: flex; align-items: center; gap: 5px;
  width: 100%; padding: 6px 10px; border-radius: 6px;
  background: rgba(229, 78, 78, 0.12); border: 1px solid rgba(229, 78, 78, 0.28);
  color: #e54e4e; font-size: 12px; font-weight: 500; cursor: pointer;
  transition: background 150ms, border-color 150ms;
}
.clear-bin-btn:hover { background: rgba(229, 78, 78, 0.22); border-color: rgba(229, 78, 78, 0.5); }
.clear-bin-btn:active { background: rgba(229, 78, 78, 0.32); }

/* ── App state classes (set by JS) ── */
/* No track loaded: hide main waveform content */
.daw.is-import .daw-track-header,
.daw.is-import .daw-section-ribbon,
.daw.is-import .daw-wave-header,
.daw.is-import .daw-content { opacity: 0.3; pointer-events: none; }
.daw.is-import .daw-content .mixer-column { opacity: 1; pointer-events: auto; }

.daw.no-track .daw-track-header,
.daw.no-track .daw-section-ribbon,
.daw.no-track .daw-wave-header,
.daw.no-track .daw-content { opacity: 0.3; pointer-events: none; }
/* Overlay and job progress live inside .daw-content but must stay fully visible while processing */
.daw.no-track .daw-content .wave-loading-overlay,
.daw.no-track .daw-content .job { opacity: 1; pointer-events: auto; }

.app.no-track #t-export-btn,
.app.is-import #t-export-btn {
  opacity: 0.35;
  pointer-events: none;
  cursor: default;
}

/* When track is playing */
.daw-play-btn .pause-icon { display: none; }

/* ── Responsive: hide zoom in small windows ── */
@media (max-width: 900px) {
  .daw-info-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .daw-track-card { grid-column: 1 / -1; width: 100%; border-right: none; border-bottom: 1px solid var(--border); }
  .stem-presence-panel { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .stem-card[data-stem="vocals"] { grid-column: span 1; }
}

/* Settings -> Logs. Read-only: paths, sizes and what writes each file. Log
   *contents* are deliberately not served -- a traceback can capture anything,
   and the files are on the machine the user is already sitting at. */
.settings-logs-dir {
  margin: 2px 2px 10px;
  padding: 8px 10px;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 6px;
}
.settings-logs-path {
  font-size: 11.5px; color: var(--fg-2); word-break: break-all; user-select: all;
}
.settings-logs-list { display: flex; flex-direction: column; gap: 6px; }
.settings-log-row {
  padding: 8px 10px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 6px;
}
.settings-log-main {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.settings-log-name { font-size: 12px; font-weight: 600; color: var(--fg); }
.settings-log-meta {
  font-size: 10.5px; color: var(--muted); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.settings-log-desc { margin-top: 3px; font-size: 11px; color: var(--muted); line-height: 1.4; }
.settings-logs-empty { font-size: 11.5px; color: var(--muted); padding: 6px 2px; }

.settings-export-logs {
  padding: 6px 12px;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  border-radius: 7px; color: var(--fg-2); cursor: pointer;
  font-family: inherit; font-size: 12px; font-weight: 600; white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast);
}
.settings-export-logs:hover:not(:disabled) { background: var(--panel-3); color: var(--fg); }
.settings-export-logs:disabled { opacity: 0.5; cursor: default; }

/* Stems location (#354). The path is long, so this row stacks instead of
   sitting on one line with the label. */
.settings-row-stack { display: block; }
.settings-btn {
  padding: 6px 12px;
  background: var(--panel-2); border: 1px solid var(--border-strong);
  border-radius: 7px; color: var(--fg-2); cursor: pointer;
  font-family: inherit; font-size: 12px; font-weight: 600; white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast);
}
.settings-btn:hover:not(:disabled) { background: var(--panel-3); color: var(--fg); }
.settings-btn:disabled { opacity: 0.5; cursor: default; }
.stems-location {
  display: flex; align-items: center; gap: 8px; margin-top: 8px;
}
.stems-location-path {
  flex: 1; min-width: 0;
  padding: 6px 8px; border-radius: 6px;
  background: var(--panel); border: 1px solid var(--border);
  color: var(--fg-2); font-family: var(--font-mono); font-size: 11px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.stems-location-size {
  flex-shrink: 0; font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.stems-location-msg {
  margin-top: 6px; font-size: 11px; line-height: 1.4; color: var(--muted);
}
.stems-location-msg.error { color: var(--danger); }
.stems-location-msg.ok { color: var(--accent); }

/* Cookies path field (#432). Full width rather than the 84px num input: this
   holds an absolute filesystem path, which is long and worth reading back. */
.settings-text-input {
  width: 100%; box-sizing: border-box; margin-top: 8px;
  background: rgba(10,17,24,0.6); border: 1px solid var(--border-strong);
  border-radius: 7px; color: var(--fg);
  font-family: var(--font-mono); font-size: 12px; padding: 6px 9px;
}
.settings-text-input:focus { outline: none; border-color: rgba(244,183,64,0.5); }
.cookies-file-msg {
  margin-top: 6px; font-size: 11px; line-height: 1.4; color: var(--danger);
}

/* Logs sub-navigation: Location / Application / Setup. */
.settings-subtabs {
  display: flex; gap: 2px; margin: 0 0 10px;
  border-bottom: 1px solid var(--border-strong);
}
.settings-subtab {
  padding: 5px 10px; background: none; border: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  color: var(--muted); cursor: pointer;
  font-family: inherit; font-size: 11.5px; font-weight: 600;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.settings-subtab:hover { color: var(--fg-2); }
.settings-subtab.active { color: var(--accent); border-bottom-color: var(--accent); }
/* The pane is a flex column and the registry/log textareas rely on flex:1 to
   fill it, so the subpane has to be one too -- otherwise the textarea collapses
   to its default character width and the lines get cut off. */
.settings-subpane { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.settings-subpane.hidden { display: none; }
/* The tail views are monospace and taller than the registry dump: log lines
   are long and wrap badly. */
.settings-logtail-view {
  width: 100%; box-sizing: border-box;
  min-height: 260px; white-space: pre; overflow-wrap: normal; overflow-x: auto;
}

/* ── Per-lane transpose (the K stepper) ────────────────────────────────────
   Sits in the row just left of M, with its arrows stacked above and below the
   reading, and drawn from the same kit as M and S: same border, same radius,
   same mono face, same width. It reads as one more control in that cluster
   rather than as a different kind of thing parked next to it.

   Stacked rather than inline because the lane row only has about 300px, and at
   the inline width the fader collapsed to 10px. The vertical form costs 22px
   instead of 55px, and the row has the height spare. */
.lane-key {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 22px;
  flex-shrink: 0;
}
/* The K is the button, and it is the same box as M and S: same size, same
   border, same radius, same mono face. Boxing the arrows instead put two
   look-alike buttons around a floating letter, which is the opposite of the
   family it belongs to -- M and S are each one box with one character in it. */
.lane-key-value {
  width: 22px;
  height: 20px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
/* Lit while the lane is off its own key, the way S lights when soloed. */
.lane-key.active .lane-key-value {
  background: rgba(244, 183, 64, 0.18);
  color: var(--accent);
  border-color: rgba(244, 183, 64, 0.4);
}

/* The arrows are the stepper around it, not two more buttons: no border, so
   they stay quiet next to the three boxes they sit beside. */
.lane-key-step {
  width: 22px;
  height: 11px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 3px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  font-family: var(--font-mono);
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--t-fast), background var(--t-fast);
}
.lane-key-step:hover:not(:disabled) {
  color: var(--fg);
  background: rgba(255, 255, 255, 0.07);
}
.lane-key-step:disabled { opacity: 0.3; cursor: default; }
/* Drums, and anything else that must never be resampled. */
.lane-key.locked,
.lane-key.unsupported { opacity: 0.35; }
.lane-key.locked .lane-key-step,
.lane-key.unsupported .lane-key-step { cursor: not-allowed; }

/* ─────────────────────────────────────────────────────────────────────────
   The composer, in two rows.

   It was one row: the search, the Extract chips and the two actions all
   competing for the same horizontal space, with the Collapse row on a line of
   its own underneath. Three lines of bar for two lines of content, and on a
   narrow window the search and the chips squeezed each other.

   It is a grid now. Row one is the import gesture, left to right: upload, the
   search field, the loaded track, then the two things that act on it. Row two
   is Extract and its chips, with the Collapse row sharing that line at the
   right-hand end.

   The search and the chips share column one, which is what makes them exactly
   the same width. As a flex row the search grew into whatever was left over
   and ran past the last chip; sharing a track means the chips size the column
   and the search follows, in any language.
   ───────────────────────────────────────────────────────────────────────── */

/* 1. Two rows, arranged as in the mockup.

   Row one is the whole import gesture, left to right: upload, the search
   field, then the two things that act on it, Detect structure and Split
   stems. Row two is Extract and its chips, on their own full-width line.

   Done with order and flex-basis rather than by moving markup. The stem
   section is the only child pushed to a second line, so it is the only one
   that needs a basis of 100%, and everything else stays on line one in DOM
   order. Nothing here restyles a control: same pills, same buttons, same
   colours, only where they sit.

   The style in the mockup that is deliberately NOT copied: Detect structure
   as a switch with a help icon, and the search field boxed separately from
   upload. Those are a restyle, and you asked for the organisation only. */
.daw-composer {
  /* A grid, not a wrapping row, so the search and the chips share one column
     and are therefore exactly the same width. As a flex row the search grew to
     whatever was left over, which is why it ran past the last chip.

     Columns: content | spacer | rule | detect | split. Row one is the import
     gesture, row two is what to extract. */
  height: auto;
  display: grid;
  /* minmax(0, 1fr), not 1fr: a grid track's automatic minimum is its
     min-content, so a plain 1fr refuses to shrink below the widest thing in
     it. The card sat at its natural width and pushed Split stems off the right
     edge of its own container. Zero as the floor lets the column give way, and
     the card's own text clips with an ellipsis rather than forcing the row. */
  grid-template-columns: minmax(0, max-content) minmax(0, 1fr) auto auto auto;
  align-items: center;
  row-gap: 8px;
  column-gap: 10px;
  padding: 8px 10px;
  overflow: visible;
}

/* Row one, column one. Starts on the EXTRACT label rather than on the All
   chip, so the row has no indent to pay for, and stretches to end where the
   chips end. 14px is the stem section's own left padding, not a measured
   English width, so this alignment holds in every language. */
.daw-composer > .daw-url-zone {
  --row1-indent: 14px;
  grid-column: 1;
  grid-row: 1;
  /* Both sides, not just the left. The column is sized by the stem section,
     which carries 14px of padding at each end, so a margin only on the left
     left the search running 14px past the Other chip. Matching it on the right
     puts the two edges on the same pixel. */
  margin-left: var(--row1-indent);
  margin-right: var(--row1-indent);
  background: var(--bg-2);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  height: 36px;
  min-height: 36px;
  padding: 0 10px 0 6px;
  gap: 6px;
}
/* Inside the box, flat against it, not a box of its own. order rather than
   markup: the button keeps its place in the DOM, so tab order and the drop
   handlers bound to .url-wrap are untouched. */
.daw-url-zone > .daw-upload-btn {
  order: -1;
  width: 26px;
  height: 26px;
  border: none;
  background: none;
}
.daw-url-zone #url {
  background: none;
  border: none;
  padding: 0;
  height: auto;
}

/* Row two, column one. This is what sizes the column, and therefore the search
   box above it. min-width: 0 is what lets it be narrower than the chips inside
   it: at max-content they are 655px, which on their own overflow a 1100px
   window once the two actions have been paid for.

   The chips used to be allowed to wrap here, which is how that overflow was
   absorbed before the row could fold. It cannot be allowed now: the rule was
   three classes deep and quietly outranked the two-class `flex-wrap: nowrap`
   the folding row declares for itself, so the row kept wrapping to a second
   line in any language whose words are longer than English (#673). */
.daw-composer > .daw-stem-section {
  grid-column: 1;
  grid-row: 2;
  min-width: 0;
}

/* The rule stays, but it now separates the search from the two actions on the
   same line, which is what it does in the mockup. It used to divide the row
   from the Extract chips, which are on their own line now. */
/* One rule, down both rows.
 *
 * There were two: this one beside the actions, and the Collapse label's own
 * left border under it. Two rules drawn by different elements have to be kept
 * level by hand, and they were a pixel apart, which is exactly the kind of gap
 * that reads as broken without being nameable. Spanning the rows makes it one
 * line that cannot drift from itself. */
.daw-composer > .daw-composer-sep {
  grid-column: 3;
  grid-row: 1 / 3;
  display: block;
  justify-self: center;
  align-self: stretch;
  width: 1px;
  height: auto;
  margin: 6px 4px;
  background: var(--border-strong);
}

/* Detect structure reads as a switch rather than a word you press. The button
   keeps its markup and its aria-pressed, so nothing about how it works
   changes; the pill and its knob are drawn from the two pseudo elements.
   Grid because the switch sits left of the label and spans both the label and
   the EXPERIMENTAL note under it. */
.daw-composer > .structure-toggle {
  grid-column: 4;
  grid-row: 1;
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: 9px;
  padding: 0 14px;
  text-align: left;
}
.daw-composer > .structure-toggle::before {
  content: "";
  grid-row: 1 / 3;
  width: 34px;
  height: 19px;
  border-radius: 999px;
  background: var(--panel-3);
  border: 1px solid var(--border-strong);
  transition: background var(--t-fast);
}
.daw-composer > .structure-toggle::after {
  content: "";
  position: absolute;
  left: 17px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--fg-2);
  transition: transform var(--t-fast), background var(--t-fast);
}
.daw-composer > .structure-toggle[aria-pressed="true"]::before {
  background: var(--accent);
  border-color: var(--accent);
}
.daw-composer > .structure-toggle[aria-pressed="true"]::after {
  transform: translateX(15px);
  background: #1a1a1a;
}
.daw-composer > .structure-toggle .structure-toggle-main { grid-column: 2; }
.daw-composer > .structure-toggle .structure-toggle-note {
  grid-column: 2;
  text-align: left;
}

/* Row two, and the only thing on it. A 100% basis is what forces the wrap. */
.daw-composer > .daw-stem-section {
  order: 1;
  flex: 1 0 100%;
  min-width: 0;
}

/* The two actions keep their shared width and stop being full-bleed. */
.daw-composer > .structure-toggle,
.daw-composer > .daw-process-btn {
  align-self: center;
  min-height: 40px;
  border-radius: 8px;
  border-left: none;
}
.daw-composer > .daw-process-btn {
  grid-column: 5;
  grid-row: 1;
}

/* 2. Ten percent off the waveform: REMOVED.

   It did what was asked, 910px down to 819px, and it read as a fault rather
   than as breathing room: the lanes stopped short while the analysis strip
   above and the footer below still ran to the window edge, so the waveform
   looked truncated rather than inset.

   To put it back, the rule was:

     .daw-wave-panel {
       flex: 1 1 0;
       max-width: calc((100% - 300px) * 0.9);
     }

   and it would need the strip and the footer inset to match, or something
   given the freed space. */

/* Row two, right-hand end: on the chips' line rather than below it.

   Columns 4 to 6, not 3: a spanning item's minimum lands in the first track it
   covers, so starting at the separator's column handed that 1px column the
   toggles' whole width and took it out of the card's. The two action columns
   are already as wide as Detect structure and Split stems, which is more than
   the toggles need, so spanning only those costs the row nothing.

   A grid item rather than absolutely positioned: out of flow it sat on the
   right line and would have landed on top of the Extract chips the moment a
   window was narrow enough for the two to meet. */
.daw-composer > .daw-panel-toggles {
  grid-column: 4 / 6;
  grid-row: 2;
  justify-self: end;
  align-self: center;
  width: auto;
  min-width: 0;
  margin: 0;
  /* The wrapper stops being a box of its own: each toggle is a chip now, so a
     frame around the group would be a box inside a box. */
  padding: 0;
  background: none;
  border: none;
  gap: 6px;
}

/* 4. The toggles become chips, the same size as the Extract ones.
   Everything below is the .stem-choice geometry, nothing else: same height,
   padding, radius, border and type size, so the two rows read as one family.
   The colours stay theirs, including the strike-through that means a panel is
   put away. */
.daw-composer-stack .daw-panel-toggle {
  height: 30px;
  padding: 0 11px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 12px;
  letter-spacing: -0.005em;
  text-transform: none;
  display: inline-flex;
  align-items: center;
}
/* The label and the separators were sized for an 8px row; at chip size the
   dots between them are noise, so only the label survives. */
/* No border of its own any more: the rule beside it spans both rows now, so a
   second one drawn here would be the thing it used to be kept level with. */
.daw-composer-stack .daw-panel-toggles-label {
  font-size: 12px;
  letter-spacing: -0.005em;
  text-transform: none;
  margin-right: 4px;
  padding: 0;
  border: none;
}
.daw-composer-stack .daw-panel-toggles-sep { display: none; }

/* 5. The dashed rectangles on empty lanes.

   Not ours. The vendored multitrack plugin draws one on any track with no
   audio: an absolutely positioned div at z-index 10 with dragover and drop
   handlers, so a file can be dragged onto an empty lane. In StemDeck an empty
   lane is a stem that was not extracted, and dropping audio onto one is not a
   thing the app does, so it is an affordance for a feature that does not
   exist.

   Hidden rather than patched out of the vendor file: that file is refreshed
   from upstream, and an edit to it would be lost on the next update. The
   selector matches on the inline style because the plugin gives these divs no
   class of their own. */
.daw .wave-scroll div[style*="2px dashed"] {
  display: none !important;
}

/* 6. The loaded track, named in the composer.

   It was in the footer, filling the gutter beside the timeline, which existed
   because that timeline's left edge had to line up with the lane waveforms.
   The timeline is gone, and this row had empty space in the middle of it, so
   the block moved in the markup rather than being pinned here: it is a cell in
   the grid, column two, spanning both rows. That is the column the search and
   the chips do not use, between them and the actions on the right. */
#nowPlayingPanel {
  position: relative;
  grid-column: 2;
  grid-row: 1 / 3;
  /* As wide as the track it is naming, not as wide as the window.
     It was stretched to fill its column, so a spare 400px of bar became 400px
     of card with the same text sitting in it. It sizes to its content now and
     stops; a long title clips with an ellipsis at the cap rather than dragging
     the card out to meet it. */
  justify-self: start;
  /* A real ceiling, not a percentage of the column: 100% just meant "as wide
     as the bar allows", so the card was 422px at 1600 and 460 at 1920 with the
     same text in it. 460 is what the meta line needs at its longest, duration
     through to the extracted date, so the card is the same size at every
     window width and a long title clips rather than pushing it wider. */
  max-width: min(100%, 460px);
  align-self: stretch;
  /* .footer-track-slot is `width: var(--daw-col-w)`, which is the gutter width
     it was built to fill in the footer. Here the column decides the width, so
     the fixed one has to go or the card never shrinks and pushes Split stems
     out of the composer on a narrow window. */
  width: auto;
  min-width: 0;
  margin: 0 14px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  padding: 0 14px;
  background: var(--panel-2, var(--panel));
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* The footer clamps the title to two lines, because its gutter is narrow and
   a long name would otherwise be cut after three or four words. This card is
   wide and short, so two lines is what makes it look cramped: the title grows
   downwards and shoves the meta line onto the bottom edge. One line with an
   ellipsis instead, which is what .daw-track-title does everywhere else. */
#nowPlayingPanel .track-panel-title {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.25;
}

/* A quieter frame. At --border-strong on --bg-2 the card was the loudest box
   in the bar, competing with the search field beside it for a thing you read
   rather than use. */
#nowPlayingPanel {
  background: var(--panel-2, var(--panel));
  border-color: var(--border);
}

/* The footer's own 40px cover, untouched. */
#nowPlayingPanel .track-panel-head {
  gap: 11px;
  align-items: center;
  justify-content: flex-start;
}
#nowPlayingPanel .track-panel-info { flex: 1 1 auto; min-width: 0; }
/* A flex item's automatic minimum is its content, all the way down, so one
   missing min-width anywhere in this chain gives the card a floor it will not
   go below and the row overflows instead. The title and the meta line both
   clip with an ellipsis, so there is nothing to protect by refusing. */
#nowPlayingPanel .track-panel-head,
#nowPlayingPanel .daw-track-meta-line { min-width: 0; }
/* Part of the card's structure rather than a button placed on top of it.
 *
 * It has been tried two ways and both read as dropped in: on the title's line
 * it sat at the right edge with a gap in front of it, and centred on the card
 * it lined up with neither row. The trouble was never the height, it was that
 * a bordered button inside a bordered card is a box in a box, floating in
 * whatever space is left over.
 *
 * So it gets a segment of its own, full height, separated by the same hairline
 * the composer uses between its clusters. The icon then has a place rather than
 * a position, and the card reads as two parts: what the track is, and the one
 * thing you can do to it from here. */
#nowPlayingPanel .track-panel-head .daw-fav-btn {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  transform: none;
  margin: 0;
  width: 46px;
  height: auto;
  border: none;
  border-left: 1px solid var(--border);
  border-radius: 0 8px 8px 0;
  background: none;
  color: var(--muted);
}
#nowPlayingPanel .track-panel-head .daw-fav-btn:hover {
  background: var(--panel-3);
  color: var(--fg);
}
#nowPlayingPanel .track-panel-head .daw-fav-btn svg {
  width: 17px;
  height: 17px;
}
/* The text stops where the segment starts. */
#nowPlayingPanel .track-panel-info,
#nowPlayingPanel .daw-track-meta-line { padding-right: 56px; }

/* Source and quality keep their footer colours, which up here is four accents
   in one short line. Muted to one voice; the stems dot keeps its colour,
   because that one means ready rather than merely saying a word. */
#nowPlayingPanel .daw-track-meta-line #track-source,
#nowPlayingPanel .daw-track-meta-line #track-quality,
#nowPlayingPanel .daw-track-meta-line #t-stems-chip {
  color: var(--muted);
}
#nowPlayingPanel .daw-track-meta-line {
  /* Indented to start under the title rather than under the artwork, and kept
     to one line so the card holds its shape whatever the track is called. The
     footer lets it wrap, because its gutter is narrow and tall; this one is
     wide and short.

     Not indented under the title either: the 51px that would cost is the
     difference between the line fitting and ending in an ellipsis. */
  margin: 0;
  flex-wrap: nowrap;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 7. The whole waveform strip in the bottom bar.

   Read this before keeping it. The strip is three things in one: the waveform
   drawing, the time ticks, and the scrub, which is the footer's seek control
   and its only progress indicator. All three go together here, so after this
   the only way to move through a track is the lanes above, and the footer no
   longer shows where you are.

   If that turns out to be too much, hiding #footer-waveform alone removes the
   drawing and leaves the ticks and the seek working. */
.footer-wave-panel {
  display: none !important;
}

/* 8. The Timeline toggle.

   It hides .footer-wave-region, which wraps two things: the waveform strip,
   now hidden outright by 7, and the now-playing block, now pinned into the top
   bar by 6. A fixed-position element is still hidden when an ancestor is
   display: none, so the only thing pressing Timeline would do now is make the
   card in the top bar disappear. A control whose label names something that is
   not there, doing something the label does not describe.

   Two parts, and the second matters as much as the first. The button is hidden
   here, but it stays in the DOM, so wireAllToggle still counts "timeline"
   among the panels All governs: pressing All would set panel-timeline-off and
   take the card with it. Neutralising the rule is what stops that. Doing this
   properly means dropping the button from the markup and letting the All row
   govern two panels rather than three. */
.daw-panel-toggle[data-panel="timeline"],
.daw-panel-toggle[data-panel="timeline"] + .daw-panel-toggles-sep,
.daw-panel-toggles-sep:has(+ .daw-panel-toggle[data-panel="timeline"]) {
  display: none !important;
}
.app.panel-timeline-off .footer-wave-region {
  display: block;
}

/* 9. An upload's format where it has no artwork (#690).

   Drawn by formatIcon.js: a file on the note icon's grid, with the format on
   a band. One colour per format, so a library of uploads can be read at a
   glance. Chosen clear of the stem colours, so a band never reads as a stem. */
.format-icon-page {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linejoin: round;
}
.format-icon-band { fill: var(--format-colour, currentColor); }
.format-icon-label {
  fill: var(--panel);
  font-family: inherit;
  font-size: 5.6px;
  font-weight: 700;
  letter-spacing: 0.2px;
}
.format-icon[data-format="wav"]  { --format-colour: var(--accent); }
.format-icon[data-format="flac"] { --format-colour: #f28c6b; }
.format-icon[data-format="mp3"]  { --format-colour: #5b9cf5; }
.format-icon[data-format="m4a"]  { --format-colour: #2ec4b6; }
.format-icon[data-format="ogg"]  { --format-colour: #a78bfa; }
.format-icon[data-format="opus"] { --format-colour: #67d4ef; }
.format-icon[data-format="mp4"]  { --format-colour: #f472b6; }
/* The square beside the title: the size the note there is drawn at. */
.np-art-placeholder .format-icon { width: 24px; height: 24px; }

/* 10. The empty state's lanes, lined up with the mixer beside them.

   Two things were out. The placeholder lane is a hard 48px in waves.css while
   every mixer row is sized from --lane-h, which is 77px on an empty studio, so
   the two columns ran at different pitches from the first row. And there are
   seven placeholders, one per stem plus "original", against six visible mixer
   rows, because the original row is hidden until a job is actually loaded.

   So: take the height from the same variable the mixer uses, minus the 2px
   separator that makes up the rest of the row, and drop the original
   placeholder while its mixer row is hidden. */
.lane-placeholder {
  height: calc(var(--lane-h, 72px) - 2px);
  margin-bottom: 2px;
}
.app.no-track .lane-placeholder[data-stem="original"] {
  display: none;
}

/* Below about 1450px the two rows cannot hold everything.
 *
 * Measured rather than picked: the chips need 655px at their widest, the
 * toggles 280, Detect structure 164 and Split stems 140. With gaps and the
 * bar's own padding that is 1299px of composer before the card gets a pixel,
 * which is a window of roughly 1450 once the rail and the margins are paid
 * for.
 *
 * So the two things that were moved up here give way, in the order they can be
 * done without: the card first, since the track is also named in the library
 * and the mixer, then the toggles drop to a line of their own, which is where
 * they lived before. Nothing is lost at any width, it just stops being one
 * line.
 */
@media (max-width: 1460px) {
  .daw-composer > #nowPlayingPanel { display: none; }
}

/* The click-track options with nothing to act on.
 *
 * They stay on the row and go quiet rather than leaving it, which is how the
 * metronome button beside them and every unavailable lane in the mixer already
 * behave. Half a control strip appearing and vanishing reads as a fault. */
.footer-metro-panel.unavailable {
  opacity: 0.4;
  pointer-events: none;
}

/* Vocals and its two mode buttons, as one thing.
 *
 * Grouped by proximity, not by a box. A frame around them needed padding and
 * borders of its own, which made the group 38px tall in a row of 30px chips:
 * it hung 8px below everything beside it. Joining them instead costs no height
 * at all, and a segmented control is what this is.
 *
 * So the chip and the toggle touch, their facing corners square off, and the
 * gap that used to sit between them moves to the outside of the pair. */
.stem-group {
  display: inline-flex;
  align-items: center;
  gap: 0;
  margin-right: 5px;
}
/* Facing edges square, and only one border between them. */
.stem-group .stem-choice[data-stem="vocals"] {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.stem-group .vocal-mode-toggle {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-left: none;
}
/* The mode buttons take the Vocals chip's border, and only its border, so one
   outline runs round the three of them.
 *
 * The outline is the grouping cue; the fill is not available for it. Tinting
 * the whole toggle made "Lead + Backing" look selected when it is not, and
 * left the active mode barely distinguishable from the inactive one, because
 * that mode's own 16% was sitting on top of the group's 14%. Inside the
 * outline the three states stay readable: Vocals is tinted because the stem is
 * selected, the active mode is tinted because it is the mode, and the other
 * one is plain.
 *
 * Keyed off the chip's pressed state rather than on the toggle being present,
 * because it is always present: with Vocals off, the group drops back to the
 * plain border and the toggle dims beside it. */
.stem-group:has(.stem-choice[data-stem="vocals"][aria-pressed="true"]) .vocal-mode-toggle {
  --color: var(--vocals);
  border-color: color-mix(in srgb, var(--color) 60%, transparent);
}
/* The divider between All and Lead + Backing comes from the same colour, so it
   reads as an internal seam rather than a second frame. */
.stem-group:has(.stem-choice[data-stem="vocals"][aria-pressed="true"]) .vocal-mode-btn + .vocal-mode-btn {
  border-left: 1px solid color-mix(in srgb, var(--color) 60%, transparent);
}

/* ── The Extract chips, and what happens when they stop fitting ───────────
   The row never wraps and the bar never gets taller. As the window narrows,
   whichever chips no longer fit move into a panel behind a three-dot button,
   one at a time, from the end of the row. The button sits after the row, on the
   side the chips are leaving from, and is hidden entirely while everything
   fits.

   Wrapping was the alternative and it is the wrong one here: the composer went
   from 96px tall to 168 and then 236 as the chips spilled onto further lines,
   so the bar grew exactly as the window shrank, and the controls on the right
   were left stranded at the top of a column stretched by chips they have
   nothing to do with.

   The measuring is in fitStemChips in main.js; this is only what it looks
   like.
   ───────────────────────────────────────────────────────────────────────── */
.stem-more-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 34px;
  height: 30px;
  margin-left: 5px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: none;
  color: var(--muted-2);
  cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.stem-more-btn[hidden] { display: none; }
.stem-more-btn:hover { background: var(--panel-2); color: var(--fg); }
.stem-more-btn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }

/* One line, always. Overflow hidden so a chip mid-move is never half drawn,
   and so this element's clientWidth is the honest budget fitStemChips
   measures against. */
.daw-composer .daw-stem-chips {
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: nowrap;
  overflow: hidden;
}

/* Fixed rather than absolute, for the same reason the footer's options panel
   is: it has to escape the bar it is anchored in. JS sets left/top from the
   button's own rect. */
.stem-overflow {
  position: fixed;
  z-index: 320;
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  max-width: min(560px, calc(100vw - 32px));
  padding: 10px;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.42);
}
.stem-overflow.open { display: flex; }
