/*
 * jam.css
 *
 * Visual language for ToneForge Jam.
 *
 * The page lives in three states (intake → band room → performance) and
 * the layout is intentionally calm — large type, generous spacing, no
 * gradients. The product promise is "be inside the song", so chrome stays
 * out of the way.
 */

:root {
  --bg: #0b0d10;
  --panel: #14181d;
  --panel-2: #1c2128;
  --text: #f3f4f6;
  --text-dim: #8b95a5;
  --accent: #f97316;       /* warm orange */
  --accent-2: #fdba74;
  --good: #4ade80;
  --warn: #fbbf24;
  --bad: #f87171;
  --border: #232a33;
}

* { box-sizing: border-box; }
html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Helvetica Neue",
               Arial, sans-serif;
  font-size: 16px;
  height: 100%;
}

/* ---------------------------------------------------------------- header */
.jam-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 28px;
  border-bottom: 1px solid var(--border);
}
.brand {
  font-weight: 700;
  font-size: 18px;
  letter-spacing: 0.5px;
  color: var(--text);
  text-decoration: none;
}
.jam-header nav { display: flex; gap: 18px; }
.jam-header nav a {
  color: var(--text-dim);
  text-decoration: none;
  font-size: 14px;
  padding: 6px 10px;
  border-radius: 6px;
}
.jam-header nav a.active {
  color: var(--text);
  background: var(--panel);
}

/* ---------------------------------------------------------------- views */
#jam-app { padding: 32px 28px 80px; max-width: 1100px; margin: 0 auto; }
.view { display: none; }
.view.active { display: block; }

/* ---------------------------------------------------------- intake view */
.hero {
  font-size: 56px;
  font-weight: 700;
  margin: 56px 0 12px;
  line-height: 1.05;
}
.sub { color: var(--text-dim); font-size: 20px; margin: 0 0 24px; }

/* engine banner --------------------------------------------------------- */
.engine-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 18px;
  margin: 0 0 18px;
  max-width: 880px;
}
.engine-banner-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--text-dim);
  flex-shrink: 0;
}
.engine-banner-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.engine-banner-text strong { font-size: 14px; font-weight: 600; }
.engine-banner-text span { font-size: 12px; color: var(--text-dim); }
.engine-banner--unknown .engine-banner-dot { background: var(--text-dim); }
.engine-banner--off .engine-banner-dot { background: var(--bad); }
.engine-banner--starting .engine-banner-dot {
  background: var(--warn);
  animation: pulse 1.2s ease-in-out infinite;
}
.engine-banner--on { border-color: rgba(74, 222, 128, 0.35); }
.engine-banner--on .engine-banner-dot { background: var(--good); }
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
.intake {
  display: grid;
  grid-template-columns: 1fr 220px auto;
  gap: 12px;
  align-items: stretch;
  max-width: 880px;
}
.intake input, .intake select {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
  font-size: 16px;
  height: 52px;
  font-family: inherit;
}
.intake input:focus, .intake select:focus {
  outline: none;
  border-color: var(--accent);
}
.intake button {
  background: var(--accent);
  color: #1a0f06;
  border: none;
  border-radius: 10px;
  padding: 0 22px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  height: 52px;
}
.intake button:hover { background: var(--accent-2); }
.hint { color: var(--text-dim); font-size: 14px; margin-top: 16px; }
.linklike {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  padding: 0;
  font: inherit;
  text-decoration: underline;
}

/* ------------------------------------------------------- band-room view */
.bandroom-header { margin-bottom: 28px; }
.song-meta h2 { margin: 0 0 6px; font-size: 28px; }
.status { color: var(--text-dim); font-size: 15px; }
.progress {
  margin-top: 14px;
  height: 4px;
  background: var(--panel);
  border-radius: 2px;
  overflow: hidden;
}
.progress-bar {
  height: 100%;
  width: 0%;
  background: var(--accent);
  transition: width 0.4s ease;
}

.bandroom-stage {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 18px;
  aspect-ratio: 16 / 10;
  max-width: 900px;
  margin: 0 auto;
}
.slot {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  transition: background 0.3s, border-color 0.3s, transform 0.3s;
  opacity: 0.55;
}
.slot.ready { opacity: 1; border-color: var(--good); }
.slot.user-slot {
  background: var(--panel-2);
  border-color: var(--accent);
  opacity: 1;
}
/* Pin the user slot to dead center of the 3x3 grid */
.slot.center { grid-column: 2; grid-row: 2; }
/* Place the remaining four around the user */
.slot[data-stem="vocals"] { grid-column: 2; grid-row: 1; }
.slot[data-stem="keys"]   { grid-column: 1; grid-row: 1; }
.slot[data-stem="bass"]   { grid-column: 1; grid-row: 3; }
.slot[data-stem="drums"]  { grid-column: 3; grid-row: 3; }

.slot-icon { font-size: 36px; margin-bottom: 6px; }
.slot-name { font-size: 16px; font-weight: 600; }
.slot-state { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
.slot.ready .slot-state { color: var(--good); }

#bandroom-cancel { margin: 28px auto 0; display: block; }

/* ------------------------------------------------------- performance view */
.perform-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 24px;
}
.np-title { font-size: 22px; font-weight: 700; }
.np-meta { color: var(--text-dim); font-size: 14px; margin-top: 4px; }
a.np-source {
  display: inline-block;
  margin-top: 8px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  text-decoration: none;
  transition: border-color 120ms, color 120ms, background 120ms;
}
a.np-source:hover,
a.np-source:focus-visible {
  border-color: var(--accent);
  color: var(--text);
  background: rgba(249, 115, 22, 0.06);
}

.perform-stage {
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 24px;
  margin-bottom: 28px;
}
.user-card {
  background: var(--panel-2);
  border: 1px solid var(--accent);
  border-radius: 16px;
  padding: 20px;
}
.user-card-label {
  color: var(--text-dim);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.user-card-tone { font-size: 22px; font-weight: 700; margin: 6px 0; }
.user-card-meta { color: var(--text-dim); font-size: 13px; margin-bottom: 16px; }
.user-card-controls { display: flex; flex-direction: column; gap: 8px; }
#connect-btn.connected {
  background: var(--accent, #4ade80);
  color: #0a0a0a;
  border-color: var(--accent, #4ade80);
}
/* Brief visual pulse so a click on the Connect button always has an
   on-element response, even when the resulting state change is purely
   textual (e.g. "already paired — no preset to push"). Without this,
   the only feedback was a status flash that auto-cleared, which made
   the button feel dead. */
#connect-btn.clicked-pulse {
  animation: connect-btn-pulse 480ms ease-out;
}
@keyframes connect-btn-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(255, 122, 0, 0.55); }
  60%  { box-shadow: 0 0 0 10px rgba(255, 122, 0, 0); }
  100% { box-shadow: 0 0 0 0 rgba(255, 122, 0, 0); }
}
.connect-status {
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.4;
  padding: 2px 2px;
}
.connect-status.ok { color: var(--accent, #4ade80); }
.connect-launcher-link {
  display: inline-block;
  margin-top: 4px;
  color: var(--accent, #ff7a00);
  text-decoration: underline;
  font-weight: 500;
}
.connect-launcher-link:hover { text-decoration: none; }
.connect-restart-btn {
  display: inline-block;
  margin-top: 6px;
  padding: 4px 10px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-dim, #aaa);
  background: transparent;
  border: 1px solid var(--text-dim, #aaa);
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
}
.connect-restart-btn:hover {
  color: var(--accent, #ff7a00);
  border-color: var(--accent, #ff7a00);
}
.connect-restart-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
/* Phase 6: install / launch CTA inside the Connect status card.
   `connect-launch-btn` is the primary action when Connect.app is on
   disk but not paired; styled more prominently than the restart
   button (filled accent rather than outlined neutral) so it reads
   as the recommended next step. `connect-install-link` is the
   download CTA when Connect.app isn't installed yet. */
.connect-launch-btn {
  display: inline-block;
  margin-top: 6px;
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 600;
  color: #fff;
  background: var(--accent, #ff7a00);
  border: 1px solid var(--accent, #ff7a00);
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
}
.connect-launch-btn:hover {
  filter: brightness(1.1);
}
.connect-launch-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
.connect-launch-version {
  display: inline-block;
  margin-left: 6px;
  font-size: 11px;
  color: var(--text-dim, #aaa);
  letter-spacing: 0.3px;
}
.connect-install-link {
  display: inline-block;
  margin-top: 4px;
  color: var(--accent, #ff7a00);
  text-decoration: underline;
  font-weight: 500;
}
.connect-install-link:hover { text-decoration: none; }
.connect-gain {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}
.connect-gain label {
  font-size: 12px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.connect-gain input[type="range"] {
  width: 100%;
}
.connect-gain-readout {
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--text-dim);
  min-width: 48px;
  text-align: right;
}
.connect-gain-hint {
  grid-column: 1 / -1;
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.4;
}

.stem-rack {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.stem-row {
  display: grid;
  grid-template-columns: 100px 1fr 60px 60px;
  align-items: center;
  gap: 14px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--border);
}
.stem-row:last-child { border-bottom: none; }
.stem-row .name { font-weight: 600; font-size: 15px; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.stem-row .name.original-instrument { color: var(--accent); }
.stem-row .stem-match {
  font-weight: 400;
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stem-row .gain { -webkit-appearance: none; appearance: none; height: 4px; border-radius: 2px; background: var(--border); }
.stem-row .gain::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  height: 16px;
  width: 16px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
}

/* Mixer master channels (Song / Guitar input). Same row template as
 * stem rows so the CSS grid stays uniform; differentiation is purely
 * visual -- bolder name, brighter accent for Guitar input, and an
 * accent-coloured divider after the Guitar Input row that separates
 * master channels from per-stem channels. */
.stem-row.mixer-channel-row .name.mixer-channel-name {
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  font-size: 13px;
}
.stem-row.mixer-channel-song .name { color: var(--text); }
.stem-row.mixer-channel-guitar .name { color: var(--accent); }
.stem-row.mixer-channel-divider {
  border-bottom: 2px solid var(--accent);
  margin-bottom: 6px;
  padding-bottom: 12px;
}

/* ---------------------------------------------------------- transport */
.transport {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px 20px;
}
.transport-row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
}
.time {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  font-size: 14px;
}
.section-bar {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.section-pill {
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.section-pill:hover { border-color: var(--accent); }
.section-pill.active { background: var(--accent); color: #1a0f06; border-color: var(--accent); }

/* Phase-5 structural-role badge on section pills.
 *
 * Renders the ANCHOR/DEVELOPMENT/UNIQUE classification from
 * backend/structural_role_classifier_design.md as a single-letter
 * chip appended to the pill text. The pill TEXT still names the
 * detected section type (Verse 1, Chorus, etc.); the badge surfaces
 * how recurrent the chord trigrams in this section are versus the
 * rest of the song.
 *
 * Color semantics:
 *   ANCHOR      — warm accent (this is "the part you hear all song")
 *   DEVELOPMENT — neutral panel (variation on the anchor material)
 *   UNIQUE      — desaturated dim (one-off material: intro, bridge)
 *
 * Glyph stays 1 char to keep the bar legible on songs with many
 * sections.
 */
.section-role-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: 0.5px;
  vertical-align: middle;
  border: 1px solid var(--border);
}
.section-role-badge.role-anchor {
  background: var(--accent);
  color: #1a0f06;
  border-color: var(--accent);
}
.section-role-badge.role-development {
  background: var(--panel);
  color: var(--text);
  border-color: var(--border);
}
.section-role-badge.role-unique {
  background: transparent;
  color: var(--text-dim);
  border-color: var(--border);
}
/* When the pill itself is the active loop region, keep the badge
 * legible against the orange background by inverting only the
 * anchor variant (which would otherwise blend in). */
.section-pill.active .section-role-badge.role-anchor {
  background: #1a0f06;
  color: var(--accent);
  border-color: #1a0f06;
}
.section-pill.active .section-role-badge.role-development,
.section-pill.active .section-role-badge.role-unique {
  border-color: #1a0f06;
}

/* Fix B: suspicious-section indicator.
 *
 * When the duration-guard post-pass
 * (analysis/section_naming.py:flag_suspicious_durations) decides a
 * section's duration is structurally implausible for its label
 * (e.g. a 70s CHORUS that clearly ate several boundaries the
 * RMS-novelty detector missed), the pill gets an amber dashed
 * border plus a ⚠ glyph. The label itself is unchanged — this is
 * an advisory hint that the boundary detector under-segmented,
 * not a re-classification.
 *
 * Amber (not red) so it doesn't compete visually with the active
 * loop pill (which uses --accent = orange). Dashed border makes
 * "something's provisional here" legible at a glance.
 */
.section-pill-suspicious {
  border-style: dashed;
  border-color: #d4a017;
}
.section-pill-suspicious:hover { border-color: #f0b820; }
.section-pill-suspicious.active {
  /* Keep the dashed provisional look even when this pill is the
   * active loop region — swapping to a solid orange border would
   * hide the fact that this section boundary is untrusted. */
  border-style: dashed;
  border-color: #d4a017;
}
.section-duration-warn {
  display: inline-block;
  margin-left: 6px;
  font-size: 11px;
  line-height: 1;
  color: #d4a017;
  vertical-align: middle;
  cursor: help;
}
.section-pill.active .section-duration-warn { color: #1a0f06; }

.loop-status { color: var(--text-dim); font-size: 13px; margin-top: 10px; }

/* ----------------------------------------------------- chord ribbon
 *
 * JAM Alpha visual reference. A horizontal strip of chord pills,
 * scrolled by `transform: translateX(...)` from updateChordPlayhead()
 * such that the currently-sounding chord sits under the fixed
 * playhead marker. `transition: none` on the transform is mandatory:
 * loop snap-back must jump, not animate. The pill currently active
 * gets `.chord-pill.active` (highlight only; no layout shift).
 */
.chord-ribbon {
  position: relative;
  margin: 16px 0;
  height: 64px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
  overflow: hidden;
}
.chord-ribbon-playhead {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--accent);
  z-index: 2;
  pointer-events: none;
}
.chord-ribbon-strip {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 8px;
  transform: translateX(0);
  /* No transition: loop snap-back must be instant. */
  transition: none;
  will-change: transform;
}
.chord-pill {
  flex: 0 0 auto;
  min-width: 56px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text);
  font-size: 16px;
  font-weight: 500;
  padding: 0 10px;
  white-space: nowrap;
}
.chord-pill.active {
  background: var(--accent);
  color: #1a0f06;
  border-color: var(--accent);
  font-weight: 700;
}

/* Guidance-mode visual gating intentionally still parked.
 *
 * The per-section classifier (analysis/guidance_mode.py) emits a
 * mode + confidence per section and JS reflects that on the chord
 * ribbon / lane via data-guidance-mode. The synthetic-fixture-tuned
 * thresholds over-classify real songs as riff/lead — flipping the
 * visuals on sections that are still musically chord-shaped — so
 * mode-based muting stays parked here as a no-op until the
 * classifier is calibrated on a real-song corpus.
 *
 * What IS shipped is the observation-based data-section-chordy
 * gate (no classifier dependency). JS computes chord count + rate
 * inside the active section in _applyGuidanceModeForActiveSection
 * and emits data-section-chordy="true|false". When chordy=false
 * the chord ribbon would be showing the rapid-flip garbage labels
 * that the chord detector produces on picked notes (Birds-of-Tokyo
 * failure mode), and the dual chord-bar / tab-lane display is hard
 * to follow. We hide the ribbon entirely so the tab lane stands
 * alone as the primary guidance. The chord-guidance NOW PLAYING
 * card stays visible because it carries shape/finger info that's
 * still useful when valid chord data exists (this is a no-op when
 * the card already shows nothing because there are no chords).
 */
#chord-ribbon[data-section-chordy="false"] { display: none; }

/* ---------------------------------------------------------- buttons */
button {
  font-family: inherit;
  cursor: pointer;
}
button.primary {
  background: var(--accent);
  color: #1a0f06;
  border: none;
  border-radius: 10px;
  padding: 10px 18px;
  font-weight: 600;
}
button.primary:hover { background: var(--accent-2); }
button.ghost {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 18px;
}
button.ghost:hover { border-color: var(--accent); }
button.big { font-size: 16px; padding: 12px 22px; }
#perform-settings {
  font-size: 20px;
  line-height: 1;
  padding: 6px 12px;
}
button[disabled] { opacity: 0.45; cursor: not-allowed; }

.intake-error {
  margin-top: 14px;
  padding: 10px 14px;
  background: rgba(211, 106, 69, 0.12);
  border: 1px solid #d36a45;
  border-radius: 10px;
  color: #f0a585;
  font-size: 14px;
}

/* ----------------------------------------------- Onboarding modal */
/* Device-discovery first-run prompt (§8). The modal sits as a sibling of
   <main> so the backdrop covers the whole viewport without inheriting the
   jam-app padding. Visible only when jam.js detects no persisted device
   preferences (GET /api/device/preferences → null). */
.onboarding-modal[hidden] { display: none !important; }
.onboarding-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
}
.onboarding-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(2px);
}
.onboarding-panel {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px 28px 22px;
  width: min(480px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.onboarding-title {
  font-size: 22px;
  font-weight: 700;
  margin: 0;
  line-height: 1.2;
}
.onboarding-sub {
  margin: 0;
  font-size: 14px;
  color: var(--text-dim);
}
.onboarding-options {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}
.onboarding-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  background: var(--panel-2);
  font-size: 15px;
  transition: border-color 120ms, background 120ms;
}
.onboarding-option:hover { border-color: var(--accent); }
.onboarding-option input[type="radio"] {
  accent-color: var(--accent);
  margin: 0;
}
.onboarding-option input[type="radio"]:checked + span { color: var(--text); font-weight: 600; }
.onboarding-detected-badge {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: rgba(80, 200, 120, 0.15);
  color: var(--good, #5c8);
  border: 1px solid rgba(80, 200, 120, 0.4);
}
.onboarding-error {
  color: var(--bad);
  font-size: 13px;
  margin-top: 4px;
}
.onboarding-input-row[hidden],
.onboarding-input-picker[hidden] { display: none !important; }
.onboarding-input-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim, #aaa);
  margin-top: 4px;
}
.onboarding-input-name { color: var(--text); font-weight: 600; }
.onboarding-input-change-link {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  padding: 0;
  font-size: 13px;
  text-decoration: underline;
}
.onboarding-input-picker {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
}
.onboarding-input-picker-label { font-size: 13px; color: var(--text-dim, #aaa); }
.onboarding-input-picker select {
  padding: 8px;
  background: var(--panel, #1a1a1a);
  color: var(--text);
  border: 1px solid var(--border, #333);
  border-radius: 4px;
}
#onboarding-submit { margin-top: 8px; align-self: flex-end; }

/* ----------------------------------------------- Settings popover */
.settings-popover {
  position: absolute;
  right: 20px;
  top: 70px;
  z-index: 20;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 18px;
  min-width: 260px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.settings-row + .settings-row { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.settings-row label { display: block; font-size: 14px; padding: 3px 0; cursor: pointer; }
.settings-label { font-weight: 600; font-size: 13px; margin-bottom: 6px; color: var(--text-dim); }
.settings-hint { font-size: 11px; color: var(--text-dim); margin-top: 2px; padding-left: 22px; }
.settings-hint--inline { padding-left: 0; margin: 0 0 6px; }
#setting-device-reset { font-size: 12px; padding: 4px 10px; }
.launchpad-status--idle { color: var(--text-dim); }
.launchpad-status--ok { color: #6ce07a; }
.launchpad-status--error { color: #e07a6c; }
.launchpad-openjam { margin-top: 6px; padding-left: 22px; }
.launchpad-openjam label { display: block; font-size: 12px; padding: 3px 0; }
.launchpad-openjam select { font-size: 12px; margin-left: 4px; }

/* ----------------------------------------------- Intonation panel */
.intonation-panel {
  margin: 12px 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 18px;
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 18px;
  align-items: center;
}
.intonation-main { display: grid; grid-template-columns: auto 1fr; gap: 18px; align-items: center; }
.intonation-note { text-align: center; min-width: 70px; }
.intonation-note-name { font-size: 28px; font-weight: 700; color: var(--text); line-height: 1; }
.intonation-cents { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
.intonation-meter {
  position: relative;
  height: 18px;
  min-width: 220px;
  display: flex;
  align-items: center;
}
.intonation-meter-track {
  position: absolute;
  inset: 8px 0;
  background: var(--border);
  border-radius: 2px;
}
.intonation-meter-center {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--text-dim);
  transform: translateX(-50%);
  opacity: 0.5;
}
.intonation-meter-needle {
  position: absolute;
  left: 50%;
  top: 2px;
  bottom: 2px;
  width: 4px;
  background: var(--accent);
  border-radius: 2px;
  transform: translateX(-50%);
  transition: left 80ms ease-out, background 200ms;
}
.intonation-meter-needle.flat { background: #d36a45; }
.intonation-meter-needle.sharp { background: #d36a45; }
.intonation-meter-needle.in-tune { background: #58c47a; }
.intonation-status { font-size: 11px; color: var(--text-dim); grid-column: 1 / -1; margin-top: 4px; }
.intonation-rolling { text-align: center; padding-left: 14px; border-left: 1px solid var(--border); }
.intonation-rolling-label { font-size: 11px; color: var(--text-dim); }
.intonation-rolling-pct { font-size: 20px; font-weight: 700; margin-top: 2px; }
.intonation-full { padding-left: 14px; border-left: 1px solid var(--border); font-size: 12px; min-width: 180px; }
.intonation-full-row { display: flex; justify-content: space-between; gap: 14px; padding: 2px 0; }
.intonation-full-label { color: var(--text-dim); }
.intonation-full-val { font-weight: 600; font-variant-numeric: tabular-nums; }
.intonation-full-divider { height: 1px; background: var(--border); margin: 6px 0; opacity: 0.6; }

/* ============================== tone card ============================== */
/* Monitor-chain ToneRecommendation surface inside .user-card. Tier
   badge colour communicates the confidence band at a glance:
     --tone-high   = green, ready to apply
     --tone-medium = amber, suggested
     --tone-low    = muted, low confidence
     --tone-unknown= grey, default chain
*/
.tone-card {
  margin: 12px 0 16px;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border, #2a2a2a);
}
.tone-card-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.tone-tier-badge {
  display: inline-block;
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  font-weight: 700;
  background: #2a2a2a;
  color: var(--text, #eee);
}
.tone-tier-badge--high    { background: #14532d; color: #bbf7d0; }
.tone-tier-badge--medium  { background: #78350f; color: #fde68a; }
.tone-tier-badge--low     { background: #3a3a3a; color: #d4d4d4; }
.tone-tier-badge--unknown { background: #2a2a2a; color: #9ca3af; }
.tone-card-title {
  font-weight: 600;
  font-size: 14px;
}
.tone-card-rationale {
  color: var(--text-dim, #aaa);
  font-size: 12px;
  line-height: 1.4;
  margin: 4px 0 10px;
}
.tone-card-actions {
  display: flex;
  gap: 8px;
}
.tone-card-alternates {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--border, #2a2a2a);
}
.tone-card-alternates-label {
  font-size: 10px;
  color: var(--text-dim, #aaa);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 4px;
}
.tone-card-alternates-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.tone-alternate-chip {
  border: 1px solid var(--border, #2a2a2a);
  background: rgba(255, 255, 255, 0.02);
  color: var(--text, #eee);
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 999px;
  cursor: pointer;
}
.tone-alternate-chip:hover {
  background: rgba(255, 255, 255, 0.08);
}

/* ============================================================
 * perform v2 — musician-first three-column layout
 * ============================================================
 *
 * Activated only when #view-perform is the visible view, via the
 * body.perform-active class toggled in jam.js showView(). The
 * intake and band-room views keep their existing 1100px max-width.
 */
body.perform-active #jam-app {
  max-width: 1440px;
  padding-bottom: 140px; /* room for the fixed playback bar */
}

/* hide the legacy perform-top header; the new header lives below */
body.perform-active #view-perform .perform-top { display: none; }
body.perform-active #view-perform .perform-stage { display: none; }
body.perform-active #view-perform .transport { display: none; }
body.perform-active #view-perform > .chord-ribbon { display: none; }

.perform-header {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
}
.perform-header .ph-song { min-width: 0; }
.perform-header .ph-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  position: relative;
}
.connect-pill {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  letter-spacing: 0.3px;
  transition: border-color 120ms, color 120ms, background 120ms;
}
.connect-pill:hover { border-color: var(--accent); }
.connect-pill.connected {
  border-color: var(--accent);
  color: var(--accent);
  background: rgba(249, 115, 22, 0.08);
}
.connect-pill.connecting {
  border-color: var(--warn);
  color: var(--warn);
}
.connect-pill.offline {
  border-color: var(--border);
  color: var(--text-dim);
}
/* Phase 1: distinct error styling. Reuses --err (red accent token)
   to separate "auto-reconnect exhausted" from the routine
   transient-offline state above. */
.connect-pill.errored {
  border-color: var(--err, #ff4d4d);
  color: var(--err, #ff4d4d);
}

.connect-monitor-pop {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 30;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  min-width: 240px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
}
.connect-monitor-pop[hidden] { display: none; }

/* grid */
.perform-grid {
  display: grid;
  /* 2-column layout: "Your slot" sidebar + everything else (chord
     ribbon, NOW PLAYING card, LEAD PART, and the relocated Stems
     mixer underneath). The prior 3-column layout had a separate
     right-hand stems column; that was moved into the centre column
     so the chord ribbon + chord card span the full available width. */
  grid-template-columns: 320px 1fr;
  gap: 24px;
  margin-bottom: 24px;
  align-items: start;
}
/* Launchpad tab: sidebar is meaningless (pads are the instrument),
   so collapse the first column and let the center reclaim the width.
   NB: `.stage-col { display: flex }` below has higher specificity than
   the UA's built-in `[hidden] { display: none }` rule, so we have to
   re-assert the hidden semantics explicitly. */
main.lp-tab-active .perform-grid {
  grid-template-columns: 1fr;
}
.stage-col[hidden] { display: none !important; }
.stage-col {
  background: var(--panel);
  border-radius: 16px;
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.stage-col.stage-center {
  background: transparent;
  padding: 0;
}
.stage-col-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--text-dim);
  font-weight: 600;
}

/* #slot-right is a <details> so the whole stem rack can collapse to
   the label row. Disclosure marker is a rotating caret before the
   label text (native ▶/▼ marker is hidden for a cleaner look). */
#slot-right > summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
#slot-right > summary::-webkit-details-marker { display: none; }
#slot-right > summary::before {
  content: '▾';
  display: inline-block;
  color: var(--text-dim);
  transition: transform 120ms ease;
  font-size: 10px;
}
#slot-right:not([open]) > summary::before { transform: rotate(-90deg); }
#slot-right:not([open]) { gap: 0; }
/* Belt-and-braces: `.stage-col { display: flex }` on <details> means
   the browser's native "hide non-summary children when not [open]"
   still works, but some layout combinations were rendering the rack
   even while collapsed. Explicitly hide the rack when closed. */
#slot-right:not([open]) > .stem-rack { display: none; }

/* left col: instrument selector */
.perform-instrument-selector {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.perform-instrument-selector select {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 14px;
  font-family: inherit;
}

/* left col: tuner-compact */
.tuner-compact {
  background: var(--panel-2);
  border-radius: 12px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.tuner-compact[hidden] { display: none; }
.tuner-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  justify-content: center;
}
.tuner-row #into-note {
  font-size: 32px;
  font-weight: 700;
  color: var(--text);
  line-height: 1;
}
.tuner-row #into-cents {
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.tuner-bar {
  position: relative;
  height: 14px;
  background: var(--border);
  border-radius: 7px;
}
.tuner-bar .tuner-bar-center {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--text-dim);
  opacity: 0.5;
  transform: translateX(-50%);
}
.tuner-bar #into-needle {
  position: absolute;
  left: 50%;
  top: 1px;
  bottom: 1px;
  width: 4px;
  border-radius: 2px;
  background: var(--accent);
  transform: translateX(-50%);
  transition: left 80ms ease-out, background 200ms;
}
.tuner-bar #into-needle.in-tune { background: var(--good); }
.tuner-bar #into-needle.flat,
.tuner-bar #into-needle.sharp { background: var(--bad); }
.tuner-compact #into-status {
  font-size: 11px;
  color: var(--text-dim);
  text-align: center;
}
.tuner-stats-toggle {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 12px;
  cursor: pointer;
}
.tuner-stats-toggle:hover { border-color: var(--accent); color: var(--text); }
.tuner-stats-body {
  border-top: 1px solid var(--border);
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tuner-stats-body[hidden] { display: none; }
/* Re-skin the embedded rolling / full panels for the compact layout */
.tuner-stats-body #into-rolling {
  padding-left: 0;
  border-left: none;
  text-align: left;
}
.tuner-stats-body #into-full {
  padding-left: 0;
  border-left: none;
  min-width: 0;
}

/* left col: tone-match */
.tone-match-card {
  background: var(--panel-2);
  border: 1px solid var(--accent);
  border-radius: 12px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tone-match-card .user-card-label {
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
}
.tone-match-card #user-tone-name {
  font-size: 18px;
  font-weight: 700;
}
.tone-match-card #user-tone-meta {
  color: var(--text-dim);
  font-size: 12px;
}
.tone-match-card .tone-card { margin: 8px 0 0; }
.tone-match-card #tone-send-connect {
  background: var(--accent);
  color: #1a0f06;
  border: none;
  border-radius: 10px;
  padding: 9px 14px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  margin-top: 6px;
}
.tone-match-card #tone-send-connect:hover { background: var(--accent-2); }
.tone-match-card #user-mute-original {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 10px;
  padding: 8px 12px;
  font-size: 12px;
  cursor: pointer;
  margin-top: 4px;
}
.tone-match-card #user-mute-original:hover { border-color: var(--accent); }

/* left col: audio-in row */
.audio-in-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-dim);
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
.audio-in-row .audio-in-label { color: var(--text-dim); }
/* `flex: 1 1 auto` lets the device name take leftover space but
 * shrink first when the indicators + Change button + latency need
 * room. `max-width: 20ch` fits "Line 6 HX Stomp" (15 chars) and
 * similar interface names in full while still capping pathological
 * labels so they can't monopolise the row.
 */
.audio-in-row #audio-in-name {
  color: var(--text);
  font-weight: 500;
  flex: 1 1 auto;
  min-width: 0;
  max-width: 20ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Spacer pushes the indicators + Change button to the right of the
 * (truncated) device name without forcing the name to grow. */
.audio-in-row .audio-in-spacer { flex: 1 1 auto; min-width: 4px; }

/* Phase 3 — lightweight in-row monitor-status indicators.
 *
 * These read out of state.monitor; the full monitor panel below the
 * row owns the controls. Sized small so the row stays a single line
 * and the chord strip remains the dominant element on screen.
 */
.audio-in-status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
  transition: background 0.18s ease, box-shadow 0.18s ease;
}
.audio-in-status-dot.audio-in-status-off { background: var(--border); }
.audio-in-status-dot.audio-in-status-on {
  background: #4caf50;
  box-shadow: 0 0 6px rgba(76, 175, 80, 0.55);
}
.audio-in-status-dot.audio-in-status-err {
  background: #e54b4b;
  box-shadow: 0 0 6px rgba(229, 75, 75, 0.55);
}
.audio-in-mini-meter {
  display: inline-block;
  position: relative;
  width: 42px;
  height: 6px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.08);
  flex: 0 0 auto;
  overflow: hidden;
}
.audio-in-mini-meter-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #4caf50 0%, #cdd54a 60%, #e54b4b 100%);
  transition: width 60ms linear;
}
/* CLIP indicator is a thin red bracket at the right edge of the
 * meter, not a wash across the whole bar. The underlying level
 * fill stays visible so the player can still see how much they're
 * clipping by. */
.audio-in-mini-meter-clip {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 4px;
  background: var(--bad);
  box-shadow: 0 0 4px var(--bad);
}
.audio-in-latency {
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}

/* --------------------------------------------- live guitar monitor */
/* Compact card under the audio-in row. Owns the play-along input
   path: device picker, enable toggle, gain, level meter, clipping
   indicator. Deliberately lightweight so the chord strip stays the
   primary surface.
*/
.monitor-panel {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
  font-size: 13px;
}
.monitor-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  /* Allow long status copy ("Off — Connect is using your input",
     "Connect is using your input — quit it to monitor here") to
     wrap to its own line instead of being ellipsed at 60% width. */
  flex-wrap: wrap;
}
.monitor-title {
  font-weight: 600;
  font-size: 13px;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  white-space: nowrap;
  flex: 0 0 auto;
}
.monitor-status {
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  min-width: 0;
  text-align: right;
  /* No truncation — see .monitor-header flex-wrap. The full status
     copy is always readable; if it doesn't fit next to the title
     it wraps to the next line within the header row. */
}
.monitor-status-ok { color: var(--good); }
.monitor-status-err { color: var(--bad); }

/* Audio-Ownership Pivot, Phase 5 — ownership-boundary disclosure.
 * Wraps the legacy #monitor-panel in <details id="audio-input-card">
 * so the browser-monitor controls collapse-by-default when Connect
 * is installed. The summary carries a status badge that mirrors
 * the four-state vocabulary. */
.audio-input-card {
  /* Reset native <details> chrome so the disclosure blends with the
     surrounding card style — we want the same visual weight as
     the previous bare div, plus a chevron. */
  margin: 0;
}
.audio-input-card-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  cursor: pointer;
  padding: 8px 10px;
  border-radius: 8px;
  list-style: none;
}
/* Hide the default disclosure marker; we'll rely on the <details>
   open/closed state class. */
.audio-input-card-summary::-webkit-details-marker { display: none; }
.audio-input-card-summary::marker { content: ''; }
.audio-input-card-summary:hover {
  background: rgba(255, 255, 255, 0.03);
}
.audio-input-card-title {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-dim, #aaa);
}
.audio-input-card-badge {
  font-size: 11px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border, #2a2a2a);
  color: var(--text-dim, #aaa);
  letter-spacing: 0.3px;
}
.audio-input-card-badge.is-paired {
  border-color: var(--good, #4caf50);
  color: var(--good, #4caf50);
}
.audio-input-card-badge.is-installed {
  border-color: var(--warn, #ff9800);
  color: var(--warn, #ff9800);
}
.audio-input-card-badge.is-fallback {
  border-color: var(--accent, #ff7a00);
  color: var(--accent, #ff7a00);
}
.audio-input-card-badge.is-missing {
  border-color: var(--border, #2a2a2a);
  color: var(--text-dim, #aaa);
}

/* Audio-Ownership Pivot, Phase 2 — fallback affordance.
 * Renders beneath the disabled "Launch Connect for low-latency
 * monitoring" toggle when Connect.app is installed but not paired.
 * Styled muted-secondary so it reads as a fallback, not the
 * recommended action. _syncMonitorVsConnect() owns visibility. */
.monitor-row-fallback {
  padding: 4px 0 0 0;
}
.monitor-fallback-link {
  color: var(--text-dim, #aaa);
  font-size: 12px;
  text-decoration: underline;
  text-decoration-color: var(--text-dim, #aaa);
  cursor: pointer;
}
.monitor-fallback-link:hover {
  color: var(--accent, #ff7a00);
  text-decoration-color: var(--accent, #ff7a00);
}

/* Audio-Ownership Pivot, Phase 3 — dual-latency card.
 * Sits inside #monitor-panel; will move into #audio-input-card
 * when Phase 5 lands the rename. Styled to feel like a nested
 * subsection of the monitor panel: lighter background, no border,
 * so it reads as "data" rather than "another card". */
.audio-latency-card {
  background: rgba(255, 255, 255, 0.03);
  border-radius: 8px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
}
.audio-latency-card-title {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-dim);
}
.audio-latency-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.audio-latency-row-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
.audio-latency-row-name {
  font-weight: 600;
  color: var(--text);
}
.audio-latency-recommended {
  font-size: 10px;
  color: var(--good);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.audio-latency-row-value {
  display: flex;
  gap: 6px;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.audio-latency-row-label {
  color: var(--text-dim);
}
.audio-latency-row-num {
  color: var(--text);
}
/* Greyed-out variant when the data source is unavailable
 * (Connect not paired, or AudioContext not constructed yet). */
.audio-latency-row.is-unavailable .audio-latency-row-num,
.audio-latency-row.is-unavailable .audio-latency-row-name {
  color: var(--text-dim);
  opacity: 0.7;
}

/* Audio-Ownership Pivot follow-up: LatencyProbe row.
 * Sits under the Connect Monitor row inside the same card. Shows
 * the ground-truth measured round-trip alongside the estimated
 * floor, plus a "Measure" button that triggers an impulse probe
 * on Connect. */
.audio-latency-measured {
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
  flex-wrap: wrap;
}
.audio-latency-measured-num {
  color: var(--text);
}
.audio-latency-measured-badge {
  font-size: 10px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 6px;
  border: 1px solid var(--border);
  color: var(--text-dim);
  white-space: nowrap;
}
.audio-latency-measured-badge.is-high {
  color: var(--good);
  border-color: var(--good);
}
.audio-latency-measured-badge.is-low {
  color: var(--warn, #c08a2c);
  border-color: var(--warn, #c08a2c);
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.audio-latency-measured-badge.is-nosignal {
  color: var(--err, #c0392b);
  border-color: var(--err, #c0392b);
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.audio-latency-measure-btn {
  margin-left: auto;
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.audio-latency-measure-btn:hover:not(:disabled) {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.audio-latency-measure-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.monitor-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.monitor-row > * { min-width: 0; }
.monitor-row-device .monitor-device-label {
  color: var(--text-dim);
  font-size: 11px;
}
.monitor-row-device .monitor-device-select {
  flex: 1;
  min-width: 0;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 6px;
  font-size: 12px;
}
.monitor-row-controls #monitor-toggle {
  /* Compact primary; keeps the panel from outgrowing slot-left. */
  padding: 6px 12px;
  font-size: 13px;
}
.monitor-row-controls #monitor-toggle.active {
  background: var(--good);
  border-color: var(--good);
  color: #0b0d10;
}
.monitor-row-controls #monitor-mute { font-size: 12px; padding: 6px 10px; }
.monitor-row-controls #monitor-mute:disabled { opacity: 0.45; cursor: not-allowed; }
.monitor-gain-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
}
.monitor-gain-label { color: var(--text-dim); font-size: 11px; flex: 0 0 auto; }
.monitor-gain-wrap .gain {
  flex: 1 1 0;
  min-width: 0;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
}
.monitor-gain-wrap .gain::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  height: 14px;
  width: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
}
.monitor-gain-readout {
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  font-size: 11px;
  min-width: 32px;
  text-align: right;
}
.monitor-row-meter {
  align-items: center;
  gap: 10px;
}
.monitor-meter {
  position: relative;
  flex: 1;
  height: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.monitor-meter-fill {
  position: absolute;
  inset: 0 auto 0 0;
  /* Green at low, yellow mid, red near top — gradient gives the
     "track to 0" mental model without needing per-frame colour
     calculations. */
  background: linear-gradient(
    90deg,
    var(--good) 0%,
    var(--good) 55%,
    var(--warn) 78%,
    var(--bad)  100%
  );
  width: 0%;
  transition: width 60ms linear;
}
.monitor-meter-clip {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 6px;
  background: var(--bad);
  box-shadow: 0 0 6px var(--bad);
  border-top-right-radius: inherit;
  border-bottom-right-radius: inherit;
}
.monitor-meter-clip::after {
  content: "!";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #0b0d10;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
}
.monitor-latency {
  font-size: 10px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  min-width: 70px;
  text-align: right;
}

/* center column: section bar + chord ribbon */
#slot-center .section-bar {
  margin-bottom: 10px;
  min-height: 28px;
}
#slot-center .chord-ribbon {
  height: 80px;
  margin: 0;
}
#slot-center .chord-pill {
  height: 56px;
  font-size: 18px;
  cursor: pointer;
  transition: transform 100ms;
}
#slot-center .chord-pill:hover { transform: translateY(-1px); }
#slot-center .chord-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
#slot-center .loop-status {
  margin-top: 10px;
  color: var(--text-dim);
  font-size: 13px;
}
.chord-snap-row {
  margin-top: 8px;
  font-size: 13px;
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.chord-snap-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  user-select: none;
}
.chord-snap-label input[type="checkbox"] {
  cursor: pointer;
  margin: 0;
}
/* Chord-lane stem selector: lives on the same row as the snap toggle
   and mirrors .chord-snap-label so the two controls read as a family.
   The <select> uses the same dim-on-dark palette as surrounding UI.
   Hidden via the [hidden] attribute when only one stem-lane exists
   (legacy bundles, single-source mixes). */
.chord-lane-stem-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  user-select: none;
}
.chord-lane-stem-label select {
  background: var(--bg-elev, #1c1c1c);
  color: var(--text, #e8e8e8);
  border: 1px solid var(--border, #333);
  border-radius: 4px;
  padding: 2px 6px;
  font-size: 13px;
  cursor: pointer;
}
.chord-lane-stem-label select:focus {
  outline: 1px solid var(--accent, #6aa6ff);
  outline-offset: 1px;
}

/* Queued chord diagrams row (Phase C of chord-diagram feature).
   4 slots showing current + next 3 chords. Slots flex-grow to fill
   the center column so the row reads as a "queue lane" rather than
   four floating thumbnails. Slot 0 (the active chord) gets a brighter
   --accent border + soft shadow so it pops from the ribbon's static
   colour. SVGs inside the slots use the same theme tokens as the
   surrounding UI (white nut/strings, --text for fret marks, --accent
   for the highlighted active slot). */
/* Chord guidance: three-card row — large NOW PLAYING focal card on
   the left + two smaller "UP NEXT" / "THEN" preview cards on the
   right. The previews show diagrams for activeIdx+1 and activeIdx+2
   so the player can see what's coming, while NOW PLAYING dominates
   visual weight to keep the eye anchored to "what do I play now". */
.chord-guidance {
  display: flex;
  gap: 16px;
  margin-top: 12px;
  padding: 0 8px;
  align-items: stretch;
}
.chord-now-block {
  flex: 1 1 50%;
  min-width: 0;
  border: 2px solid var(--accent);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: 0 0 0 1px var(--accent), 0 4px 12px rgba(249, 115, 22, 0.20);
  padding: 14px 16px 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
}
.chord-now-label {
  font-size: 11px;
  letter-spacing: 0.12em;
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  margin-bottom: 4px;
  align-self: flex-start;
}
.chord-now-symbol {
  font-size: 56px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: 0.02em;
  margin-bottom: 8px;
  line-height: 1;
}
.chord-now-diagram {
  flex: 0 0 auto;
  width: 100%;
  max-width: 520px;
  height: 380px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.chord-now-diagram svg {
  width: 100%;
  height: 100%;
  max-height: 380px;
  display: block;
}
/* Footer row inside NOW PLAYING — countdown on the left, transition
   hint on the right. On narrow viewports they stack via flex-wrap. */
.chord-now-footer {
  width: 100%;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 16px;
}
.chord-countdown {
  font-size: 14px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  min-height: 1.2em;
}
.chord-transition {
  font-size: 14px;
  color: var(--accent);
  font-weight: 600;
}
/* Phase 7 (JAM Learning V1) — per-section "Report wrong" affordance.
   Compact ghost button living in the chord-now-footer. The button is
   shown only when the current section has a defined section_id; the
   click handler captures the current chord symbol and POSTs to
   /api/learning/correct as evidence. */
.chord-now-report-wrong {
  font-size: 12px;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 8px;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease;
}
.chord-now-report-wrong:hover,
.chord-now-report-wrong:focus-visible {
  color: var(--text);
  border-color: var(--accent);
  outline: none;
}
.chord-now-report-wrong.is-busy {
  opacity: 0.5;
  pointer-events: none;
}
/* Up-next preview cards (activeIdx+1, +2). Smaller than NOW PLAYING
   with a dim border so the eye isn't tempted away from the focal
   card. The whole card is a <button> for click-to-seek; keyboard
   focus picks up the accent ring. */
.chord-up-next {
  flex: 1 1 25%;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
  padding: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.chord-up-next:hover,
.chord-up-next:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
  outline: none;
}
.chord-up-next-label {
  font-size: 10px;
  letter-spacing: 0.12em;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  margin-bottom: 4px;
  align-self: flex-start;
}
.chord-up-next-symbol {
  font-size: 28px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: 0.02em;
  margin-bottom: 6px;
  line-height: 1;
}
.chord-up-next-diagram {
  flex: 1 1 auto;
  width: 100%;
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.chord-up-next-diagram svg {
  width: 100%;
  height: 100%;
  max-height: 260px;
  display: block;
}
/* NOW PLAYING diagram is a click target for the voicing picker
   (§4). Hover affordance via subtle accent ring; cursor:pointer to
   communicate interactivity. */
.chord-now-diagram.is-clickable {
  cursor: pointer;
  border-radius: 8px;
  transition: box-shadow 120ms ease;
}
.chord-now-diagram.is-clickable:hover {
  box-shadow: 0 0 0 2px var(--accent);
}
/* Voicing picker popover — anchored under chord-now-block, opens on
   click of the NOW PLAYING diagram. */
.chord-now-block {
  position: relative;
}
.chord-voicing-popover {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 8px;
  z-index: 50;
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 8px;
  min-width: 180px;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.chord-voicing-header {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 2px 6px 6px;
  border-bottom: 1px solid var(--border);
}
.chord-voicing-option {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 6px 10px;
  margin: 0;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease;
}
.chord-voicing-option:hover,
.chord-voicing-option:focus-visible {
  background: rgba(249, 115, 22, 0.08);
  border-color: var(--accent);
  outline: none;
}
.chord-voicing-option.is-selected {
  background: rgba(249, 115, 22, 0.16);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
/* Overlay labels under NOW PLAYING (§8). Nashville + Roman sit
   inline above the diagram; the scale suggestion sits below. */
.chord-now-overlays {
  display: flex;
  gap: 10px;
  align-items: baseline;
  min-height: 20px;
  margin-bottom: 4px;
}
.chord-overlay-nashville {
  font-size: 15px;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.04em;
}
.chord-overlay-roman {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-dim);
  font-style: italic;
}
.chord-overlay-scales {
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-dim);
  text-align: center;
}
/* Advanced overlays toggle row — collapsed by default via <details>. */
.chord-overlays-row {
  margin-top: 8px;
  font-size: 12px;
  color: var(--text-dim);
}
.chord-overlays-details {
  background: transparent;
}
.chord-overlays-summary {
  cursor: pointer;
  user-select: none;
  padding: 4px 8px;
  border-radius: 6px;
  display: inline-block;
}
.chord-overlays-summary:hover {
  color: var(--accent);
}
.chord-overlay-option {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 12px;
  cursor: pointer;
}
.chord-diagram-empty {
  font-size: 12px;
  color: var(--text-dim);
  text-align: center;
  padding: 6px;
}

/* On narrow viewports stack the three guidance cards vertically so
   the focal card isn't squeezed and the up-next previews stay
   legible. */
@media (max-width: 900px) {
  .chord-guidance {
    flex-direction: column;
  }
  .chord-now-block,
  .chord-up-next {
    flex: 1 1 auto;
  }
}
@media (max-width: 600px) {
  .chord-now-block {
    padding: 10px 12px 12px;
  }
  .chord-now-symbol {
    font-size: 44px;
  }
  .chord-now-diagram {
    height: 260px;
  }
  .chord-now-diagram svg {
    max-height: 260px;
  }
}

/* SVG element styling — the renderer in chord_diagrams.js applies
   these class names to <line>/<circle>/<text>/<rect>. Default SVG
   paint is black, which is invisible on the dark panel; pin every
   stroke/fill to a theme token here so the diagrams read like the
   rest of the UI. */
.chord-diagram-title {
  fill: var(--text);
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
}
.chord-diagram-slot.is-active .chord-diagram-title,
.chord-diagram-svg.is-active .chord-diagram-title {
  fill: var(--accent);
}
.chord-diagram-string {
  stroke: var(--text-dim);
  stroke-width: 1.2;
}
.chord-diagram-fret {
  stroke: var(--text-dim);
  stroke-width: 1;
  opacity: 0.7;
}
.chord-diagram-nut {
  stroke: var(--text);
  stroke-width: 2.5;
}
.chord-diagram-dot {
  fill: var(--accent);
  stroke: none;
}
.chord-diagram-open {
  fill: none;
  stroke: var(--text);
  stroke-width: 1.2;
}
.chord-diagram-mute {
  fill: var(--text-dim);
  font-size: 9px;
  font-weight: 700;
  font-family: inherit;
}
.chord-diagram-finger {
  fill: #0b0d10;
  font-size: 7px;
  font-weight: 700;
  font-family: inherit;
}
.chord-diagram-barre {
  fill: var(--accent);
  opacity: 0.85;
}
.chord-diagram-fret-label {
  fill: var(--text-dim);
  font-size: 8px;
  font-family: inherit;
}
.chord-diagram-placeholder {
  fill: var(--text-dim);
  font-size: 14px;
  font-family: inherit;
}

/* Lead-tab lane: single full-width fretboard SVG showing the
   monophonic lead-line notes for the active chord window. */
.chord-tab-queue {
  margin-top: 12px;
  padding: 10px 12px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
  min-height: 110px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.chord-tab-label {
  font-size: 11px;
  letter-spacing: 0.12em;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
}
.chord-tab-body {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 90px;
}
.chord-tab-body svg {
  width: 100%;
  height: auto;
  display: block;
  max-height: 120px;
}
.chord-tab-body .chord-tab-empty {
  font-size: 13px;
  color: var(--text-dim);
  text-align: center;
}

/* Tab-SVG elements — same theming approach as the chord diagram
   classes above. Default SVG fill (black) is invisible on the
   dark panel; pin to theme tokens. */
.chord-tab-string {
  stroke: var(--text-dim);
  stroke-width: 1;
  opacity: 0.7;
}
.chord-tab-clef {
  fill: var(--text-dim);
  font-size: 9px;
  font-weight: 700;
  font-family: inherit;
}
.chord-tab-fret {
  fill: var(--accent);
  font-size: 11px;
  font-weight: 700;
  font-family: inherit;
}
.chord-tab-placeholder {
  fill: var(--text-dim);
  font-size: 12px;
  font-family: inherit;
}

/* ─── Picking-tab-lane header + controls ──────────────────────────
 * The LEAD PART panel grew a header row that pairs the label with
 * Difficulty / Note-display selectors. The lane itself is rendered
 * by picking-tab-lane.js::createTabLane into #chord-tab-body and
 * scrolls notes past a fixed playhead. CSS only handles theming;
 * geometry lives in the JS module. */
.chord-tab-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.chord-tab-controls {
  display: inline-flex;
  gap: 8px;
}
.tab-control-group {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  background: var(--panel);
}
.tab-control-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  padding: 3px 8px;
  font-size: 11px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  min-width: 28px;
  border-right: 1px solid var(--border);
}
.tab-control-group .tab-control-btn:last-child {
  border-right: 0;
}
.tab-control-btn:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.04);
}
.tab-control-btn.is-active {
  color: var(--bg);
  background: var(--accent);
}

/* The scrolling tab SVG. Sits inside #chord-tab-body, alongside or
   in place of the legacy renderLeadTabSVG output. */
.picking-tab-lane {
  width: 100%;
  height: auto;
  /* Explicit aspect-ratio: older Safari resolves height:auto on an SVG
     with only viewBox (no width/height attrs) to 0, collapsing the lane
     vertically even though child glyphs are correctly positioned.
     viewBox is 640×110 → aspect 64:11. */
  aspect-ratio: 64 / 11;
  min-height: 90px;
  display: block;
  max-height: 140px;
  /* SVG root clips to viewBox by default. We rely on that for hiding
     off-screen scrolling notes and disable the per-group clip-path
     below — see .tab-notes rule for rationale. */
  overflow: hidden;
}
/* Disable the SVG-attribute clip-path that picking-tab-lane.js sets on
   the notes group. The clipPath rect is authored in viewBox space
   (x=padLeft, width=usable area), but Safari applies the group's own
   translate(-tx 0) transform to the clip rect coordinates as well —
   so at runtime the clip rect moves thousands of viewBox units
   off-screen with the content, and every glyph gets clipped out.
   The SVG root's intrinsic overflow:hidden handles bleed past the
   string-label gutter; the gutter sits at viewBox x ∈ [0,36], so
   labels can be briefly overdrawn by a scrolling glyph near the
   edge — a minor cosmetic cost for the notes being visible at all. */
.picking-tab-lane .tab-notes {
  clip-path: none !important;
}
.picking-tab-lane .tab-string {
  stroke: var(--text-dim);
  stroke-width: 1;
  opacity: 0.55;
}
.picking-tab-lane .tab-string-label {
  fill: var(--text-dim);
  font-size: 9px;
  font-weight: 700;
  font-family: inherit;
  letter-spacing: 0.05em;
}
.picking-tab-lane .tab-playhead {
  stroke: var(--accent);
  stroke-width: 1.5;
  opacity: 0.85;
}
.picking-tab-lane .tab-note-dot {
  fill: var(--accent);
  opacity: 0.95;
}
.picking-tab-lane .tab-note-fret {
  fill: var(--accent);
  font-size: 11px;
  font-weight: 700;
  font-family: inherit;
}
.picking-tab-lane .tab-note-name {
  fill: var(--accent);
  font-size: 10px;
  font-weight: 700;
  font-family: inherit;
}

/* View-mode toggle row: Diagrams / Tab / Both. Mirrors the
   chord-snap-row typography so the two toggles look like a family. */
.chord-view-mode-row {
  margin-top: 10px;
  font-size: 13px;
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.chord-view-mode-label {
  color: var(--text-dim);
}
.chord-view-mode-option {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  user-select: none;
}
.chord-view-mode-option input[type="radio"] {
  cursor: pointer;
  margin: 0;
}

/* Click-track toggle: when ON, accent border + faint accent fill so the
   user can tell the metronome scheduler is actually running. */
#t-click.click-on {
  border-color: var(--accent);
  color: var(--accent);
}

/* right column: stem mixer (tighter rows) */
.stage-col.stage-right .stem-rack {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  gap: 4px;
}
.stage-col.stage-right .stem-row {
  grid-template-columns: 1fr 1fr 28px 28px;
  gap: 10px;
  padding: 8px 2px;
}
.stage-col.stage-right .stem-row .name { font-size: 13px; }
.stage-col.stage-right .stem-row .mute-btn,
.stage-col.stage-right .stem-row .solo-btn {
  padding: 4px 0;
  font-size: 10px;
  height: 28px;
  border-radius: 6px;
}

/* playback bar (fixed) */
.playback-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 25;
  background: var(--panel);
  border-top: 1px solid var(--border);
  padding: 12px 20px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 18px;
  align-items: center;
}
.playback-bar .pb-transport {
  display: flex;
  gap: 8px;
  align-items: center;
}
.playback-bar .pb-transport button {
  padding: 8px 14px;
  font-size: 13px;
}
.playback-bar #waveform-canvas {
  display: block;
  width: 100%;
  height: 64px;
  background: var(--panel-2);
  border-radius: 8px;
  cursor: pointer;
}
.playback-bar #t-time {
  margin-left: 0;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  font-size: 13px;
}


/* ─── NOW PLAYING view toggle ──────────────────────────────────────
   Chord-diagram vs scrolling-tab-lane switcher inside the chord-now-
   block. The Tab view shows landmark notes from picking-tab-lane.js;
   the glyph subgroup lets the user A/B different rendering styles
   (dot, fret number, note name) for effectiveness evaluation. */
.chord-now-view-toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 6px 0 8px;
  font-size: 12px;
}
.cv-toggle-btn,
.cv-style-btn {
  background: transparent;
  border: 1px solid var(--border, #2c2c2c);
  color: var(--text-dim, #888);
  padding: 3px 10px;
  border-radius: 4px;
  cursor: pointer;
  font: inherit;
  line-height: 1.4;
}
.cv-toggle-btn.is-active,
.cv-style-btn.is-active {
  background: var(--accent, #e8743b);
  color: #0b0b0b;
  border-color: var(--accent, #e8743b);
}
.cv-toggle-btn:hover:not(.is-active),
.cv-style-btn:hover:not(.is-active) {
  color: var(--text, #ddd);
  border-color: var(--text-dim, #888);
}
.cv-style-group {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding-left: 8px;
  border-left: 1px solid var(--border, #2c2c2c);
}

/* The tab lane SVG fills the same horizontal envelope as the chord
   diagram. Only one of {diagram, lane} is visible at a time; the
   hidden attribute on either container drives display. */
.chord-now-tab-lane {
  width: 100%;
  max-width: 360px;
  margin: 0 auto;
}
.chord-now-tab-lane svg.picking-tab-lane {
  width: 100%;
  height: auto;
  display: block;
}
.chord-now-tab-lane .tab-string {
  stroke: var(--border, #2c2c2c);
  stroke-width: 1;
}
.chord-now-tab-lane .tab-playhead {
  stroke: var(--accent, #e8743b);
  stroke-width: 1.5;
}
.chord-now-tab-lane .tab-string-label {
  fill: var(--text-dim, #888);
  font-size: 10px;
  font-family: ui-monospace, SFMono-Regular, monospace;
}
.chord-now-tab-lane .tab-note-dot {
  fill: var(--accent, #e8743b);
}
.chord-now-tab-lane .tab-note-fret,
.chord-now-tab-lane .tab-note-name {
  fill: var(--accent, #e8743b);
  font-size: 10px;
  font-family: ui-monospace, SFMono-Regular, monospace;
}

/* ------------------------------------------------------- rehearsal view
 * Section-by-section onboarding practice. The layout is a two-column
 * grid (section list + main pane) with a fixed transport strip below.
 * Colours reuse the existing palette (--accent for progress, --good
 * for pass state, --bad for fail state) so the surface feels native
 * next to the perform view. */
#view-rehearsal {
  max-width: 1100px;
  margin: 0 auto;
}
.rehearsal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin: 24px 0 20px;
}
.rehearsal-title {
  font-size: 28px;
  font-weight: 700;
  color: var(--text);
}
.rehearsal-meta {
  color: var(--text-dim);
  font-size: 14px;
  margin-top: 4px;
}
.rehearsal-header-actions {
  display: flex;
  gap: 10px;
}
.rehearsal-grid {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 18px;
  margin-bottom: 24px;
}
.rehearsal-sections {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
}
.rehearsal-sections-label {
  color: var(--text-dim);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 10px;
}
.rehearsal-section-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.rehearsal-section-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
  text-align: left;
  width: 100%;
}
.rehearsal-section-item:hover {
  border-color: var(--border);
}
.rehearsal-section-item.is-active {
  border-color: var(--accent);
  background: rgba(249, 115, 22, 0.08);
}
.rehearsal-section-item .rehearsal-check {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  color: var(--good);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  flex-shrink: 0;
}
.rehearsal-section-item.is-complete .rehearsal-check {
  border-color: var(--good);
  background: rgba(74, 222, 128, 0.16);
}
.rehearsal-section-item .rehearsal-item-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rehearsal-section-item .rehearsal-item-role {
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.rehearsal-section-variant {
  padding-left: 24px;
  font-size: 13px;
  color: var(--text-dim);
}

.rehearsal-main {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px;
  min-height: 460px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.rehearsal-section-heading {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
}
.rehearsal-section-heading-name {
  font-size: 22px;
  font-weight: 700;
}
/* Pattern-ROI subtitle: "Appears 4× · unlocks 41% of the song".
   Sits on its own line under the heading name to frame the section
   as a mastery investment. Colour intentionally dim so it reads
   as context, not competing with the name. */
.rehearsal-section-heading-roi {
  flex-basis: 100%;
  font-size: 13px;
  color: var(--text-dim);
  letter-spacing: 0.01em;
  margin-top: 2px;
}
.rehearsal-section-heading-roi[hidden] {
  display: none;
}
.rehearsal-section-heading-badge {
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 10px;
  background: rgba(249, 115, 22, 0.12);
  color: var(--accent-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.rehearsal-mic-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 12px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  font-size: 12px;
  align-self: flex-start;
}
.rehearsal-mic-chip .rehearsal-mic-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-dim);
}
.rehearsal-mic-chip.rehearsal-mic-on .rehearsal-mic-dot {
  background: var(--good);
  box-shadow: 0 0 6px rgba(74, 222, 128, 0.6);
}
.rehearsal-mic-chip.rehearsal-mic-off .rehearsal-mic-label { color: var(--text-dim); }
.rehearsal-mic-pitch {
  color: var(--text-dim);
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 11px;
}
.rehearsal-items {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-content: flex-start;
  flex: 1;
  min-height: 200px;
  padding: 8px;
  background: var(--panel-2);
  border-radius: 8px;
}
.rehearsal-tile {
  min-width: 96px;
  padding: 12px 14px;
  border-radius: 8px;
  background: var(--panel);
  border: 1.5px solid var(--border);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: ui-monospace, SFMono-Regular, monospace;
  transition: transform 0.12s, border-color 0.12s, background 0.12s;
}
.rehearsal-tile-label {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
}
.rehearsal-tile-sub {
  font-size: 11px;
  color: var(--text-dim);
}
/* Card meta strip under the diagram: musician-language duration
   ("2 bars") + inline fretting-hand tag chip. The chip reuses the
   same severity/hand mixins as the heading strip so a barre tile
   glows warm without needing bespoke styling. */
.rehearsal-tile-meta {
  display: flex;
  justify-content: center;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
  font-size: 11px;
}
.rehearsal-tile-bars {
  color: var(--text-dim);
  letter-spacing: 0.02em;
}
.rehearsal-tile-tag {
  padding: 1px 6px;
  border-radius: 8px;
  background: rgba(148, 163, 184, 0.14);
  color: var(--text-dim);
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.02em;
}
.rehearsal-tile-tag.tag-severity-3 {
  background: rgba(249, 115, 22, 0.16);
  color: var(--accent-2);
}
/* Next-chord preview under the meta strip. Muted colour keeps it
   subordinate to the current chord label; the arrow suggests
   forward motion. */
.rehearsal-tile-next {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px dashed rgba(148, 163, 184, 0.28);
  font-size: 11px;
  color: var(--text-dim);
  letter-spacing: 0.02em;
}
.rehearsal-tile.is-active {
  border-color: var(--accent);
  background: rgba(249, 115, 22, 0.10);
  transform: translateY(-1px);
}
.rehearsal-tile.is-pass {
  border-color: var(--good);
  background: rgba(74, 222, 128, 0.12);
}
.rehearsal-tile.is-fail {
  border-color: var(--bad);
  background: rgba(248, 113, 113, 0.10);
}
.rehearsal-empty {
  color: var(--text-dim);
  font-style: italic;
  padding: 40px 0;
  text-align: center;
}
.rehearsal-score {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}
.rehearsal-score-label {
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-size: 11px;
}
.rehearsal-score-value {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-weight: 700;
  color: var(--text);
}
.rehearsal-score-hint { color: var(--text-dim); margin-left: auto; }

.rehearsal-transport {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: 40px;
}
.rehearsal-transport button.is-active {
  border-color: var(--accent);
  color: var(--accent-2);
}
.rehearsal-speed {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 8px;
}
.rehearsal-speed-label {
  color: var(--text-dim);
  font-size: 12px;
  margin-right: 4px;
}
.rehearsal-speed-btn {
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 4px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
  font-family: inherit;
}
.rehearsal-speed-btn.is-active {
  background: var(--panel);
  color: var(--text);
  border-color: var(--accent);
}
.rehearsal-speed-btn:hover:not(.is-active) { color: var(--text); }
#rehearsal-next { margin-left: auto; }

/* ─── v2 rehearsal redesign ──────────────────────────────────────
   Musician-language IA: three group buckets in the left list
   (Parts to Learn / Moments / Extras), warm status line replacing
   the "Passes 0/0" scoreboard, quieter Skip-to-jam text link, and
   soft-label italics for low-confidence sections. Row layout has
   a reserved beads slot so the Phase-2 mastery rollout drops in
   without a second grid-shift.
   ───────────────────────────────────────────────────────────── */
.rehearsal-header-meta,
#rehearsal-meta {
  color: var(--text-dim);
  font-size: 13px;
  margin-top: 4px;
  letter-spacing: 0.1px;
}
.text-link {
  background: transparent;
  border: none;
  color: var(--text-dim);
  padding: 6px 8px;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  border-radius: 6px;
  transition: color 0.15s, background 0.15s;
}
.text-link:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.04);
  text-decoration: underline;
}

/* Left list — group headings (Parts to learn / Moments / Extras). */
.rehearsal-group-heading {
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.1px;
  font-weight: 600;
  margin: 14px 0 8px;
  padding: 0 4px;
}
.rehearsal-group-heading:first-child { margin-top: 0; }
.rehearsal-group-heading--extras {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  user-select: none;
}
.rehearsal-group-heading--extras::before {
  content: '▸';
  font-size: 10px;
  transition: transform 0.15s;
  display: inline-block;
}
.rehearsal-group-heading--extras.is-open::before {
  transform: rotate(90deg);
}
.rehearsal-extras-tray {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}
.rehearsal-extras-tray.is-collapsed {
  display: none;
}

/* Section row. Grid columns: name, mastery beads, state chip, check.
   Keeps baseline consistent across variants and hover states. */
.rehearsal-section-row {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  background: var(--panel-2);
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
  text-align: left;
  width: 100%;
  font-family: inherit;
  transition: border-color 0.12s, background 0.12s;
}
.rehearsal-section-row + .rehearsal-section-row { margin-top: 6px; }
.rehearsal-section-row:hover:not(.is-active) {
  border-color: var(--border);
}
.rehearsal-section-row.is-active {
  border-color: var(--accent);
  background: rgba(249, 115, 22, 0.10);
}
.rehearsal-section-row.is-variant {
  padding-left: 28px;
  font-size: 12.5px;
  color: var(--text-dim);
  background: transparent;
}
.rehearsal-section-row.is-variant.is-active {
  color: var(--text);
  background: rgba(249, 115, 22, 0.08);
}
/* Cluster wraps one anchor + its variation drawer. Kept transparent
   so it's invisible when a group has no variants; the drawer
   toggle only appears when there's something to reveal. */
.rehearsal-cluster {
  display: flex;
  flex-direction: column;
}
.rehearsal-variation-toggle {
  align-self: flex-start;
  margin: 2px 0 2px 28px;
  padding: 2px 8px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-size: 11px;
  letter-spacing: 0.02em;
  cursor: pointer;
  text-align: left;
}
.rehearsal-variation-toggle::before {
  content: '▸';
  display: inline-block;
  margin-right: 4px;
  transition: transform 0.12s;
}
.rehearsal-variation-toggle.is-open::before {
  transform: rotate(90deg);
}
.rehearsal-variation-toggle:hover {
  color: var(--text);
}
.rehearsal-variation-drawer {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
  max-height: 400px;
  transition: max-height 0.16s ease-out;
}
.rehearsal-variation-drawer.is-collapsed {
  max-height: 0;
}
.rehearsal-section-row.is-soft .rehearsal-section-row-name {
  font-style: italic;
  color: var(--text-dim);
}
.rehearsal-section-row-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rehearsal-section-row-beads {
  display: inline-flex;
  gap: 3px;
  align-items: center;
  min-width: 0;
}
.rehearsal-section-row .rehearsal-check {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  color: var(--good);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  flex-shrink: 0;
}
.rehearsal-section-row.is-complete .rehearsal-check {
  border-color: var(--good);
  background: rgba(74, 222, 128, 0.16);
}

/* Section heading — "Maybe …" italics for low-confidence labels. */
.rehearsal-section-heading-name.is-soft {
  font-style: italic;
  color: var(--text-dim);
}

/* Phase 3: recurrence subtitle under an anchor row ("repeats 4×").
   Rendered inline inside .rehearsal-section-row-name so it wraps
   below the label without stealing width from the beads column. */
.rehearsal-section-row-subtitle {
  display: block;
  font-size: 11px;
  color: var(--text-dim);
  font-weight: 400;
  margin-top: 1px;
  letter-spacing: 0.01em;
}

/* Phase 3: locked-row treatment. The row is still visible so
   musicians see what's coming, but it's desaturated + non-
   interactive-looking. The 🔒 sits in the check slot. Long-press
   hint provided by title attr, no explicit affordance in the row
   itself (matches the design memo — "let the musician discover it,
   don't clutter the list"). */
.rehearsal-section-row.is-locked {
  opacity: 0.55;
  cursor: not-allowed;
}
.rehearsal-section-row.is-locked:hover {
  background: transparent;
}
.rehearsal-section-row .rehearsal-check.is-lock {
  border-color: transparent;
  background: transparent;
  color: var(--text-dim);
  font-size: 12px;
}

/* Phase 3: difficulty hints under the section heading. Chips flow
   inline so multiple hints ("has a barre chord", "quick change")
   read as a comma-less short list. Colour is intentionally subdued
   — this is context, not a warning. */
.rehearsal-section-heading-hints {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.rehearsal-section-heading-hints[hidden] {
  display: none;
}
.difficulty-hint {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  background: rgba(148, 163, 184, 0.12);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.01em;
}
/* Severity styling: severity 3 (roadblock) gets the warm accent so
   barre chords stand out in the heading strip; severity 2 keeps the
   neutral default. */
.difficulty-hint.tag-severity-3 {
  background: rgba(249, 115, 22, 0.14);
  color: var(--accent-2);
}
/* Hand hint: a small leading marker tells the musician at a glance
   which hand or timing dimension the tag targets. Purely
   decorative — the chip label carries the copy. */
.difficulty-hint::before {
  font-size: 9px;
  letter-spacing: 0.06em;
  margin-right: 4px;
  opacity: 0.6;
  text-transform: uppercase;
}
.difficulty-hint.tag-hand-fretting::before { content: 'FRET'; }
.difficulty-hint.tag-hand-picking::before { content: 'PICK'; }
.difficulty-hint.tag-hand-timing::before { content: 'TIME'; }
.difficulty-hint.tag-hand-both::before { content: 'BOTH'; }

/* Dev-only tag-id badges on rehearsal rows (?debug=1). Renders a
   compact monospace pill per detected tag id so the machinery is
   visible without opening /debug. The wrapper only participates
   in layout when the body carries `.debug-tags-visible`; on normal
   loads it collapses to zero-width so section rows stay identical
   to the shipped design. */
.rehearsal-row-debug-tags {
  display: none;
  gap: 4px;
  margin: 0 8px 0 4px;
  align-items: center;
  flex-wrap: wrap;
}
body.debug-tags-visible .rehearsal-row-debug-tags {
  display: inline-flex;
}
.rehearsal-row-debug-tag {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(148, 163, 184, 0.18);
  color: var(--text-dim);
  line-height: 1.4;
}
.rehearsal-row-debug-tag[data-tag-id="barre"] {
  background: rgba(249, 115, 22, 0.24);
  color: var(--accent-2);
}
.rehearsal-row-debug-tag[data-tag-id="colour"] {
  background: rgba(56, 189, 248, 0.20);
  color: #38bdf8;
}
.rehearsal-row-debug-tag[data-tag-id="jumps"] {
  background: rgba(168, 85, 247, 0.20);
  color: #c084fc;
}
.rehearsal-row-debug-tag[data-tag-id="quick"] {
  background: rgba(34, 197, 94, 0.20);
  color: #4ade80;
}

/* Phase A: session-end summary card. Overlays #view-rehearsal
   without unmounting it so the practice state is still behind the
   card if the user picks "Keep practicing". The card carries the
   locked-in tally, best/needs-work rows, and a practice
   recommendation; sits on a translucent scrim so the rehearsal
   view visibly recedes but doesn't disappear. */
.session-summary-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 14, 20, 0.72);
  z-index: 20;
  padding: 24px;
  opacity: 0;
  transition: opacity 0.15s ease-out;
  pointer-events: none;
}
.session-summary-overlay.is-visible {
  opacity: 1;
  pointer-events: auto;
}
.session-summary-overlay[hidden] {
  display: none;
}
.session-summary-card {
  background: var(--surface, #131922);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px 32px;
  max-width: 440px;
  width: 100%;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.session-summary-heading {
  font-size: 22px;
  font-weight: 600;
  margin-bottom: 20px;
  color: var(--text, #e9edf2);
}
/* Follow-up: today's-goal badge on the summary card. A small inline
   stripe that surfaces when the elapsed session met the 15 min soft
   target. Muted good-green so it feels like a nod, not a trophy. */
.session-summary-goal {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  margin: -12px 0 20px 0;
  border-radius: 999px;
  background: rgba(74, 222, 128, 0.10);
  color: var(--good, #4ade80);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.session-summary-goal[hidden] { display: none; }
.session-summary-goal-icon {
  font-size: 11px;
  line-height: 1;
}
.session-summary-lines {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 20px;
}
.session-summary-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: 14px;
}
.session-summary-line[hidden] {
  display: none;
}
.session-summary-line-label {
  color: var(--text-dim);
  font-weight: 500;
}
.session-summary-line-value {
  color: var(--text, #e9edf2);
  font-weight: 600;
  text-align: right;
}
.session-summary-recommendation {
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.5;
  margin-bottom: 20px;
  padding: 10px 12px;
  border-left: 2px solid var(--accent, #6ea8ff);
  background: rgba(110, 168, 255, 0.06);
  border-radius: 4px;
}
.session-summary-recommendation:empty {
  display: none;
}
.session-summary-cta-row {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
.session-summary-footer {
  margin-top: 12px;
  display: flex;
  justify-content: center;
  border-top: 1px solid var(--border);
  padding-top: 12px;
}
/* view-rehearsal needs to be a positioned container so the overlay
   can inset:0 to it rather than the whole viewport. */
#view-rehearsal {
  position: relative;
}

/* Phase B: cross-song skill map. Modal overlay pinned to the
   viewport so it works from any view (bandroom or rehearsal
   summary card). Two-column mastery breakdown + Recent Songs
   strip. Read-only — dismissed via Close button or backdrop
   click. */
.skill-map-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 14, 20, 0.72);
  z-index: 50;
  padding: 24px;
}
.skill-map-overlay[hidden] {
  display: none;
}
.skill-map-card {
  background: var(--surface, #131922);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px 32px;
  max-width: 640px;
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.skill-map-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 20px;
}
.skill-map-heading {
  font-size: 22px;
  font-weight: 600;
  color: var(--text, #e9edf2);
}
.skill-map-empty {
  color: var(--text-dim);
  font-size: 14px;
  text-align: center;
  padding: 32px 0;
}
.skill-map-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin-bottom: 24px;
}
.skill-map-column-heading {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  margin-bottom: 8px;
}
.skill-map-column-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.skill-map-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 13px;
  background: rgba(148, 163, 184, 0.06);
}
.skill-map-row-label {
  color: var(--text, #e9edf2);
  font-weight: 500;
}
.skill-map-row-dots {
  color: var(--accent, #6ea8ff);
  letter-spacing: 2px;
  font-size: 10px;
}
.skill-map-column-empty {
  color: var(--text-dim);
  font-size: 12px;
  padding: 8px;
  font-style: italic;
}
.skill-map-recent {
  border-top: 1px solid var(--border);
  padding-top: 20px;
}
.skill-map-recent-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.skill-map-recent-song {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 12px;
  padding: 8px 10px;
  border-radius: 6px;
  background: rgba(148, 163, 184, 0.06);
  font-size: 13px;
  align-items: baseline;
}
.skill-map-recent-song-title {
  color: var(--text, #e9edf2);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.skill-map-recent-song-parts {
  color: var(--text-dim);
  font-size: 12px;
}
.skill-map-recent-song-when {
  color: var(--text-dim);
  font-size: 11px;
  font-style: italic;
}

/* ─── Phase C warm-up mode ─── */

/* Bandroom suggestion chip. Appears under the CTA row when the
   session includes a barre section and no warm-up in the last 24h. */
.warmup-suggestion-chip {
  align-self: stretch;
  padding: 8px 12px;
  border-radius: 999px;
  border: 1px dashed var(--border);
  background: rgba(148, 163, 184, 0.05);
  color: var(--text-dim);
  font-size: 12px;
  cursor: pointer;
  font-style: italic;
  transition: background 0.15s, color 0.15s;
}
.warmup-suggestion-chip:hover {
  background: rgba(148, 163, 184, 0.12);
  color: var(--text);
}

/* Tertiary CTA style for the bandroom "Warm up" button — quieter
   than .ghost so it doesn't compete visually with Just jam. */
.bandroom-cta .ghost.tertiary {
  opacity: 0.78;
  font-size: 13px;
}

/* Modal overlay wrapper — mirrors .skill-map-overlay so the two
   feel like the same class of surface. */
.warmup-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(10, 14, 20, 0.72);
}
.warmup-overlay[hidden] {
  display: none;
}
.warmup-card {
  width: min(560px, 100%);
  max-height: 80vh;
  overflow-y: auto;
  padding: 24px;
  background: var(--card, #1c2028);
  border: 1px solid var(--border);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.warmup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.warmup-heading {
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
}
.warmup-hint {
  color: var(--text-dim);
  font-size: 13px;
}
.warmup-tile-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.warmup-tile {
  padding: 20px 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: rgba(148, 163, 184, 0.05);
  color: var(--text);
  font-size: 20px;
  font-weight: 600;
  text-align: center;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.warmup-tile.is-active {
  border-color: var(--accent, #6ea8ff);
  box-shadow: 0 0 0 2px rgba(110, 168, 255, 0.25);
  background: rgba(110, 168, 255, 0.08);
}
.warmup-tile.is-passed {
  border-color: var(--good, #66c67d);
  background: rgba(102, 198, 125, 0.12);
  color: var(--good, #66c67d);
}
.warmup-progress {
  color: var(--text-dim);
  font-size: 13px;
  text-align: center;
}
.warmup-done-copy {
  padding: 12px;
  border-radius: 8px;
  background: rgba(102, 198, 125, 0.10);
  color: var(--good, #66c67d);
  text-align: center;
  font-weight: 500;
}
.warmup-done-copy[hidden] {
  display: none;
}

/* ─── Phase D best-rep playback ─── */

/* Small sibling button rendered after each rehearsal-section-row.
   Uses text-link styling so it doesn't compete with the row itself
   but is discoverable. */
.best-rep-button {
  align-self: flex-end;
  margin: -4px 12px 8px auto;
  padding: 2px 8px;
  border: none;
  background: transparent;
  color: var(--accent, #6ea8ff);
  font-size: 11px;
  cursor: pointer;
  border-radius: 4px;
  transition: background 0.15s;
}
.best-rep-button:hover {
  background: rgba(110, 168, 255, 0.10);
}

/* Sibling "⬇ WAV" download control. Uses the same text-link visual
   language as the play button but with a slightly dimmer accent so
   Play remains the primary affordance. Sits directly after the play
   button in the row (no extra margin) so the two read as a pair. */
.best-rep-download-button {
  align-self: flex-end;
  margin: -4px 12px 8px 0;
  padding: 2px 8px;
  border: none;
  background: transparent;
  color: var(--text-dim, #94a3b8);
  font-size: 11px;
  cursor: pointer;
  border-radius: 4px;
  transition: background 0.15s;
}
.best-rep-download-button:hover {
  background: rgba(148, 163, 184, 0.10);
  color: var(--accent, #6ea8ff);
}

/* Session-summary "Hear your best rep" link. Inline next to the
   Best mastery value; text-link sizing keeps the line clean. */
#session-summary-best-rep {
  margin-left: 8px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--accent, #6ea8ff);
  font-size: 12px;
  cursor: pointer;
}
#session-summary-best-rep[hidden] {
  display: none;
}

/* Opt-in toast — bottom-centre fixed banner. Auto-inserted by
   _promptBestRepOptIn on the first qualifying perfect run. */
.bestrep-optin-toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 18px;
  background: var(--card, #1c2028);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  color: var(--text);
  font-size: 13px;
}
.bestrep-optin-copy {
  font-weight: 500;
}
.bestrep-optin-cta {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.bestrep-optin-cta button {
  padding: 6px 12px;
  font-size: 12px;
}

/* Status line replacing the "Passes 0/0" scoreboard. Warm sentence
   copy from _rehearsalStatusCopy(); container reuses the
   #rehearsal-score div so DOM ids stay stable. */
.rehearsal-status {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  min-height: 34px;
}
.rehearsal-status-line {
  color: var(--text);
  font-family: inherit;
  font-weight: 500;
  line-height: 1.4;
}
.rehearsal-status-line.is-complete {
  color: var(--good);
  font-weight: 600;
}
.rehearsal-status-hint {
  color: var(--text-dim);
  margin-left: auto;
}

/* ─── Phase 2 mastery beads, state chip, tile fine-grained states ─── */

/* Bead: 5-dot row rendered inside .rehearsal-section-row-beads. Empty
   beads are a hollow outline; graded beads pick up quality colour. */
.bead {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: transparent;
  display: inline-block;
  transition: background 0.15s, border-color 0.15s;
}
.bead.is-empty {
  /* explicit for readability; same as default */
  background: transparent;
}
.bead.is-rough {
  background: #f4c063;
  border-color: #f4c063;
}
.bead.is-clean {
  background: #86efac;
  border-color: #86efac;
}
.bead.is-perfect {
  background: var(--good);
  border-color: var(--good);
  box-shadow: 0 0 4px rgba(74, 222, 128, 0.5);
}

/* State chip: small pill shown next to the beads. Hidden when
   rec.state === 'not_tried'. */
.mastery-chip {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-dim);
  background: transparent;
  white-space: nowrap;
}
.mastery-chip[hidden] { display: none; }
.mastery-chip.is-learning {
  color: #f4c063;
  border-color: rgba(244, 192, 99, 0.35);
  background: rgba(244, 192, 99, 0.08);
}
.mastery-chip.is-practicing {
  color: #86efac;
  border-color: rgba(134, 239, 172, 0.35);
  background: rgba(134, 239, 172, 0.08);
}
.mastery-chip.is-locked-in {
  color: var(--good);
  border-color: rgba(74, 222, 128, 0.45);
  background: rgba(74, 222, 128, 0.14);
}
.mastery-chip.is-mastered {
  color: #ffffff;
  border-color: var(--good);
  background: rgba(74, 222, 128, 0.35);
}
/* Fading: previously locked-in, but stale. Muted amber signals
   "refresher needed" without the alarm of a fail state. */
.mastery-chip.is-fading {
  color: #f4c063;
  border-color: rgba(244, 192, 99, 0.4);
  background: rgba(244, 192, 99, 0.10);
}

/* Today's-goal strip: a quiet, single-row session-budget indicator
   above the section list. Not a hard cap — just a nudge to help a
   practicer notice they've been at it long enough to hit their
   Duolingo-style daily goal. Progress bar fills in real time. */
.rehearsal-session-goal {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  margin: 0 0 8px 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.02);
  font-size: 12px;
  color: var(--dim);
}
.rehearsal-session-goal[hidden] { display: none; }
.rehearsal-session-goal-label {
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.02em;
}
.rehearsal-session-goal-value {
  color: var(--dim);
  white-space: nowrap;
}
.rehearsal-session-goal-bar {
  flex: 1 1 auto;
  height: 4px;
  min-width: 40px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
}
.rehearsal-session-goal-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.4s ease-out;
}
.rehearsal-session-goal.is-complete .rehearsal-session-goal-fill {
  background: var(--good);
}
.rehearsal-session-goal.is-complete .rehearsal-session-goal-label {
  color: var(--good);
}

/* Tile fine-grained failure states. is-fail keeps the existing red
   treatment (defined elsewhere); is-close/early/late nudge the
   colour warmer so a musician sees "almost there" vs "wrong". */
.rehearsal-tile.is-close {
  border-color: #86efac;
  background: rgba(134, 239, 172, 0.10);
}
.rehearsal-tile.is-early {
  border-color: #93c5fd;
  background: rgba(147, 197, 253, 0.10);
}
.rehearsal-tile.is-late {
  border-color: #f4c063;
  background: rgba(244, 192, 99, 0.10);
}

/* Per-tile why-hint. One line max, breaks below the tile's main
   content. Written by _setWhyHint on fail; cleared on next active. */
.tile-why-hint {
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.3;
  color: var(--text-dim);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rehearsal-tile.is-close .tile-why-hint { color: #86efac; }
.rehearsal-tile.is-early .tile-why-hint { color: #93c5fd; }
.rehearsal-tile.is-late .tile-why-hint { color: #f4c063; }

/* Bandroom post-analysis CTA pair. Hidden until onAnalysisComplete
   flips the bandroom into "Ready". Skip-to-jam is styled ghost so the
   primary rehearsal CTA reads first. */
.bandroom-cta {
  display: flex;
  gap: 12px;
  margin-top: 18px;
  justify-content: center;
}

/* ==========================================================
   Launchpad center-column view (tab strip + dedicated panel)
   ==========================================================
   The tab strip swaps #chord-stack (chord ribbon + guidance +
   riff + stems) for #launchpad-panel (status + mode + mirror
   grid + legend + voice tweaks). Persisted via
   state.settings.launchpadCenterTab.
*/
.center-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--border, #2b2f38);
}
.center-tab {
  background: transparent;
  border: 0;
  color: var(--text-dim, #8a8f99);
  font: inherit;
  padding: 8px 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.center-tab:hover { color: var(--text, #e6e8ec); }
.center-tab.is-active {
  color: var(--text, #e6e8ec);
  border-bottom-color: var(--accent, #6ce07a);
}
.center-tab[disabled] { opacity: 0.5; cursor: not-allowed; }
.center-tab-warn-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #e07a6c;
  box-shadow: 0 0 4px #e07a6c;
}
.chord-stack[hidden], .launchpad-panel[hidden] { display: none !important; }

/* Panel body */
.launchpad-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 4px 0;
}
.lp-section-label {
  display: block;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim, #8a8f99);
  margin-bottom: 4px;
}
.lp-status-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.lp-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  background: #1c1f26;
  font-size: 13px;
  font-weight: 500;
}
.lp-status-device { color: var(--text-dim, #8a8f99); font-size: 12px; }
.lp-status-enable { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
.lp-status-enable[hidden] { display: none !important; }

/* Preset ("Play style") selector: five opinionated radios stacked
   vertically inside the settings column, with a short caption below
   that swaps in on selection. */
.lp-preset-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 10px 12px;
  background: #16181e;
  border-radius: 8px;
}
/* Chunky button-style radios. The native radio is hidden and the
   entire label becomes the clickable button — round accent dot on
   the left, label text on the right, glowing border/background when
   selected. The per-preset accent color is set via CSS custom
   properties on each label (--preset-color / --preset-color-soft) so
   the shared button rules stay DRY. Matches the Launchpad top-row
   preset LEDs painted from jam.js. */
.lp-preset-row label {
  --preset-color: #8a8f99;
  --preset-color-soft: rgba(138, 143, 153, 0.14);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  min-height: 34px;
  background: #1c1f27;
  border: 1px solid #2b2f38;
  border-radius: 999px;
  color: #d8dbe1;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, transform 60ms ease;
}
.lp-preset-row label:hover {
  background: #20242d;
  border-color: #3a3f4a;
}
.lp-preset-row label:active {
  transform: translateY(1px);
}
/* Hide the native radio; the label itself is the clickable target. */
.lp-preset-row label input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}
/* Accent dot rendered via ::before so we don't need to touch the
   markup. Empty circle by default; filled bright when selected. */
.lp-preset-row label::before {
  content: '';
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--preset-color);
  background: transparent;
  box-shadow: none;
  transition: background 120ms ease, box-shadow 120ms ease;
}
/* Selected: full accent color, subtle glow, filled dot, soft tint
   background so the button visibly stands out from its siblings. */
.lp-preset-row label:has(input:checked) {
  background: var(--preset-color-soft);
  border-color: var(--preset-color);
  color: #ffffff;
}
.lp-preset-row label:has(input:checked)::before {
  background: var(--preset-color);
  box-shadow: 0 0 0 3px var(--preset-color-soft),
              0 0 10px 0 var(--preset-color);
}
/* Per-preset accent colors — matches the Launchpad hardware LEDs
   painted from _launchpadPaintPresetTopRow (jam.js). */
.lp-preset-row label:has(input[value="learn-song"])       { --preset-color: #64b5f6; --preset-color-soft: rgba(100, 181, 246, 0.14); }
.lp-preset-row label:has(input[value="perform-song"])     { --preset-color: #4fc3f7; --preset-color-soft: rgba( 79, 195, 247, 0.14); }
.lp-preset-row label:has(input[value="learn-harmony"])    { --preset-color: #ba68c8; --preset-color-soft: rgba(186, 104, 200, 0.14); }
.lp-preset-row label:has(input[value="jam-in-key"])       { --preset-color: #ff8a65; --preset-color-soft: rgba(255, 138, 101, 0.14); }
.lp-preset-row label:has(input[value="melody-practice"])  { --preset-color: #ffd54f; --preset-color-soft: rgba(255, 213,  79, 0.14); }
.lp-preset-row label:has(input[value="play-bassline"])    { --preset-color: #a1887f; --preset-color-soft: rgba(161, 136, 127, 0.14); }
.lp-preset-row label:has(input[value="contribute-vocal"])    { --preset-color: #66bb6a; --preset-color-soft: rgba(102, 187, 106, 0.16); }
.lp-preset-row label:has(input[value="contribute-drums"])    { --preset-color: #ef5350; --preset-color-soft: rgba(239,  83,  80, 0.16); }
.lp-preset-row label:has(input[value="contribute-bass"])     { --preset-color: #ab47bc; --preset-color-soft: rgba(171,  71, 188, 0.16); }
.lp-preset-row label:has(input[value="contribute-harmonic"]) { --preset-color: #26c6da; --preset-color-soft: rgba( 38, 198, 218, 0.16); }
.lp-preset-row label:has(input[value="contribute-sections"]) { --preset-color: #ffb74d; --preset-color-soft: rgba(255, 183,  77, 0.16); }
/* Sub-header inside the preset row splitting the play-along presets
   from the Contribute family. Visually a thin caption above a divider
   line to signal "different mental model below". */
.lp-preset-group-label {
  margin-top: 6px;
  padding-top: 8px;
  width: 100%;
  border-top: 1px solid #23262e;
  color: var(--text-dim, #8a8f99);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.lp-preset-caption {
  margin: 4px 0 0;
  padding-top: 6px;
  border-top: 1px solid #23262e;
  width: 100%;
  color: var(--text-dim, #8a8f99);
  font-size: 12px;
  line-height: 1.4;
}
/* Suggestion shown when the classifier thinks a different preset
   would fit the loaded song better than the currently-selected one. */
.lp-preset-hint {
  margin: 6px 0 0;
  padding: 6px 8px;
  width: 100%;
  background: rgba(255, 183, 77, 0.08);
  border-left: 2px solid #ffb74d;
  border-radius: 4px;
  color: #ffcc80;
  font-size: 11px;
  line-height: 1.4;
}

/* Advanced-controls disclosure wraps the underlying mode/layout/assist/
   instrument/anticipation/openjam controls so they're hidden by default
   but still reachable for power users. */
.lp-advanced {
  border-top: 1px solid var(--border, #2b2f38);
  padding-top: 8px;
}
.lp-advanced-summary {
  cursor: pointer;
  padding: 4px 0;
  font-size: 12px;
  color: var(--text-dim, #8a8f99);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.lp-advanced-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 8px 0;
}

.lp-mode-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}
.lp-mode-row label { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }
.lp-openjam-options {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 8px 12px;
  background: #16181e;
  border-radius: 8px;
}
.lp-openjam-options label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }

/* Three-column split: legend (auto-width) | mirror (fills the free
   space) | settings (fixed narrow on the right). Legend on the left
   lets it flow vertically without stealing the grid's headroom, and
   the mirror stays the visual anchor in the middle. */
.lp-split {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr) 260px;
  gap: 20px;
  align-items: start;
}
@media (max-width: 900px) {
  .lp-split { grid-template-columns: 1fr; }
}

/* Right-side settings stack — mode / layout / assist / instrument /
   anticipation / open-jam options / legend, top-to-bottom. */
.lp-settings-column {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.lp-settings-column .lp-mode-row,
.lp-settings-column .lp-openjam-options {
  padding: 6px 10px;
  background: #16181e;
  border-radius: 8px;
}
.lp-settings-column .lp-mode-row {
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}
.lp-settings-column .lp-mode-row .lp-section-label {
  margin-bottom: 0;
}
.lp-settings-column .lp-legend-wrap {
  padding: 6px 10px;
  background: #16181e;
  border-radius: 8px;
}

/* On-screen 8×8 mirror. Rendered so bottom-left = pad 11 to
   match the physical device orientation. The grid fills the free
   column of .lp-split (settings sit at a fixed 260px on the right)
   so the mirror scales up as the viewport grows. */
.lp-grid-wrap {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  /* Cap by both dimensions so the grid never overflows the viewport
     vertically. `min()` picks whichever is smaller: full column width,
     or the available vertical space (viewport minus header + tabs +
     status/legend rows above + transport controls below). The cap
     lives on the wrapper (not .lp-grid) so the "PADS" label and
     countdown bar share the same centered width as the grid itself. */
  width: min(100%, calc(100vh - 190px));
  margin: 0 auto;
}
.lp-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-auto-rows: 1fr;
  gap: 4px;
  aspect-ratio: 1 / 1;
  padding: 6px;
  background: #0e1015;
  border-radius: 10px;
  width: 100%;
}
.lp-pad {
  border: 0;
  border-radius: 4px;
  background: #1b1e25;
  cursor: pointer;
  padding: 0;
  transition: transform 60ms ease-out, box-shadow 60ms ease-out;
  position: relative;
}
.lp-pad:hover { transform: scale(1.05); }
.lp-pad:active { transform: scale(0.95); }
.lp-pad:focus-visible { outline: 2px solid var(--accent, #6ce07a); outline-offset: 2px; }
.lp-pad--pulse {
  /* Upcoming-chord pads pulse and stay dimmer than active pads so
     the currently-sounding tones remain the visual anchor. */
  animation: lp-pulse 0.85s ease-in-out infinite;
}
/* Static (all playable pads that aren't currently sounding) get
   knocked back so the active pad reads as "you are here" at a glance.
   Only applied in modes with a single sounding pad (song-verify /
   song-display); jam/instrument modes leave every pad bright because
   the user is free-picking, not tracking a "now" position. */
.lp-grid--has-active .lp-pad--static { filter: brightness(0.28); }
/* Disabled / empty pad: knocked way back visually so it clearly
   reads as "unusable" next to bright, tap-able chop pads. Uses
   both a low opacity and a dark overlay so the residual tint
   from the recorded RGB doesn't compete with active neighbours. */
.lp-pad--disabled {
  filter: brightness(0.35) saturate(0.4);
  opacity: 0.45;
}
/* Active pad: brightened + white ring + strong glow so the user's
   current position pops against the dimmed backdrop. */
.lp-pad--active {
  filter: brightness(1.5) saturate(1.15);
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.9),
    0 0 18px 4px currentColor;
  z-index: 1;
}
@keyframes lp-pulse {
  0%, 100% { filter: brightness(0.55); }
  50%     { filter: brightness(0.15); }
}

/* Legend */
.lp-legend { display: flex; flex-direction: column; gap: 8px; font-size: 12px; }
.lp-legend-item { display: flex; align-items: center; gap: 8px; }
.lp-legend-swatch {
  width: 14px; height: 14px; border-radius: 3px; flex: 0 0 14px;
  border: 1px solid rgba(255,255,255,0.08);
}
.lp-legend-caption {
  margin-top: 6px;
  color: var(--text-dim, #8a8f99);
  font-size: 11px;
  line-height: 1.4;
}

/* Horizontal variant: chips flow left-to-right in a wrap row above
   the pad grid; caption drops to its own line below. */
.lp-legend-wrap--horizontal {
  padding: 6px 10px;
  background: #16181e;
  border-radius: 8px;
}
.lp-legend--horizontal {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
}
.lp-legend--horizontal .lp-legend-caption {
  flex-basis: 100%;
  margin-top: 2px;
}

/* Countdown bar: sits above the pad grid, communicates time until the
   next distinct chord change. Fill shrinks smoothly from 100% to 0. */
.lp-countdown {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 10px;
  background: #16181e;
  border-radius: 8px;
  margin-bottom: 6px;
}
.lp-countdown-track {
  position: relative;
  height: 8px;
  background: #0d0f13;
  border-radius: 4px;
  overflow: hidden;
}
.lp-countdown-fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 0%;
  background: linear-gradient(90deg, #4dd0e1 0%, #ffb74d 65%, #ff5252 100%);
  transition: width 80ms linear;
  border-radius: 4px;
}
.lp-countdown-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 12px;
  color: var(--text-dim, #8a8f99);
}
.lp-countdown-next {
  color: var(--text, #e6e8ec);
  font-weight: 600;
}
.lp-countdown-time {
  font-variant-numeric: tabular-nums;
}
.lp-countdown.lp-countdown--imminent .lp-countdown-fill {
  animation: lp-countdown-pulse 0.4s ease-in-out infinite alternate;
}
@keyframes lp-countdown-pulse {
  0% { filter: brightness(0.9); }
  100% { filter: brightness(1.35); }
}

/* Voice tweaks (collapsible) */
.lp-voice { border-top: 1px solid var(--border, #2b2f38); padding-top: 8px; }
.lp-voice-summary { cursor: pointer; padding: 4px 0; font-size: 13px; }
.lp-voice-body { display: flex; flex-direction: column; gap: 6px; padding: 8px 0; }
.lp-tweak-row {
  display: grid;
  grid-template-columns: 130px 1fr 60px;
  align-items: center;
  gap: 10px;
  font-size: 12px;
}
.lp-tweak-label { color: var(--text-dim, #8a8f99); }
.lp-tweak-readout { text-align: right; font-variant-numeric: tabular-nums; color: var(--text, #e6e8ec); }
.lp-tweak-row input[type="range"] { width: 100%; }
.lp-voice-actions { display: flex; justify-content: flex-end; padding-top: 6px; }
