/* =============================================================================
   theory — standalone app shell ("Theory Lab")
   -----------------------------------------------------------------------------
   Studio-rack aesthetic on the site's token system: four hairline panels
   (wheel / neck / harmony / spectrum), mono uppercase labels, violet accent.
   Everything that can light up shares one pulse animation (th-pulse) so
   cross-view tracing reads as a single instrument, not four widgets.
============================================================================= */

html, body { height: 100%; }
body.app {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  overflow-x: clip;
}

/* Chord-family colors — same palette semantics as the songbook app. */
[data-family="major"]     { --kind: var(--c-cyan-1);   --kind-2: var(--c-cyan-2); }
[data-family="major-alt"] { --kind: var(--c-mint-1);   --kind-2: var(--c-mint-2); }
[data-family="minor"]     { --kind: var(--c-violet-1); --kind-2: var(--c-violet-2); }
/* minor-alt is unreachable from diatonicChords' FAMILY map, but the Shapes and
   Barre panels classify chords by name (familyOf, shared with Songbook) and do
   emit it — so this stays, deliberately, not as a leftover. */
[data-family="minor-alt"] { --kind: var(--c-coral-1);  --kind-2: var(--c-coral-2); }
[data-family="other"]     { --kind: var(--c-amber-1);  --kind-2: var(--c-amber-2); }
[data-family] {
  --kind-soft: color-mix(in srgb, var(--kind) 18%, transparent);
  --kind-glow: color-mix(in srgb, var(--kind) 40%, transparent);
}
:root { --kind: var(--accent); --kind-2: var(--accent-2); --kind-soft: var(--accent-soft); --kind-glow: var(--accent-glow); }

/* =========================================================================
   Shell + top bar
========================================================================= */
/* Wide by design, and FIXED HEIGHT. Panels grow rightward, never downward
   ([RYDER-VERDICT] 2026-07-27) — and a page that scrolls for 4000px breaks that
   ruling just as surely as putting a panel at the bottom would, because
   everything past the fold has stopped participating.

   So the lab is sized to the viewport and never scrolls the page. Each panel
   scrolls its own body instead. That is the difference between an app
   workspace and a website, and it is the only way a 43-row list can live here
   without dragging every other panel's height with it. */
.th-shell {
  max-width: 2100px;
  margin: 0 auto;
  padding: 0 clamp(.5rem, 1.4vw, 1rem) clamp(.5rem, 1.4vw, 1rem);
  height: calc(100dvh - var(--portal-chrome-h, 64px));   /* JS refines this exactly */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  --accent: var(--c-violet-1);
  --accent-2: var(--c-violet-2);
  --accent-soft: color-mix(in srgb, var(--accent) 16%, transparent);
  --accent-glow: color-mix(in srgb, var(--accent) 36%, transparent);
}
body.app { overflow: hidden; }

/* Transparent over the page by default; the blurred surface lives in a
   full-bleed ::before (spanning the viewport, not the centered shell) and
   fades in only once the page is scrolled — no boxed edge at the top. */
.th-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 6px 2px 7px;
  flex: 0 0 auto;
}
.th-bar::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: calc(50% - 50vw); right: calc(50% - 50vw);
  z-index: -1;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--hairline);
  opacity: 0;
  transition: opacity var(--t-base) var(--ease);
  pointer-events: none;
}
.th-bar[data-stuck]::before { opacity: 1; }
.th-title {
  font: 800 17px var(--sans);
  letter-spacing: -.02em;
  display: inline-flex; align-items: center; gap: 9px;
  color: var(--fg);
}
.th-glyph {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: 9px;
  font-size: 17px;
  color: var(--accent);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}
.th-bar-spacer { flex: 1; }
.th-controls { display: flex; gap: 8px; align-items: center; }
.th-meta-controls { display: flex; gap: 8px; align-items: center; }
.th-home {
  font: 600 11px var(--mono);
  color: var(--fg-mute);
  text-transform: uppercase;
  letter-spacing: .12em;
}
.th-home:hover { color: var(--accent); }

/* Key readout — what the two dropdowns used to be. Not a control: the wheel
   sets the key and the tray sets the scale, so this only has to say where you
   are, and it pulses with everything else when the tonic sounds. */
.th-keyread {
  display: inline-flex;
  align-items: baseline;
  gap: 9px;
  padding: 5px 12px;
  border-radius: 10px;
  background: var(--bg-soft);
  border: 1px solid var(--border);
}
.th-keyread b {
  font: 800 17px var(--mono);
  color: var(--accent);
  letter-spacing: -.01em;
}
.th-keyread span {
  font: 600 11px var(--mono);
  color: var(--fg-dim);
  letter-spacing: .04em;
}
.th-sigread {
  font: 700 12px var(--mono);
  color: var(--fg-mute);
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
}

/* Scale tray — grouped chips under the wheel, beside the key they modify. */
.th-scaletray {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px solid var(--hairline);
}
.th-tray-group { display: flex; align-items: baseline; gap: 10px; }
.th-tray-label {
  font: 700 9px var(--mono);
  text-transform: uppercase;
  letter-spacing: .13em;
  color: var(--fg-mute);
  flex: 0 0 62px;
  text-align: right;
  padding-top: 2px;
}
.th-tray-chips { display: flex; flex-wrap: wrap; gap: 5px; flex: 1; min-width: 0; }
.th-tray-chips .th-chip { padding: 4px 9px; font-size: 10.5px; }

/* =========================================================================
   Chips + segmented controls
========================================================================= */
.th-chip {
  font: 700 11px var(--mono);
  letter-spacing: .06em;
  color: var(--fg-dim);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 99px;
  padding: 5px 11px;
  transition: all var(--t-fast) var(--ease);
  white-space: nowrap;
}
.th-chip:hover { border-color: var(--accent); color: var(--fg); }
.th-chip.active, .th-chip.accent {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
  color: var(--accent);
}
.th-chip.accent:hover { background: var(--accent); color: var(--accent-fg); }

.th-seg:empty { display: none; }
.th-seg {
  display: inline-flex;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 2px;
  gap: 2px;
}
.th-seg button {
  font: 700 11px var(--mono);
  letter-spacing: .04em;
  color: var(--fg-mute);
  background: transparent;
  border: 0;
  border-radius: 8px;
  padding: 4px 9px;
  min-width: 28px;
  transition: all var(--t-fast) var(--ease);
}
.th-seg button:hover { color: var(--fg); }
.th-seg button.active {
  background: var(--accent-soft);
  color: var(--accent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);
}

/* =========================================================================
   Panel grid
========================================================================= */
/* Panels grow to the RIGHT ([RYDER-VERDICT] 2026-07-27). Spectrum stays the
   full-width floor it always was; new columns are added beside the existing
   ones, never stacked beneath them, so every view stays lit at once.

   The narrower steps below are FALLBACKS for windows that cannot hold the
   design, not alternative designs. They collapse right-to-left: the newest
   column folds first, the original two-panel pairing survives longest. */
.th-grid {
  display: grid;
  grid-template-columns:
    minmax(300px, 340px)    /* key            */
    minmax(560px, 1.35fr)   /* neck / harmony */
    minmax(260px, 300px)    /* shapes / barre */
    minmax(300px, 360px);   /* progressions / drills */
  grid-template-areas:
    "key neck    shapes progs"
    "key harmony barre  drills"
    "spectrum spectrum spectrum spectrum";
  /* The spectrum row is `auto`, so on a viewport too short for everything it
     was the row that got clipped by the shell's overflow. minmax(0, auto) lets
     it give ground like the others — the strip compresses instead of losing
     its axis labels off the bottom edge. */
  grid-template-rows: minmax(150px, 1.5fr) minmax(130px, 1fr) minmax(0, auto);
  gap: 8px;
  flex: 1;
  min-height: 0;
}
.th-wheel-panel    { grid-area: key; }
.th-neck-panel     { grid-area: neck; }
.th-harmony-panel  { grid-area: harmony; }
.th-shapes-panel   { grid-area: shapes; }
.th-barre-panel    { grid-area: barre; }
.th-progs-panel    { grid-area: progs; }
.th-drill-panel    { grid-area: drills; }
.th-spectrum-panel { grid-area: spectrum; overflow: hidden; }
.th-spectrum-panel .th-spectrum-wrap { min-height: 0; overflow: hidden; }
.th-spectrum-panel .th-panel-body { overflow: hidden; }

/* Narrower than the design wants: drop the fourth column, but progressions and
   drills stay BESIDE the others rather than under them. */
@media (max-width: 1400px) {
  .th-grid {
    grid-template-columns: minmax(280px, 320px) minmax(0, 1.3fr) minmax(250px, 300px);
    grid-template-areas:
      "key neck    progs"
      "key harmony drills"
      "shapes barre barre"
      "spectrum spectrum spectrum";
    grid-template-rows: minmax(0, 1.1fr) minmax(0, 1fr) minmax(0, .8fr) auto;
  }
}
/* Below this nothing fits side by side and the page becomes a document again —
   a fallback, not a design. */
@media (max-width: 1180px) {
  body.app { overflow: auto; }
  .th-shell { height: auto; min-height: 100dvh; overflow: visible; }
  .th-grid {
    grid-template-columns: 1fr;
    grid-template-areas: "key" "neck" "harmony" "shapes" "barre" "progs" "drills" "spectrum";
    grid-template-rows: none;
  }
  .th-panel { max-height: none; }
  .th-panel-body { overflow: visible; }
  .th-wheel-wrap { min-height: 0; }
  #th-wheel { max-width: 360px; }
}

/* ---- panel: a fixed head and a body that scrolls on its own ------------- */
.th-panel {
  background: var(--mat-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px 9px;
  box-shadow: var(--shadow-sm);
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.th-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 7px;
  flex: 0 0 auto;
}
.th-panel-head > span:first-child {
  font: 700 9px var(--mono);
  text-transform: uppercase;
  letter-spacing: .13em;
  color: var(--fg-dim);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 8px;
}
/* Content sits at the TOP of its panel. It used to be centred inside a
   flex:1 wrapper, which is how the wheel ended up floating in the middle of a
   2000px column with nothing around it. */
.th-panel-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}
/* A scrolling body must not ALSO squash its children. Flex children shrink by
   default, so a card in a short panel was compressed below its content and —
   because the card clips its own overflow for the accent rule — silently cut
   itself in half. The body scrolls; its children keep their natural size. */
.th-panel-body > * { flex-shrink: 0; }

.th-panel-body::-webkit-scrollbar,
.th-prog-list::-webkit-scrollbar { width: 7px; height: 7px; }
.th-panel-body::-webkit-scrollbar-thumb,
.th-prog-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--fg) 16%, transparent);
  border-radius: 99px;
}
.th-panel-body::-webkit-scrollbar-track,
.th-prog-list::-webkit-scrollbar-track { background: transparent; }

.th-hint {
  font: 500 9.5px var(--mono);
  color: var(--fg-mute);
  opacity: .75;
  margin-left: auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* =========================================================================
   Focus — the SVG surfaces are keyboard-reachable now

   The wheel segments, fretboard dots and spectrum ticks carry role="button"
   + tabindex. SVG ignores outline in most engines, so focus is drawn as a
   stroke instead, and it must be visible against every fill state.
========================================================================= */
.wh-seg:focus-visible,
.nk-note:focus-visible,
.sp-tick:focus-visible {
  outline: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  paint-order: stroke;
}
.nk-note:focus-visible circle,
.nk-note:focus-visible rect {
  stroke: var(--accent);
  stroke-width: 2.5;
}
.sp-tick:focus-visible { filter: drop-shadow(0 0 6px var(--accent-glow)); }
.th-chip:focus-visible,
.th-seg button:focus-visible,
.th-chord:focus-visible,
.th-prog:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* =========================================================================
   Wheel
========================================================================= */
.th-wheel-wrap {
  position: relative;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
}
#th-wheel { width: 100%; max-width: 290px; height: auto; display: block; }

.wh-seg {
  fill: var(--bg-soft);
  stroke: var(--bg);
  stroke-width: 1.5;
  cursor: pointer;
  transition: fill var(--t-fast) var(--ease);
}
.wh-seg.fan { fill: color-mix(in srgb, var(--accent) 9%, var(--bg-soft)); }
.wh-seg:hover { fill: color-mix(in srgb, var(--accent) 22%, var(--bg-soft)); }
.wh-seg.active {
  fill: var(--accent);
  filter: drop-shadow(0 0 14px var(--accent-glow));
}
.wh-seg.chord-lit { fill: color-mix(in srgb, var(--kind, var(--accent)) 30%, var(--bg-soft)); }
.wh-seg.active.chord-lit { fill: var(--accent); }
.wh-seg.tx-target {
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-dasharray: 5 4;
  fill: color-mix(in srgb, var(--accent) 18%, var(--bg-soft));
  animation: th-dash 1.2s linear infinite;
}
@keyframes th-dash { to { stroke-dashoffset: -18; } }

.wh-label {
  fill: var(--fg-dim);
  text-anchor: middle;
  dominant-baseline: central;
  pointer-events: none;
  font-family: var(--mono);
}
.wh-label-maj { font-size: 17px; font-weight: 700; fill: var(--fg); }
.wh-label-min { font-size: 12px; font-weight: 500; }

/* The active segment fills with --accent, and the label sits ON it as a
   sibling — so the label has to flip to the accent's foreground or it is
   --fg text on an --accent field. There was an empty rule here with a comment
   describing this and no declarations; the labels are marked instead. */
.wh-label.on-active { fill: var(--accent-fg); font-weight: 800; }
.wh-sig {
  fill: var(--fg-mute);
  font: 500 9px var(--mono);
  text-anchor: middle;
  dominant-baseline: central;
  pointer-events: none;
}

.wh-trace {
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  filter: drop-shadow(0 0 8px var(--accent-glow));
  animation: th-trace .7s var(--ease-out) forwards;
}
@keyframes th-trace { to { stroke-dashoffset: 0; } }
.wh-trace-dot {
  fill: var(--accent);
  filter: drop-shadow(0 0 8px var(--accent-glow));
  opacity: 0;
  animation: th-fade-in .3s .55s var(--ease) forwards;
}
.wh-trace-label {
  fill: var(--accent);
  font: 700 11.5px var(--mono);
  text-anchor: middle;
  dominant-baseline: central;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4;
  opacity: 0;
  animation: th-fade-in .3s .4s var(--ease) forwards;
}
@keyframes th-fade-in { to { opacity: 1; } }

/* Hub — the key readout lives inside the wheel */
.th-hub {
  position: absolute;
  width: 46%;
  aspect-ratio: 1;
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  text-align: center;
  background: radial-gradient(circle, var(--bg-elev) 58%, color-mix(in srgb, var(--bg-elev) 60%, transparent) 84%, transparent 100%);
  pointer-events: none;
  padding: 8px;
}
.th-hub > * { pointer-events: auto; }
.th-hub-key {
  font: 900 clamp(28px, 3vw, 38px)/1 var(--sans);
  letter-spacing: -.03em;
  color: var(--fg);
  border-radius: 12px;
  padding: 0 6px;
}
.th-hub-scale {
  font: 700 11px var(--mono);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--accent);
}
.th-hub-row { font: 500 10.5px var(--mono); color: var(--fg-mute); }
.th-hub-row b { color: var(--fg-dim); }
.th-hub-tx {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  margin-top: 4px;
}
.th-hub-tx-to { font: 800 14px var(--mono); color: var(--accent); }
.th-hub-capo { font: 600 10px var(--mono); color: var(--fg-dim); max-width: 18ch; }
.th-hub-tx-btns { display: flex; gap: 5px; }
.th-hub-armed { font: 600 10.5px var(--mono); color: var(--accent); animation: th-blink 1.4s ease infinite; }
@keyframes th-blink { 50% { opacity: .45; } }
.th-hub-txbtn { margin-top: 5px; }

/* =========================================================================
   Neck
========================================================================= */
.th-neck-scroll { overflow-x: auto; }
#th-neck { width: 100%; min-width: 760px; height: auto; display: block; }

.nk-fret   { stroke: var(--border-hi); stroke-width: 1; }
.nk-nut    { stroke: var(--fg-dim); stroke-width: 4; }
.nk-string { stroke: color-mix(in srgb, var(--fg-mute) 70%, transparent); }
.nk-inlay  { fill: color-mix(in srgb, var(--fg-mute) 35%, transparent); }
.nk-fretnum { fill: var(--fg-mute); font: 600 11px var(--mono); text-anchor: middle; }
.nk-window {
  fill: var(--accent-soft);
  stroke: color-mix(in srgb, var(--accent) 32%, transparent);
  stroke-dasharray: 3 4;
}

.nk-note { cursor: pointer; }
.nk-note circle {
  fill: var(--bg-deep);
  stroke: var(--border-hi);
  stroke-width: 1.25;
  transition: fill var(--t-fast) var(--ease), stroke var(--t-fast) var(--ease);
}
.nk-note text {
  fill: var(--fg-dim);
  font: 700 9.5px var(--mono);
  text-anchor: middle;
  dominant-baseline: central;
  pointer-events: none;
}
.nk-note:hover circle { stroke: var(--accent); fill: color-mix(in srgb, var(--accent) 18%, var(--bg-deep)); }
.nk-note.root circle { fill: var(--accent); stroke: var(--accent); filter: drop-shadow(0 0 6px var(--accent-glow)); }
.nk-note.root text { fill: var(--accent-fg); }
.nk-note.chord-lit circle { fill: var(--kind); stroke: var(--kind); filter: drop-shadow(0 0 7px var(--kind-glow)); }
.nk-note.chord-lit text { fill: var(--accent-fg); }
.nk-note.dim { opacity: .16; }
.nk-note.dim.chord-lit { opacity: .3; }

.nk-ghost {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.75;
  stroke-dasharray: 4 4;
  opacity: .75;
  pointer-events: none;
  animation: th-dash 1.4s linear infinite;
}
.nk-ghost.ghost-root { stroke-width: 2.5; }

/* ---- Piano layout (same .nk-note machinery, key-shaped) ----------------- */
.pk-board { min-width: 700px; }
.pk { cursor: pointer; }
/* A piano's keys are white and black in every theme — that is the instrument,
   not the palette, so these do NOT follow --bg/--fg. But they were literal
   hexes, the one place in this file that escaped the token system, and the dot
   contrast broke in light mode. Named locally instead: still fixed, now
   derived and adjustable in one place. */
.th-piano {
  --pk-white: color-mix(in srgb, #ffffff 94%, var(--accent) 6%);
  --pk-white-edge: color-mix(in srgb, var(--pk-white) 72%, #000);
  --pk-black: color-mix(in srgb, #12141b 92%, var(--accent) 8%);
  --pk-black-edge: #05060a;
  --pk-ink: #eef0f6;
  --pk-dot-w: color-mix(in srgb, var(--pk-black) 78%, var(--pk-white));
  --pk-dot-b: color-mix(in srgb, var(--pk-black) 55%, var(--pk-white));
}
.pk-white-key { fill: var(--pk-white); stroke: var(--pk-white-edge); stroke-width: 1; transition: fill var(--t-fast) var(--ease); }
.pk-black-key { fill: var(--pk-black); stroke: var(--pk-black-edge); stroke-width: 1; transition: fill var(--t-fast) var(--ease); }
.pk:hover .pk-white-key { fill: color-mix(in srgb, var(--accent) 16%, var(--pk-white)); }
.pk:hover .pk-black-key { fill: color-mix(in srgb, var(--accent) 35%, var(--pk-black)); }
/* dots need their own contrast against the fixed key colors */
.pk-w:not(.root):not(.chord-lit) circle { fill: var(--pk-dot-w); stroke: color-mix(in srgb, var(--pk-dot-w) 70%, #000); }
.pk-w:not(.root):not(.chord-lit) text { fill: var(--pk-ink); }
.pk-b:not(.root):not(.chord-lit) circle { fill: var(--pk-dot-b); stroke: rgba(255,255,255,.3); }
.pk-b text { fill: var(--pk-ink); }
.pk.root text, .pk.chord-lit text { fill: var(--accent-fg); }
.pk text { font-size: 8px; }
.pk-oct {
  fill: var(--fg-mute);
  font: 600 10px var(--mono);
  text-anchor: middle;
  pointer-events: none;
}

/* =========================================================================
   Harmony rail
========================================================================= */
.th-chord-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 8px;
}
.th-chord {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 6px 4px 5px;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: all var(--t-fast) var(--ease);
  position: relative;
  min-width: 0;
}
.th-chord:hover {
  border-color: color-mix(in srgb, var(--kind) 55%, var(--border));
  background: var(--kind-soft);
}
.th-chord.active {
  border-color: var(--kind);
  background: var(--kind-soft);
  box-shadow: 0 6px 18px -6px var(--kind-glow), inset 0 1px 0 color-mix(in srgb, var(--kind) 28%, transparent);
}
.th-chord-num {
  font: 800 14px var(--mono);
  color: var(--kind);
  letter-spacing: -.02em;
}
.th-chord-name { font: 700 11.5px var(--sans); color: var(--fg); letter-spacing: -.01em; }
.th-chord-note {
  font: 500 9px var(--mono);
  color: var(--fg-mute);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.th-chord-tx {
  font: 700 10.5px var(--mono);
  color: var(--accent);
  margin-top: 2px;
  padding-top: 3px;
  border-top: 1px dashed color-mix(in srgb, var(--accent) 40%, transparent);
  width: 100%;
  text-align: center;
}

/* =========================================================================
   Progression catalogue

   Was a chip row of six loops that left most of the harmony panel empty. It is
   a browsable catalogue now — genre partition, mood axis, and rows whose
   identity is the roman-numeral string. The list scrolls inside the panel so
   the panel keeps its height instead of the page growing.
========================================================================= */
.th-progs {
  display: flex;
  flex-direction: column;
  gap: 7px;
  flex: 1;
  min-height: 0;
}
.th-prog-facets { display: flex; flex-direction: column; gap: 5px; }
.th-facet-row { display: flex; flex-wrap: wrap; gap: 3px; }
.th-facet-row .th-chip { padding: 3px 7px; font-size: 9.5px; letter-spacing: .02em; }
/* the genre's own hue rides on the chip, so the partition is visible at a
   glance rather than only on the rows */
.th-facet-row .th-chip[data-pgenre] { border-left: 3px solid var(--gcol, var(--border)); }
.th-facet-row .th-chip[data-pgenre].active {
  background: color-mix(in srgb, var(--gcol) 18%, transparent);
  border-color: color-mix(in srgb, var(--gcol) 55%, var(--border));
  border-left-color: var(--gcol);
  color: var(--fg);
}
.th-facet-row .th-chip i {
  font-style: normal;
  opacity: .55;
  font-size: 9px;
  margin-left: 3px;
}

.th-prog-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding-right: 3px;
}
.th-prog-none {
  margin: 0;
  padding: 14px 4px;
  font: 500 11px var(--sans);
  color: var(--fg-mute);
}

.th-prow {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  grid-template-areas: "num meta" "names names";
  align-items: center;
  gap: 1px 8px;
  width: 100%;
  text-align: left;
  padding: 5px 8px;
  border-radius: 6px;
  background: var(--bg-soft);
  border: 1px solid transparent;
  border-left: 3px solid var(--gcol, var(--border));
  transition: all var(--t-fast) var(--ease);
}
.th-prow:hover { background: var(--bg-elev); border-color: var(--border-hi); border-left-color: var(--gcol); }
.th-prow.sel {
  background: var(--bg-elev);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  border-left-color: var(--gcol);
}
.th-prow.playing {
  background: var(--accent-soft);
  border-color: var(--accent);
  border-left-color: var(--gcol);
}
/* IDENTITY — the numerals are the name of the thing, so they lead. */
.th-prow-num {
  grid-area: num;
  font: 800 11px var(--mono);
  color: var(--fg);
  letter-spacing: .01em;
  white-space: nowrap;
}
.th-prow.playing .th-prow-num { color: var(--accent); }
/* the same loop spelled in the current key — follows, never leads */
.th-prow-names {
  grid-area: names;
  font: 500 9.5px var(--mono);
  color: var(--fg-mute);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.th-prow-meta {
  grid-area: meta;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  white-space: nowrap;
  overflow: hidden;
}
.th-prow-meta i {
  font-style: normal;
  font: 600 8.5px var(--mono);
  color: var(--fg-mute);
  background: var(--bg-deep);
  border-radius: 4px;
  padding: 2px 5px;
  letter-spacing: .04em;
}
.th-prow-meta b {
  font: 700 9px var(--mono);
  color: var(--fg-mute);
  opacity: .75;
}

/* Expanded row — the grips for every chord in the loop, which is the answer to
   "fine, but how do I play it". */
.th-prow-shapes {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  padding: 9px 10px 11px;
  margin: -2px 0 4px;
  border-radius: 0 0 8px 8px;
  background: var(--bg-elev);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border));
  border-top: 0;
}
.th-prow-shape {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}
.th-prow-shape b { font: 700 10px var(--mono); color: var(--kind, var(--fg-dim)); }
.th-prow-shape i {
  font-style: normal;
  font: 500 8px var(--mono);
  color: var(--fg-mute);
  min-height: 10px;
}
.th-prow-noshape { font: 600 10px var(--mono); color: var(--fg-mute); padding: 22px 10px; }


/* =========================================================================
   Spectrum
========================================================================= */
.th-formula { font: 600 11px var(--mono); color: var(--fg-mute); }
.th-formula sup { font-size: 8px; }
.th-a4 { display: inline-flex; align-items: center; gap: 4px; margin-left: auto; }
.th-a4-label {
  font: 700 10px var(--mono);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--fg-mute);
  margin-right: 2px;
}
.th-a4 button {
  font: 700 11px var(--mono);
  color: var(--fg-dim);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 3px 8px;
  transition: all var(--t-fast) var(--ease);
}
.th-a4 button:hover { border-color: var(--accent); color: var(--fg); }
.th-a4 button.active { background: var(--accent-soft); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.th-a4-val { font: 700 12px var(--mono); color: var(--accent); min-width: 52px; text-align: center; }

/* The rail is SVG; the live trace is a canvas sitting behind it, so the
   reference grid always stays readable over whatever is sounding. */
.th-spectrum-wrap { width: 100%; position: relative; flex: 0 0 auto; }
#th-scope {
  position: absolute;
  inset: 28px 0 30px 0;      /* matches the SVG's y0/y1 plot area */
  width: 100%;
  pointer-events: none;
  opacity: .82;
}
.th-spectrum-wrap #th-spectrum { position: relative; }

.sp-cross {
  stroke: var(--fg);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  opacity: .55;
  pointer-events: none;
}
.sp-cross-lab {
  fill: var(--fg);
  font: 700 10px var(--mono);
  text-anchor: middle;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4;
  pointer-events: none;
}

/* Tempo — progressions play at a real BPM now, so there has to be one. */
.th-tempo {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 2px;
}
.th-tempo button {
  font: 700 12px var(--mono);
  color: var(--fg-mute);
  background: transparent;
  border: 0;
  border-radius: 6px;
  padding: 2px 7px;
  transition: color var(--t-fast) var(--ease);
}
.th-tempo button:hover { color: var(--accent); }
.th-tempo-val {
  font: 700 11px var(--mono);
  color: var(--fg-dim);
  min-width: 46px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
#th-spectrum { display: block; width: 100%; }

.sp-band { fill: color-mix(in srgb, var(--fg) 2.5%, transparent); }
.sp-band.alt { fill: color-mix(in srgb, var(--fg) 5.5%, transparent); }
.sp-band-label {
  fill: var(--fg-mute);
  font: 700 8.5px var(--mono);
  letter-spacing: .14em;
  text-anchor: middle;
}
.sp-tick {
  stroke: color-mix(in srgb, var(--fg-dim) 55%, transparent);
  stroke-width: 1.5;
  cursor: pointer;
  transition: stroke var(--t-fast) var(--ease);
}
.sp-tick:hover { stroke: var(--fg); stroke-width: 3; }
.sp-tick.tonic {
  stroke: var(--accent);
  stroke-width: 2.5;
  filter: drop-shadow(0 0 5px var(--accent-glow));
}
.sp-tick.chord-lit { stroke: var(--kind); stroke-width: 2.5; }
.sp-tonic-label { fill: var(--accent); font: 700 9.5px var(--mono); text-anchor: middle; pointer-events: none; }
.sp-tonic-hz { fill: var(--fg-mute); font: 500 8.5px var(--mono); text-anchor: middle; pointer-events: none; }

/* =========================================================================
   Drills — the panel that asks
========================================================================= */
.th-drill-types {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 9px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--hairline);
}
.th-drill-types .th-chip { padding: 4px 9px; font-size: 10.5px; }

/* Running record — the panel is a scoreboard when it is not mid-question, so
   the column is never dead space. */
.th-drill-board {
  margin-top: auto;
  padding-top: 9px;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.th-drill-board-h {
  font: 700 9px var(--mono);
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--fg-mute);
  margin-bottom: 3px;
}
.th-drill-line {
  display: grid;
  grid-template-columns: 1fr auto 46px 28px;
  align-items: center;
  gap: 6px;
  padding: 1px 0;
}
.th-drill-line span {
  font: 600 10px var(--mono);
  color: var(--fg-mute);
  white-space: nowrap;
}
.th-drill-line.cur span { color: var(--accent); }
.th-drill-line i {
  font-style: normal;
  font: 500 9px var(--mono);
  color: var(--fg-mute);
  opacity: .8;
}
.th-drill-bar {
  height: 3px;
  border-radius: 99px;
  background: var(--bg-deep);
  overflow: hidden;
}
.th-drill-bar span {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--accent);
  transition: width var(--t-base) var(--ease);
}
.th-drill-line.cur .th-drill-bar span { background: var(--accent); }
.th-drill-line b {
  font: 700 9px var(--mono);
  color: var(--fg-dim);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.th-drill-q {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  background: var(--bg-soft);
  border: 1px solid var(--border);
  transition: border-color var(--t-base) var(--ease), background var(--t-base) var(--ease);
}
.th-drill-q.right { border-color: color-mix(in srgb, var(--c-mint-1) 60%, var(--border)); }
.th-drill-q.wrong { border-color: color-mix(in srgb, var(--c-coral-1) 60%, var(--border)); }

.th-drill-prompt {
  margin: 0;
  font: 600 13.5px/1.5 var(--sans);
  color: var(--fg);
  letter-spacing: -.01em;
}
.th-drill-prompt b { color: var(--accent); font-weight: 800; }

/* The instruction that matters: this question is answered on the instrument,
   not in this box. */
.th-drill-mode {
  margin: 0;
  font: 600 9.5px var(--mono);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--accent);
}

.th-drill-opts { display: flex; flex-wrap: wrap; gap: 5px; }
.th-drill-opts .th-chip { padding: 5px 10px; }

.th-drill-verdict {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  padding: 9px 10px;
  border-radius: var(--radius-xs);
  font: 500 11px/1.55 var(--sans);
  color: var(--fg-dim);
}
.th-drill-verdict b {
  font: 800 10px var(--mono);
  text-transform: uppercase;
  letter-spacing: .14em;
}
.th-drill-verdict.right { background: color-mix(in srgb, var(--c-mint-1) 12%, transparent); }
.th-drill-verdict.right b { color: var(--c-mint-1); }
.th-drill-verdict.wrong { background: color-mix(in srgb, var(--c-coral-1) 12%, transparent); }
.th-drill-verdict.wrong b { color: var(--c-coral-1); }

/* =========================================================================
   Explain — the why, folded into the panel it explains

   Not a tooltip and not a help page: a caption row that belongs to the panel,
   off by default so the instrument stays an instrument.
========================================================================= */
.th-why {
  margin: 10px 0 0;
  padding: 9px 11px;
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--accent) 6%, var(--bg-soft));
  border-left: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
  font: 500 11px/1.6 var(--sans);
  color: var(--fg-dim);
  letter-spacing: .005em;
}
.th-why:empty { display: none; }

/* =========================================================================
   Shapes — the grip, the voicing, the inversion
========================================================================= */
.th-shape-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 0 0 auto;
  gap: 5px;
  padding: 8px 8px 7px;
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, var(--bg-elev), var(--bg-soft));
  border: 1px solid var(--border);
  position: relative;
  overflow: hidden;
}
.th-shape-card::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--kind, var(--accent)), var(--kind-2, var(--accent-2)));
}
.th-shape-top { display: flex; align-items: baseline; gap: 9px; }
.th-shape-num {
  font: 700 11px var(--mono);
  color: var(--kind, var(--accent));
  letter-spacing: .04em;
}
.th-shape-name { font: 800 16px var(--sans); letter-spacing: -.02em; color: var(--fg); }
.th-shape-name i { font-style: normal; font-weight: 500; color: var(--fg-mute); }
.th-shape-art { display: grid; place-items: center; }
.th-shape-note {
  font: 500 9.5px var(--mono);
  color: var(--fg-mute);
  text-align: center;
  line-height: 1.45;
  max-width: 30ch;
}
.th-noshape {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  font: 600 11px var(--mono);
  color: var(--fg-mute);
  padding: 26px 8px;
}
.th-noshape span { font: 700 13px var(--mono); color: var(--fg-dim); }

.th-voicing { margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--hairline); }
.th-voicing-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 7px;
}
.th-voicing-head > span:first-child {
  font: 700 9px var(--mono);
  text-transform: uppercase; letter-spacing: .14em;
  color: var(--fg-mute);
}
.th-voicing-inv { font: 700 10px var(--mono); color: var(--accent); }
.th-roll { width: 100%; height: auto; display: block; }
.th-roll-w { fill: var(--pk-white); stroke: var(--pk-white-edge); stroke-width: 1; }
.th-roll-b { fill: var(--pk-black); stroke: var(--pk-black-edge); stroke-width: 1; }
.th-roll-w.on   { fill: color-mix(in srgb, var(--accent) 42%, var(--pk-white)); }
.th-roll-b.on   { fill: color-mix(in srgb, var(--accent) 62%, var(--pk-black)); }
.th-roll-w.bass, .th-roll-b.bass { fill: var(--accent); }
.th-roll-lab {
  font: 700 7px var(--mono);
  fill: var(--pk-black); text-anchor: middle; pointer-events: none;
}
.th-roll-lab.blk { fill: var(--pk-ink); }
.th-voice-play { margin-top: 9px; width: 100%; }

/* =========================================================================
   Barre — the movable grip, ticked against the key
========================================================================= */
.th-grips { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; flex: 0 0 auto; }
.th-barre-slot { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.th-grip-fam { display: flex; align-items: baseline; gap: 9px; }
.th-grip-fam .th-tray-label { flex: 0 0 26px; }

.th-barre-card {
  display: flex;
  flex-direction: column;
  gap: 7px;
  flex: 1 1 auto;
  min-height: 0;
  padding: 8px 9px 9px;
  border-radius: var(--radius-sm);
  /* the warm marquee treatment the Songbook barre panel carries, kept so the
     feature reads as the same idea in both apps */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--c-amber-1) 7%, var(--bg-soft)), var(--bg-soft));
  border: 1px solid var(--border);
}
.th-barre-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.th-barre-name { font: 800 17px var(--sans); letter-spacing: -.02em; color: var(--fg); }
.th-barre-num {
  font: 800 12px var(--mono);
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: 6px;
  padding: 3px 8px;
}
.th-barre-out { font: 600 9.5px var(--mono); color: var(--fg-mute); letter-spacing: .04em; }
.th-barre-art { display: grid; place-items: center; }

/* Fret picker — twelve positions, not a slider. Each button says what chord
   the grip makes there; the ones in the current key are marked. */
.th-fretpick {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 3px;
}
.th-fret {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 6px 3px 5px;
  border-radius: 7px;
  background: var(--bg-deep);
  border: 1px solid var(--hairline);
  transition: all var(--t-fast) var(--ease);
  min-width: 0;
}
.th-fret:hover { border-color: var(--accent); background: var(--bg-elev); }
.th-fret-n {
  font: 600 8px var(--mono);
  color: var(--fg-mute);
  letter-spacing: .04em;
}
.th-fret-chord {
  font: 700 12px var(--mono);
  color: var(--fg-dim);
  line-height: 1.1;
  white-space: nowrap;
}
.th-fret-num {
  font: 700 8px var(--mono);
  color: transparent;
  min-height: 10px;
  letter-spacing: .04em;
}
/* in the key */
.th-fret.in {
  background: color-mix(in srgb, var(--accent) 10%, var(--bg-deep));
  border-color: color-mix(in srgb, var(--accent) 34%, var(--hairline));
}
.th-fret.in .th-fret-chord { color: var(--fg); }
.th-fret.in .th-fret-num { color: var(--accent); }
/* where the grip is now */
.th-fret.at {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 12px var(--accent-glow);
}
.th-fret.at .th-fret-n,
.th-fret.at .th-fret-chord,
.th-fret.at .th-fret-num { color: var(--accent-fg); }
.th-fret:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.th-barre-note {
  font: 500 9.5px var(--mono);
  color: var(--fg-mute);
  line-height: 1.5;
  border-top: 1px solid var(--hairline);
  padding-top: 8px;
  margin-top: auto;
}
.th-barre-note b {
  color: var(--accent);
  font: 700 9px var(--mono);
  text-transform: uppercase;
  letter-spacing: .12em;
}

/* The grip drawn on the real fretboard — Songbook's card cannot travel, this
   can, so the slider visibly moves the shape along the neck. */
.nk-grip { pointer-events: none; --grip: var(--c-amber-1); }
.nk-grip.in-key { --grip: var(--c-mint-1); }
.nk-grip-bar {
  fill: color-mix(in srgb, var(--grip) 30%, transparent);
  stroke: var(--grip);
  stroke-width: 1.5;
}
/* hollow rings — the grip sits OVER the scale dots and must not hide them */
.nk-grip-dot {
  fill: none;
  stroke: var(--grip);
  stroke-width: 2.5;
}
.nk-grip-dot.root {
  stroke-width: 3.5;
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--grip) 60%, transparent));
}
.nk-grip-mute {
  fill: var(--grip);
  font: 700 12px var(--mono);
  text-anchor: middle;
  dominant-baseline: central;
}
/* the label is what stops this reading as a capo */
.nk-grip-label {
  fill: var(--grip);
  font: 800 14px var(--mono);
  text-anchor: middle;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 5;
}
.nk-grip-sub {
  fill: var(--fg-mute);
  font: 600 9px var(--mono);
  text-anchor: middle;
  letter-spacing: .08em;
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4;
}

/* =========================================================================
   Cross-view pulse + tooltip
========================================================================= */
.pulsing { animation: th-pulse .65s var(--ease-out); }
@keyframes th-pulse {
  0%   { filter: drop-shadow(0 0 0 transparent); opacity: 1; }
  25%  { filter: drop-shadow(0 0 16px var(--accent)) brightness(1.6); }
  100% { filter: drop-shadow(0 0 0 transparent); }
}
span.pulsing, button.pulsing {
  animation: th-pulse-box .65s var(--ease-out);
}
@keyframes th-pulse-box {
  25% { box-shadow: 0 0 0 5px var(--accent-soft), 0 0 22px var(--accent-glow); }
}

.th-tip {
  position: fixed;
  z-index: 90;
  pointer-events: none;
  font: 600 11.5px var(--mono);
  color: var(--fg);
  background: var(--mat-1);
  border: 1px solid var(--border-hi);
  border-radius: 9px;
  padding: 6px 10px;
  box-shadow: var(--shadow-md);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  white-space: nowrap;
}
.th-tip b { color: var(--accent); }

/* =========================================================================
   Responsive
========================================================================= */
@media (max-width: 1080px) {
  .th-grid {
    grid-template-columns: 1fr;
    grid-template-areas: "wheel" "neck" "harmony" "shapes" "barre" "drill" "spectrum";
  }
  .th-wheel-wrap { min-height: 0; }
  #th-wheel { max-width: 380px; }
}
@media (max-width: 640px) {
  .th-bar { gap: 8px; }
  .th-hint { display: none; }
  .th-chord-row { grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  .wh-trace, .wh-trace-dot, .wh-trace-label { animation-duration: .01s; animation-delay: 0s; }
  .pulsing { animation: none; }
  .nk-ghost, .wh-seg.tx-target { animation: none; }
}
