/* =============================================================================
   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. */
/* Edge to edge, no gutter. The rack used to sit in a 2100px centred column with
   a page-coloured margin around it, which is the "website" feeling this is not
   supposed to have — a workspace fills the glass. The panels are separated by
   hairlines they share, not by empty space (see .th-grid / .th-panel below). */
.th-shell {
  max-width: none;
  margin: 0;
  padding: 0;
  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 clamp(10px, 1vw, 16px) 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): 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.

   The right column is the TRAINING column — progressions above, drills below —
   and it owns the full height of the rack, floor included. Three things make
   that possible, and they are one move, not three:

     · Barre gives up width (its column is the narrowest now). A grip diagram
       and twelve fret buttons never needed 300px; the drills do.
     · Spectrum stops at the barre column instead of running the full width, so
       the training column continues past the floor line to the bottom edge.
     · The harmony row is split in two, which puts the progressions/drills seam
       HALFWAY DOWN HARMONY rather than at the neck/harmony line — progressions
       keep growing while the question below them gets a screen's worth of room
       ([RYDER-VERDICT] 2026-07-30).

   Row 4 is `auto` (the spectrum's own height); rows 2 and 3 are equal halves,
   which is what makes the seam land exactly mid-harmony. */
.th-grid {
  display: grid;
  grid-template-columns:
    minmax(290px, 330px)    /* key            */
    minmax(520px, 1.4fr)    /* neck / harmony */
    minmax(226px, 252px)    /* shapes / barre */
    minmax(340px, 410px);   /* progressions / drills */
  grid-template-areas:
    "key neck    shapes progs"
    "key harmony barre  progs"
    "key harmony barre  drills"
    "spectrum spectrum spectrum drills";
  /* 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.1fr)
    minmax(64px, .55fr)
    minmax(64px, .55fr)
    minmax(0, auto);
  /* No gutters between panels. They share hairline edges instead — one surface
     divided, rather than eight cards floating on a page. */
  gap: 0;
  border-top: 1px solid var(--border);
  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 — and keep owning one
   tall column with the seam mid-harmony. */
@media (max-width: 1400px) {
  .th-grid {
    grid-template-columns: minmax(270px, 310px) minmax(0, 1.3fr) minmax(300px, 360px);
    grid-template-areas:
      "key    neck    progs"
      "key    harmony progs"
      "key    harmony drills"
      "shapes barre   drills"
      "spectrum spectrum spectrum";
    grid-template-rows:
      minmax(0, 1fr) minmax(0, .5fr) minmax(0, .5fr) minmax(0, .9fr) 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 -------------
   Each panel draws its OWN right and bottom edge and the grid draws the top,
   so every seam in the rack is exactly one hairline wide no matter which
   panels meet there — no doubled borders, no gap for the page to show
   through. The floating-card treatment (radius + drop shadow) is gone with
   the gutters: a card floats above a page, and there is no page here. */
.th-panel {
  background: var(--mat-1);
  border: 0;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 8px 10px 9px;
  box-shadow: none;
  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; }
/* The neck scales with its column, and the column has no outer cap any more
   (the shell fills the screen). Past ~1040px a wider fretboard buys nothing —
   the frets are already generous — and it would spend the height that the row
   below it needs, so it stops growing there. */
#th-neck { width: 100%; min-width: 760px; max-width: 1040px; 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; }

/* ---- Starred loops -------------------------------------------------------
   The one thing in this catalogue that is yours. Genre and mood were true
   about these progressions before you arrived; a star is not, so it gets its
   own mark rather than another facet chip.

   It sits in the row's left margin beside the genre bar — a column you scan
   down, instead of a control hidden inside each row — and it is its OWN button
   (a sibling of the row, never a child; see renderProgressions), so keeping a
   loop never also plays it. Amber, because the accent carries key state and
   the left bar carries genre; a third meaning needs a third colour. */
.th-prow-slot { position: relative; display: block; }
.th-prow-slot .th-prow { padding-left: 26px; }
.th-prow-star {
  position: absolute;
  left: 6px;
  top: 5px;              /* on the numerals line — the row's identity line */
  width: 17px; height: 17px;
  display: grid;
  place-items: center;
  font: 400 12px/1 var(--sans);
  color: color-mix(in srgb, var(--fg) 26%, transparent);
  background: transparent;
  border: 0;
  border-radius: 5px;
  transition: all var(--t-fast) var(--ease);
}
.th-prow-star:hover {
  color: var(--c-amber-1);
  background: color-mix(in srgb, var(--c-amber-1) 16%, transparent);
}
.th-prow-star.on { color: var(--c-amber-1); }
.th-prow-star.on:hover { transform: scale(1.14); }
.th-prow-star:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The head toggle reads as a count of what you have kept until you switch it
   on, and then it IS the filter. Amber over the accent, same as the marks. */
.th-star-chip { letter-spacing: .06em; font-variant-numeric: tabular-nums; }
.th-star-chip:hover { border-color: var(--c-amber-1); }
.th-star-chip.active {
  background: color-mix(in srgb, var(--c-amber-1) 15%, transparent);
  border-color: color-mix(in srgb, var(--c-amber-1) 50%, var(--border));
  color: var(--c-amber-1);
}


/* =========================================================================
   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

   THE PANEL DOES NOT SCROLL. A drill is a single question you answer and move
   past; having to scroll to see the options, or to see what your record is
   after answering, turns three seconds of work into a hunt. So the three
   parts — pick a type, the question, the record — are each sized to hold
   their worst case (six types, twelve interval options, a two-line reveal)
   inside one column of height, and the question keeps whatever slack is left
   over. The panel body keeps `overflow: auto` as a floor for very short
   windows: nothing is ever clipped away, it just should not come to that.
========================================================================= */
.th-drill-types {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  margin-bottom: 7px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--hairline);
}
.th-drill-types .th-chip { padding: 3px 8px; font-size: 10px; letter-spacing: .04em; }

/* The question owns the slack: it is the part whose height actually varies. */
#th-drill {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Running record — the panel is a scoreboard when it is not mid-question, so
   the column is never dead space. Two columns of three rather than six rows:
   the same six numbers in half the height, which is height the question needs
   more than the scoreboard does. */
.th-drill-board {
  margin-top: auto;
  padding-top: 8px;
  border-top: 1px solid var(--hairline);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px 14px;
}
.th-drill-board-h {
  grid-column: 1 / -1;
  font: 700 9px var(--mono);
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--fg-mute);
  margin-bottom: 2px;
}
.th-drill-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 34px 26px;
  align-items: center;
  gap: 5px;
  padding: 0;
}
.th-drill-line span {
  font: 600 9.5px var(--mono);
  color: var(--fg-mute);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.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;
  flex: 0 0 auto;      /* keeps its own height; it never gets squashed */
  gap: 7px;
  padding: 8px 9px;
  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 12.5px/1.4 var(--sans);
  color: var(--fg);
  letter-spacing: -.01em;
}
.th-drill-prompt b { color: var(--accent); font-weight: 800; }

/* Replay sits on the same line as the instruction rather than on a line of its
   own — it is one chip, and a chip does not deserve a row of the panel. */
.th-drill-act {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  width: 100%;
}
.th-drill-act:empty { display: none; }

/* 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: 4px; }
.th-drill-opts .th-chip { padding: 4px 9px; font-size: 10.5px; }

/* Verdict and "next" share a line: the answer and the way forward are one
   beat, and stacking them cost a whole row for a single button. */
.th-drill-verdict {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 3px 8px;
  width: 100%;
  padding: 7px 9px;
  border-radius: var(--radius-xs);
  font: 500 10.5px/1.5 var(--sans);
  color: var(--fg-dim);
}
.th-drill-verdict span { grid-column: 1 / -1; }
.th-drill-verdict b {
  font: 800 10px var(--mono);
  text-transform: uppercase;
  letter-spacing: .14em;
}
.th-drill-verdict .th-chip { padding: 3px 10px; }
.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: 3px; margin-bottom: 7px; 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: 7px; }
.th-grip-fam .th-tray-label { flex: 0 0 22px; letter-spacing: .08em; }
.th-grip-fam .th-tray-chips { gap: 3px; }
.th-grip-fam .th-chip { padding: 3px 7px; font-size: 10px; letter-spacing: .03em; }

.th-barre-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 auto;
  min-height: 0;
  padding: 8px 7px 8px;
  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 15px var(--sans); letter-spacing: -.02em; color: var(--fg); }
.th-barre-num {
  font: 800 11px var(--mono);
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: 6px;
  padding: 3px 7px;
}
.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. */
/* Four across, in a column that is now the narrowest in the rack — so the cells
   carry the chord name at 11px and lean on tight padding rather than dropping
   to three across, which would have cost a row of height to save width the
   drills wanted anyway. */
.th-fretpick {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
}
.th-fret {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 5px 2px 4px;
  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 11px var(--mono);
  color: var(--fg-dim);
  line-height: 1.1;
  white-space: nowrap;
  letter-spacing: -.02em;   /* A♭maj7 is six characters and still has to fit */
}
.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
========================================================================= */
/* The single-column stack is already set at 1180px. This step only widens the
   wheel — it used to redeclare the areas under names no panel carries any more
   ("wheel", "drill", and no progressions at all), which silently dropped every
   panel out of its area and left the stack to auto-placement. */
@media (max-width: 1080px) {
  .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; }
}
