:root {
  --clay: #c4a07a;
  --clay-dark: #6b4a32;
  --ink: #1c140f;
  --paper: #f6efe6;
  --paper-2: #efe4d4;
  --line: #d7c4ae;
  --accent: #8b3d2f;
  --good: #2f6b4f;
  /* Chamber symbology — same in every theme. */
  --ch1: #8b3d2f;
  --ch2: #2f5f73;
  --ch3: #6b4a8b;
  --panel: #ffffff;
  --head: #faf7f2;
  --muted: #6a5648;
  --oca-bg: #f3e6d6;
  --key: #faf7f2;
  --key-line: #c9b8a4;
  --key-label: #7a6552;
  /* Rest / bar / tempo tokens — symbology, same in every theme. */
  --token-neutral: #c9b8a4;
  --token-pause: #8a7a6a;
  --rest-ink: #7a6552;
  --ch-line-empty: #ddd4c8;
  --warn-bg: #fbe9e5;
  --warn-ink: #7a2a1f;
  --zen: #2b1d12;
  --zen-btn: #4a331f;
  --key-now: #f3d2b3;
  --key-now-ring: #c45c26;
  --key-now-black: #6b4a32;
  --key-now-black-ring: #e8c07a;
}
/* Hidden Hyrule Field theme: enable with ?oot
   Dark saturated grass from hyrule.jpg so white type reads; fingering cards
   stay cream. Chamber colors (--ch1/2/3) are intentionally not overridden. */
html[data-theme="oot"] {
  --clay: #2e8a1c;
  --clay-dark: #d4f0c8;
  --ink: #082808;
  --paper: #f4f8f0;
  --paper-2: #12500e;
  --line: rgba(200, 240, 160, 0.38);
  --good: #2e8a1c;
  --panel: rgba(10, 64, 10, 0.92);
  --head: rgba(12, 76, 12, 0.88);
  --muted: rgba(245, 250, 240, 0.84);
  --oca-bg: #f3f0dc;
  --key: #ffffff;
  --key-line: #c9b8a4;
  --key-label: #1a3a12;
  --ch-line-empty: #3d7a28;
  --zen: #071807;
  --zen-btn: #142814;
  --key-now: #7edc5a;
  --key-now-ring: #2d8a18;
  --key-now-black: #145c10;
  --key-now-black-ring: #b8f090;
  --menu: #0a3a0a;
}
/* Light surfaces inside the Hyrule theme: the library dialog card and the perf
   meters pop keep the app's cream look, so they need the light-surface token
   values locally (OOT re-scopes --ink/--muted/--panel for the dark panels,
   which made the save-dialog input/Cancel and the perf table unreadable). */
html[data-theme="oot"] .lib-dialog-card { --panel: #fff; }
html[data-theme="oot"] .lib-dialog-input {
  background: #fff; border-color: rgba(24, 80, 18, 0.5);
}
html[data-theme="oot"] .lib-dialog-row button:not(.lib-dialog-ok) {
  background: #fff; border-color: rgba(24, 80, 18, 0.5); color: #12380d;
}
html[data-theme="oot"] .lib-toast { --ink: #eaf6e2; }
html[data-theme="oot"] .perf-pop { --muted: rgba(20, 62, 14, 0.82); }
/* Buttons on OOT light surfaces lose the white-on-glass treatment: the Lite
   switch in the perf pop and the toast's ghost buttons sit on cream/warm
   light backgrounds where white text vanished. */
html[data-theme="oot"] .perf-pop button {
  color: #12380d; border-color: rgba(24, 80, 18, 0.5);
}
html[data-theme="oot"] .glitch-toast button.ghost {
  color: var(--warn-ink); border-color: rgba(122, 42, 31, 0.45);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--paper); color: var(--ink);
  font: 15px/1.45 "Source Sans 3", "Segoe UI", sans-serif; }
html[data-theme="oot"] {
  background-color: var(--ink);
  color: #fff;
}
html[data-theme="oot"] body { background: transparent; color: #fff; }
html[data-theme="oot"] body::before {
  content: "";
  position: fixed;
  inset: -4px;
  z-index: 0;
  pointer-events: none;
  background: url("../hyrule.webp") center / cover no-repeat;
  filter: blur(2px);
}
html[data-theme="oot"] .wrap {
  position: relative;
  z-index: 1;
}
html[data-theme="oot"] .card {
  background: rgba(255, 252, 245, 0.94);
  color: #14200c;
}
html[data-theme="oot"] .card.rest .compact,
html[data-theme="oot"] .card.rest .nm,
html[data-theme="oot"] .card.live.is-rest .nm,
html[data-theme="oot"] .card.live .rest-over {
  color: var(--rest-ink);
}
/* --ink turns dark in the theme, so the .card.now ring would vanish against
   the dark grass; give it a white ring (mirrors .tok.now's white + ink).
   An outline with offset keeps a gap of grass between ring and cream card. */
html[data-theme="oot"] .card.now {
  box-shadow: none;
  outline: 3px solid #fff;
  outline-offset: 2px;
}
/* The ring paints 5px outside the card; zen clips at .sheet.live (overflow
   hidden) and the live card fills it edge to edge, so pad the sheet — the
   clip boundary is the padding box, which gives the ring room to breathe. */
html[data-theme="oot"] #tabPanel.focus .sheet.live {
  padding: 6px;
}
html[data-theme="oot"] header.app {
  background: rgba(6, 40, 6, 0.92);
  color: #fff;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
html[data-theme="oot"] .panel {
  color: #fff;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
/* The glass blur isolates each .panel into its own stacking context, so the
   perf pop (descending from the playback head's glass) could never paint
   over the fingering-cards panel below, whatever its z-index said. Order the
   two sections explicitly: the input/playback panel rides above the tabs. */
html[data-theme="oot"] section.panel.noprint { position: relative; z-index: 2; }
html[data-theme="oot"] #tabPanel { position: relative; z-index: 1; }
html[data-theme="oot"] .block {
  background: rgba(8, 52, 8, 0.4);
}
html[data-theme="oot"] .box-head,
html[data-theme="oot"] .playback-head,
html[data-theme="oot"] #inputBlock .input-legend {
  background: rgba(12, 68, 12, 0.45);
}
html[data-theme="oot"] .box-title,
html[data-theme="oot"] .box-sub,
html[data-theme="oot"] .tempo-lab,
html[data-theme="oot"] button.ghost,
html[data-theme="oot"] .toggle-btn,
html[data-theme="oot"] .seg-btn,
html[data-theme="oot"] h2#title,
html[data-theme="oot"] .click-help,
html[data-theme="oot"] #stats,
html[data-theme="oot"] .check-lab {
  color: #fff;
}
html[data-theme="oot"] button.ghost,
html[data-theme="oot"] .toggle-btn {
  border-color: rgba(255, 255, 255, 0.45);
}
html[data-theme="oot"] .toggle-btn.on {
  background: #082808;
  color: #fff;
  border-color: #082808;
}
/* Selected segment gets the same dark fill as the toggle, but keeps its plain
   --line border-left: the divider between segments is the right segment's
   border-left, so recoloring it dark would make the Grid|Scroll divider
   vanish only when Scroll or Single is selected (asymmetric with Grid).
   Keeping it painted makes dividers identical in every selection state. */
html[data-theme="oot"] .seg-btn.on {
  background: #082808;
  color: #fff;
}
html[data-theme="oot"] .mode-seg {
  background: rgba(6, 36, 6, 0.6);
  border-color: rgba(255, 255, 255, 0.35);
}
/* Bar lines between fingering cards paint with --ink, which the theme turns
   near-black #082808 — invisible on the dark green glass panels. White keeps
   them as legible as the white-on-grass chrome; accent bar lines (.has-note)
   stay accent-tinted via their higher-specificity rule. */
html[data-theme="oot"] .tab-bar::before {
  background: #fff;
}
html[data-theme="oot"] textarea#src,
html[data-theme="oot"] #inputBlock textarea#src {
  background: #0a3a0a;
  color: #fff;
}
html[data-theme="oot"] .lib-dd-btn,
html[data-theme="oot"] .lib-dd-menu,
html[data-theme="oot"] .box-head select,
html[data-theme="oot"] #scale,
html[data-theme="oot"] .inst-sel,
html[data-theme="oot"] .inst-sel option {
  background: var(--menu) !important;
  color: #fff !important;
  border-color: rgba(255, 255, 255, 0.35);
}
html[data-theme="oot"] .lib-dd-opt {
  color: #fff;
}
html[data-theme="oot"] .lib-dd-opt:hover,
html[data-theme="oot"] .lib-dd-opt:focus-visible,
html[data-theme="oot"] .lib-dd-opt[aria-selected="true"] {
  background: #12500e;
}
html[data-theme="oot"] .lib-dd-group,
html[data-theme="oot"] .lib-dd-foot {
  color: rgba(245, 250, 240, 0.8);
}
html[data-theme="oot"] kbd {
  background: rgba(8, 48, 8, 0.7);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.3);
}
/* Debug-panel test-note pills: the shared rule pairs --clay-dark (pale green
   here) with --paper (near-white) — unreadable in OOT — and its hover pairs
   --clay (dark green) with --ink (near-black). Pin readable pairs instead. */
html[data-theme="oot"] #dbgPanel .dbg-note {
  color: #12380d;
}
html[data-theme="oot"] #dbgPanel .dbg-note:hover {
  background: #12500e;
  color: #fff;
}
header.app {
  background: var(--ink); color: var(--paper);
  padding: 14px 20px;
  /* The ocarina picker centers over the whole sticky bar, like the section
     headers do: equal side columns on both sides of the centered pick. */
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: baseline; gap: 4px 14px;
  position: sticky; top: 0; z-index: 5;
}
header.app h1 { font-size: 18px; margin: 0; font-weight: 650; letter-spacing: .02em; white-space: nowrap; }
header.app span { opacity: .7; font-size: 13px; }
.inst-picker { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; justify-self: center; }
@media (max-width: 760px) {
  header.app { display: flex; flex-wrap: wrap; }
  .inst-picker { margin-inline: auto; }
}
.inst-sel {
  background: rgba(255,255,255,.08); color: var(--paper);
  border: 1px solid rgba(255,255,255,.28); border-radius: 6px;
  padding: 4px 8px; font: 13px/1 inherit; cursor: pointer;
}
.inst-sel:hover { background: rgba(255,255,255,.16); }
.inst-sel option { color: var(--ink); background: var(--paper); }
.wrap { max-width: 1440px; margin: 0 auto; padding: 16px 22px 80px; }
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; margin: 0 0 14px; }
/* The square box-head sits flush at the block's top: round its own corners so
   the head's background doesn't poke past the block's rounded border
   (top-left/top-right of each sub-section were visually clipped). */
.block > .box-head { border-radius: 7px 7px 0 0; }
label.k { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--clay-dark); font-weight: 700; display: block; margin-bottom: 6px; }
textarea { display: block; } /* baseline-gap fix — LOW specificity on purpose: the
   `.block.collapsed > :not(.box-head)` hide rule (0,3,0) must keep beating it;
   `textarea#src` carries an ID and would win over any class-level rule */
textarea#src {
  width: 100%; min-height: 74px; resize: vertical; font: 15px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; background: var(--paper);
}
.row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px; }
button, select {
  background: var(--ink); color: var(--paper); border: 0; border-radius: 7px;
  padding: 7px 12px; cursor: pointer; font: 13px/1 inherit;
}
button.ghost { background: transparent; color: var(--ink); border: 1px solid var(--line); }
/* A disabled button must read as inert across the family (dark, ghost, round) */
button:disabled {
  opacity: .45; cursor: not-allowed; filter: saturate(.4);
}
/* Practice buttons are state toggles, not primary actions: off = outline
   (nothing black/active-looking), engaged = the app's filled toggle. In zen
   the active hue differs per mode: green = play running, blue = practice
   running. */
#practiceBtn { padding: 7px 12px; }
/* Zen transport: play left, practice right — BOTH at the big size; the
   running mode wears its hue (green = play, blue = practice), paused =
   neutral dark. */
#tabPanel.focus .focus-btn.play {
  width: 92px; height: 92px; font-size: 40px;
  background: var(--zen-btn);
  padding: 0;
}
#tabPanel.focus .focus-btn.play.is-playing { background: var(--good); }
#tabPanel.focus #practiceFocusBtn {
  width: 92px; height: 92px; font-size: 36px;
}
#tabPanel.focus #practiceFocusBtn.on { background: var(--ch2); }
button.tiny { padding: 4px 8px; font-size: 12px; }
.toggle-btn { background: transparent; color: var(--ink); border: 1px solid var(--line); }
.toggle-btn.on { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.err { color: var(--accent); font-size: 13px; }
.err:empty { display: none; }
/* OoT: the accent's dark red disappears on the grass — errors need the same
   bright salmon the bad-chip scribbles wear. */
html[data-theme="oot"] .err { color: #ff9d80; }
.range-warn {
  display: flex; align-items: center; gap: 10px;
  background: var(--warn-bg); color: var(--warn-ink);
  border: 1.5px solid var(--accent); border-left-width: 5px;
  border-radius: 8px; padding: 10px 14px; margin: 0 0 12px;
  font-size: 14px; font-weight: 600;
  animation: rangeWarnPulse 1.6s ease-in-out 3;
}
.range-warn[hidden] { display: none; }
.range-warn .rw-icon { font-size: 20px; line-height: 1; }
@keyframes rangeWarnPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(139,61,47,0); }
  50% { box-shadow: 0 0 0 4px rgba(139,61,47,.22); }
}
.card.oor {
  border: 2px solid var(--accent);
  background: var(--warn-bg);
}
.card.oor:not(.tie) {
  display: flex; flex-direction: column; justify-content: center;
}
.card.oor.tie .compact { color: var(--accent); }
.card.oor.tie .meta .nm { color: var(--accent); }
.card.oor .compact.oor-mark {
  display: flex; align-items: center; justify-content: center;
  min-height: 92px; font-size: 52px; font-weight: 800; color: var(--accent);
}
.card.oor .meta .nm { color: var(--warn-ink); font-weight: 700; }
.badge.oor-badge { background: var(--accent); text-transform: uppercase; }
.kb { display: flex; gap: 0; align-items: flex-end; flex-wrap: nowrap; flex: 1; }
.kb .oct { display: flex; gap: 0; position: relative; height: 122px; flex: 1; }
.pkey { position: relative; flex: 1; min-width: 0; }
.key { width: 100%; height: 104px; background: var(--key); border: 1px solid var(--key-line); border-right-width: 0; border-radius: 0 0 0 0; cursor: pointer; position: relative; box-sizing: border-box; }
.pkey:last-child .key:not(.black) { border-right-width: 1px; border-radius: 0 0 5px 0; }
.pkey:first-child .key:not(.black) { border-radius: 0 0 0 5px; }
.key.black { position: absolute; left: 100%; top: 0; width: 58%; min-width: 0; max-width: 28px; height: 64px; background: var(--ink); border: 1px solid var(--ink); border-radius: 0 0 3px 3px; margin: 0; transform: translateX(-50%); z-index: 2; }
.key:hover { filter: brightness(1.05); }
.key:not([data-note]) { cursor: default; }
.key .n { position: absolute; bottom: 3px; left: 0; right: 0; text-align: center; font-size: 9px; color: var(--key-label); }
.key.black .n { color: #ddd; bottom: 2px; }
.legend { display: flex; gap: 14px; font-size: 12px; color: var(--muted); flex-wrap: wrap; }
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 4px; vertical-align: -1px; }
.sheet { display: flex; flex-wrap: wrap; gap: 18px 28px; align-items: stretch; }
/* Fixed width, no flex-grow: grow distributes free space per flex line, so a
   short last row grows its fewer cards wider than the full rows above (up to
   the old 280px cap) and the columns stop lining up. A fixed width keeps every
   card identical on every row; scroll/live/print set their own sizes. */
.card {
  flex: 0 0 auto; width: 240px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  overflow: hidden; page-break-inside: avoid; break-inside: avoid;
}
.card.head { width: 100%; background: transparent; border: 0; padding: 4px 2px 2px; }
.card img { display: block; width: 100%; height: auto; background: #fff; }
.card .meta { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px 16px; font-size: 13px; }
.card.live .meta { padding: 16px 20px 18px; }
.card .nm { font-weight: 800; font-size: 22px; letter-spacing: .01em; line-height: 1.25; }
.card .dur { color: var(--ink); font-size: 28px; line-height: 1; font-weight: 500; min-width: 1.3em; text-align: right; position: relative; top: 0.14em; font-family: "Noto Music", "Noto Sans Symbols 2", "Segoe UI Symbol", serif; }
.card.ch1 .meta { background: var(--ch1); }
.card.ch2 .meta { background: var(--ch2); }
.card.ch3 .meta { background: var(--ch3); }
.card.ch1 .meta,
.card.ch2 .meta,
.card.ch3 .meta {
  color: #fff;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.card.ch1 .meta .nm,
.card.ch2 .meta .nm,
.card.ch3 .meta .nm,
.card.ch1 .meta .dur,
.card.ch2 .meta .dur,
.card.ch3 .meta .dur {
  color: #fff;
}
.badge { font-size: 10px; font-weight: 700; letter-spacing: .06em; padding: 2px 6px; border-radius: 99px; color: #fff; }
.tab-bar {
  flex: 0 0 0; width: 0; position: relative; align-self: stretch;
  /* Bars ride the column gap: the left negative margin cancels the flex gap
     BEFORE the zero-width line, so a bar costs exactly the one gap it sits
     in — rows keep the card rhythm (no extra width, no early wrap) and the
     line paints centered in that gap. */
  margin: 0 0 0 -28px;
  background: none; overflow: visible;
}
.tab-bar::before {
  content: ""; position: absolute; top: 10px; bottom: 10px; width: 3px;
  left: 12.5px; /* mid-gap (the bar rides the 28px column gap) */
  background: var(--ink); border-radius: 2px;
}
.tab-bar + .tab-bar::before { left: 18px; width: 2px; }
/* A bar line carrying a hover description gets an accent tint + a dotted cue. */
.tab-bar.has-note::before { background: var(--accent, #b5651d); }
.tab-bar.has-note::after {
  content: ""; position: absolute; top: 2px; left: -16px; width: 5px; height: 5px;
  border-radius: 50%; background: var(--accent, #b5651d); cursor: help;
}
.tok.bar.has-note {
  border-bottom: 2px dotted var(--accent, #b5651d); cursor: help;
}
.tab-tempo {
  flex: none; align-self: center; margin: 0 6px; padding: 3px 8px;
  background: var(--token-neutral); color: var(--ink); border-radius: 6px;
  font-weight: 700; font-size: 13px; white-space: nowrap;
}
.tok.tempo {
  background: var(--token-neutral); color: var(--ink); font-weight: 700;
  height: 44px; width: auto; flex: none; min-height: 44px;
  display: inline-flex; align-items: center;
}
.card.rest {
  border-style: dashed;
}
.card.rest .compact {
  min-height: 0; display: flex; align-items: center; justify-content: center;
  background: var(--oca-bg); color: var(--rest-ink); font-size: 22px; font-weight: 800; line-height: 1;
}
.card.tie .compact {
  font-size: 48px; font-weight: 500; color: var(--ink); letter-spacing: .04em; line-height: 1;
}
.tok.tie { opacity: .78; }
.tok.slide { position: relative; }
.tok .slide-mark { font-weight: 800; margin-right: 2px; opacity: .85; }
.stac-mark { font-weight: 900; margin-left: 1px; opacity: .9; }
.tok .stac-mark { vertical-align: super; font-size: .8em; }
.tok.staccato { font-style: italic; }
.meta .nm .stac-mark {
  display: inline-block; width: 0; overflow: visible; line-height: 0;
  position: relative; top: -0.5em; font-size: .7em;
}
.sheet.scroll .card.rest { flex: 0 0 260px; width: 260px; }
.compact { display: block; line-height: 0; background: var(--oca-bg); aspect-ratio: 1; }
.official { display: none; }
svg.ocarina { width: 100%; height: auto; display: block; background: var(--oca-bg); }
.card { min-height: 160px; }
.hole { fill: rgba(255,255,255,0.18); stroke: #5a3a28; stroke-width: 3; }
.hole.on { fill: rgba(20,10,6,0.78); stroke: #1a0c08; }
.hole.tune { fill: none; stroke: #b89a80; stroke-width: 2; stroke-dasharray: 4 3; }
/* The piano head mirrors the other two: the centered sub-section title. */
#pianoBlock > .box-head { justify-content: center; }
kbd { font: 11px ui-monospace, monospace; background: var(--paper-2); border: 1px solid var(--line); border-bottom-width: 2px; padding: 0 4px; border-radius: 3px; }
@media print {
  header.app, .noprint, .tab-tools, #stats, .focus-exit { display: none !important; }
  html[data-theme="oot"], html[data-theme="oot"] body,
  body { background: #fff !important; background-image: none !important; }
  html[data-theme="oot"] body::before { display: none !important; }
  .wrap { max-width: none; padding: 0; }
  .panel { border: 0; padding: 0; margin: 0; }
  .sheet { gap: 2.5mm 3mm; }
  .tab-bar { margin: 0 0 0 -3mm; }
  .tab-bar::before { left: 5px; }
  /* Same centered-block sizing with the print card slot (32mm card + 3mm
     margin); #sheet outranks the base rule that lands later in the file. */
  #sheet:not(.scroll):not(.live) {
    width: 100%; /* plain fallback: engines without round() drop the next line */
    width: max(calc(round(down, 100%, 35mm) - 3mm), min(100%, 32mm));
    margin-inline: auto;
  }
  .card, .rest,
  .sheet.scroll .card,
  .sheet.scroll .card.rest,
  .sheet.live .card.live {
    flex: 0 0 32mm;
    width: 32mm;
    min-width: 32mm;
    max-width: 32mm;
    min-height: 0;
  }
  .card .meta { padding: 1.5mm 2mm 1.8mm; }
  .card .nm { font-size: 11px; }
  .card .dur { font-size: 13px; top: 0.08em; }
  .compact, svg.ocarina { background: #fff !important; }
  /* Clay body, rim, gold deco, and relief lines eat ink and hide the holes.
     Near-white fill + a thin outline keeps the silhouette without a wash of color. */
  svg.ocarina .oca-body { fill: #f7f4ef !important; stroke: #222 !important; }
  svg.ocarina .oca-rim { fill: #f3efe8 !important; stroke: #444 !important; }
  svg.ocarina .oca-deco,
  svg.ocarina .oca-deco path { fill: #eee9dc !important; stroke: #555 !important; }
  svg.ocarina .oca-relief { stroke: #ccc !important; opacity: 0.35 !important; }
}

.click-help { margin: 0 0 0 10px; font-size: 12px; color: var(--muted); line-height: 1.35; }
.tokens { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 4px 10px 6px; min-height: 36px; }
.tok {
  display: inline-flex; align-items: center; gap: 8px;
  height: 44px; padding: 0 14px; border-radius: 10px; color: #fff;
  font-weight: 700; font-size: 18px; line-height: 1; white-space: nowrap;
  cursor: default; user-select: none;
  /* hold-to-listen target: a planted finger must not raise text selection
     or the iOS-style callout mid-dwell */
  -webkit-user-select: none; -webkit-touch-callout: none;
  box-shadow: 0 1px 0 rgba(0,0,0,.12);
}
.tok .td { font-size: 24px; line-height: 1; font-weight: 500; font-family: "Noto Music","Noto Sans Symbols 2","Segoe UI Symbol",serif; opacity: .95; }
.tok.ch1 { background: var(--ch1); }
.tok.ch2 { background: var(--ch2); }
.tok.ch3 { background: var(--ch3); }
.tok.bar { background: var(--token-neutral); color: var(--ink); font-weight: 600; height: 44px; width: auto; flex: none; min-height: 44px; }
.tok.pause { background: var(--token-pause); }
/* Junk spans the grammar cannot consume: a token-shaped pill (tan, like the
   bar chips — same var in both themes) carrying the raw junk text with a
   spell-check style wavy scribble. The first take used a bare transparent
   chip (not readable/especially OoT) and an earlier one an opaque accent box
   (illegible on the grass): pill + scribble reads like a real token. */
.tok.bad {
  background: var(--token-neutral);
  color: var(--ink);
  text-decoration-line: underline;
  text-decoration-style: wavy;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  text-decoration-skip-ink: none;
  text-decoration-color: var(--accent);
}
.tok.oor {
  background: #fff; color: var(--accent);
  border: 1.5px solid var(--accent);
  box-shadow: 0 0 0 2px rgba(139,61,47,.18);
  font-weight: 700; opacity: 1;
}
.tok.oor.tie {
  box-shadow: none; border-style: dashed; font-weight: 600; opacity: 1;
}
.tok:hover { filter: brightness(1.08); }

/* Section header rows in the playback token strip: one named bar in the band
   starts a section — its name gets a full-width row before the section's
   tokens. */
.sec-head {
  flex: 1 0 100%;
  margin: 7px 0 0; padding: 3px 2px 0;
  font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--clay-dark);
  border-top: 1px dashed var(--line);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sec-head:first-child { border-top: 0; margin-top: 0; }
/* Same header row on the card sheet (grid view + the print/download sheet):
   one step larger than the strip's, and printing must not strand a header
   alone at a page bottom with its section's cards on the next page. */
.sheet .sec-head {
  font-size: 12px;
  padding-left: 0; /* flush with the (centered) grid's left edge */
  page-break-after: avoid; break-after: avoid;
}

.playback {
  margin-top: 12px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel);
}
.playback-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 8px 12px; border-bottom: 1px solid var(--line);
  background: var(--head);
}
.playback .tokens { padding: 10px 12px 12px; min-height: 48px; }

#tempoVal { display: inline-block; min-width: 3ch; text-align: right; font-variant-numeric: tabular-nums; }

.block {
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel); margin: 0 0 12px;
}
.box-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 8px 12px; border-bottom: 1px solid var(--line);
  background: var(--head);
}
.box-title { font-size: 11px; font-weight: 700; letter-spacing: .08em; color: var(--clay-dark); }
.box-sub { font-size: 12px; color: var(--muted); }
.tempo-lab {
  margin-left: auto;
  font-size: 13px; font-weight: 600; color: var(--ink);
  display: flex; flex-direction: column; gap: 4px;
}
/* Each dial is its own 3-column line: [title | slider | value]. Every row
   shares the same tracks, so the two sliders command exactly over each
   other (the title column is fixed too, so the slider edges cannot drift). */
.dial-row {
  display: grid;
  grid-template-columns: 48px 90px max-content;
  align-items: center; gap: 8px;
}
.dial-row > span:first-child { justify-self: end; }
.tempo-lab input[type=range] { width: 90px; }
#inputBlock textarea#src {
  width: 100%; min-height: 74px; resize: vertical; border: 0;
  font: 15px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  padding: 10px 12px; background: var(--paper);
  /* straight rectangle inside the block — sub-section edges carry the rounding */
  border-radius: 0;
}

.tok.now {
  box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--ink);
  filter: brightness(1.1);
}

#pianoBlock .kb { display: flex; width: 100%; padding: 10px 12px 6px; box-sizing: border-box; }
#pianoBlock .click-help { padding: 0 12px 10px; margin: 0; color: var(--muted); font-size: 12px; }

#inputBlock .input-legend { margin: 0; padding: 6px 12px 8px; font-size: 12px; color: var(--muted); border-top: 1px solid var(--line); background: var(--head); border-radius: 0 0 8px 8px; }

.box-head select, #scale {
  font: 13px/1.3 inherit; padding: 4px 8px; border-radius: 6px;
  border: 1px solid var(--line); background: var(--panel) !important; color: var(--ink) !important;
}
.box-head select option, #scale option, #scale optgroup {
  background: var(--panel); color: var(--ink);
}

.loop-lab {
  display: flex; align-items: center; gap: 6px; cursor: pointer; margin: 0;
}
.loop-lab input { margin: 0; }
/* Library file actions pair into two-button stacks: each stack is a grid so
   both buttons stretch to the same column width — labels align perfectly. */
.lib-pair {
  display: grid; gap: 4px; align-self: start;
}
.lib-pair > button { width: 100%; }
/* The playback tick button wears the toggle vocabulary when engaged. */
#tickBtn.on {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
/* Removing from the library destroys data: the destructive accent clothes
   it (OoT wears the theme's bright salmon). */
#libRemove {
  color: var(--accent); border-color: var(--accent);
}
#libRemove:hover { background: rgba(139, 61, 47, 0.08); }
html[data-theme="oot"] #libRemove {
  color: #ff9d80; border-color: rgba(255, 157, 128, 0.5);
}
/* Clear empties the input field itself — meaningless while the field is
   folded away, so it follows the block's collapsed state. */
#inputBlock.collapsed .box-head #clear { display: none; }
/* Tool rows under the song title: Zen mode alone on its centered line, the
   plain buttons row BELOW it. The buttons row rides the same centered-block
   width math as the card grid (equal 268px slots minus one gap), so its
   left edge sits over the left cards and its right edge over the right
   cards; on screens narrower than the row it clamps to the panel and wraps. */
.zen-row { display: flex; justify-content: center; margin: 0 0 10px; }
.tab-tools {
  margin: 0 0 10px;
  width: max(calc(round(down, 100%, 268px) - 28px), 640px);
  max-width: 100%;
  margin-inline: auto;
  /* The display-mode segment rides the exact centerline of the row (whose
     ends already line up with the card grid); view toggles lead on the left,
     export actions trail on the right. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 8px; flex-wrap: wrap;
}
.tab-tools .tools-left,
.tab-tools .tools-right {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center; min-width: 0;
}
.tab-tools .tools-right { justify-content: flex-end; }
/* Symbol-only action buttons: same ghost outline, one crisp stroke glyph. */
.icon-btn {
  width: 40px; height: 34px; padding: 0; border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center;
}
.icon-btn > svg {
  width: 18px; height: 18px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
/* Fullscreen as the window-corner control of the tablature section. */
#tabPanel { position: relative; }
.fs-corner {
  position: absolute; top: 10px; right: 12px; z-index: 50;
}
@media (max-width: 1000px) {
  /* Narrow: release the exact centerline, wrap the zones as blocks. */
  .tab-tools { display: flex; }
  .tab-tools .mode-seg { grid-column: auto; grid-row: auto; margin-inline: auto; }
}

/* Display-mode segmented control */
.mode-seg {
  display: inline-flex; border: 1px solid var(--line); border-radius: 8px;
  overflow: hidden; background: var(--paper);
}
.seg-btn {
  background: transparent; color: var(--ink); border: 0; border-radius: 0;
  padding: 7px 14px; font: 600 13px/1 inherit; cursor: pointer;
  border-left: 1px solid var(--line);
}
.seg-btn:first-child { border-left: 0; }
.seg-btn:hover { background: var(--paper-2); }
.seg-btn.on { background: var(--ink); color: var(--paper); }

/* Zen mode call-to-action — the marquee action: one step larger than the
   surrounding buttons, crisp inner highlight. */
.zen-btn {
  background: var(--accent); color: #fff; border: 0;
  border-radius: 8px; padding: 10px 22px;
  font: 700 14.5px/1 inherit; cursor: pointer;
  box-shadow: 0 1px 0 rgba(0,0,0,.15), inset 0 0 0 1px rgba(255,255,255,.25);
}
/* Zen button hover: ONE white hit-pulse rolls outward, like striking a tone
   in Zen (whose halo is also a blurred, true circle on a square element).
   Built as a real element (.zen-wave, spawned by spawnZenWave in ui.js), not
   a pseudo: it must paint ABOVE the input/play/piano panel, which the OOT
   theme stacks at z 2 while the tab panel sits at z 1 — nothing inside the
   tab panel can ever out-paint that. Real fullscreen paints only the
   fullscreen element's subtree, so the wave remounts inside #tabPanel there.
   One pulse per hover entry; it keeps going through unhover and is stopped
   only when Zen opens. */
.zen-wave {
  position: fixed; z-index: 99999;
  width: 140px; height: 140px;        /* square = perfect circle */
  margin-left: -70px; margin-top: -70px;
  border-radius: 50%;
  background: radial-gradient(circle closest-side at 50% 50%,
    rgba(255,255,255,0) 52%,
    rgba(255,255,255,.55) 70%,
    rgba(255,255,255,0) 94%);
  filter: blur(7px);
  box-shadow: 0 0 14px rgba(28, 20, 15, .12); /* visibility on white panels */
  pointer-events: none;
  opacity: 0;
  animation: zen-wave 1s ease-out forwards;
}
@keyframes zen-wave {
  0%   { opacity: 0;   transform: scale(.5); }
  22%  { opacity: .85; }
  100% { opacity: 0;   transform: scale(2.8); }
}
.zen-btn:active { filter: brightness(.92); }

.check-lab {
  margin: 0; display: flex; align-items: center; gap: 6px;
  text-transform: none; letter-spacing: 0; font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.check-lab input { margin: 0; }
/* The song title IS the fingering-charts section title: it leads the whole
   panel above the tool row, wearing the sub-section vocabulary one step up
   in size. */
h2#title {
  font-size: 22px; margin: 0 0 10px; text-align: center;
}

/* Library cluster in the input head: the Song Library title sits on top, the
   song dropdown below it — centered over the whole header exactly like the
   playback head's center column (same 1fr-auto-1fr recipe below). */
.lib-dd {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
}
#inputBlock > .box-head {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 12px;
}
#inputBlock > .box-head .lib-dd { justify-self: center; }
.lib-pairs {
  display: flex; gap: 10px; justify-self: end;
}
@media (max-width: 760px) {
  #inputBlock > .box-head { display: flex; flex-wrap: wrap; }
  .lib-dd { margin-inline: auto; }
  .lib-pairs { margin-inline: auto; }
}
.lib-dd-wrap { position: relative; }
.lib-dd-btn {
  background: var(--panel) !important; color: var(--ink) !important;
  border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px;
  font: 13px/1.3 inherit; display: inline-flex; align-items: center; gap: 10px;
  /* Roomy: the open menu spans the button exactly, so the drop-down reads
     as centered under the selected song. */
  min-width: 20em; position: relative;
}
/* The selected song centers in its own track; the caret anchors the right
   edge without shifting the text off center. */
#libDdText { flex: 1; text-align: center; }
#libDdText.placeholder { color: var(--muted); font-weight: 400; }
.lib-dd-caret { position: absolute; right: 8px; font-size: 10px; opacity: .65; }
.lib-dd-menu {
  position: absolute; top: calc(100% + 4px); right: 0; left: 0; z-index: 40;
  min-width: max(100%, 13rem); max-width: min(18rem, calc(100vw - 24px));
  max-height: 280px; overflow: auto;
  background: var(--panel); color: var(--ink); border: 1px solid var(--line);
  border-radius: 8px; padding: 4px; box-shadow: 0 10px 28px rgba(28,20,15,.18);
}
.lib-dd-group {
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--clay-dark); padding: 8px 10px 3px;
}
.lib-dd-opt {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: transparent;
  color: var(--ink); border: 0; border-radius: 6px; padding: 7px 12px 7px 28px;
  font: 13px/1.3 inherit;
}
.lib-dd-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-dd-oor {
  flex: 0 0 auto; font-size: 11px; font-weight: 700; line-height: 1;
  color: #fff; background: var(--accent); border-radius: 99px; padding: 3px 7px;
}
.lib-dd-opt.has-oor .lib-dd-name { color: var(--accent); }
.lib-dd-opt:hover, .lib-dd-opt:focus-visible { background: var(--paper-2); }
.lib-dd-opt[aria-selected="true"] { font-weight: 700; background: var(--paper); }
.lib-dd-foot {
  border-top: 1px solid var(--line); margin-top: 6px; padding: 8px 12px 4px;
  font: 12px/1.3 inherit; color: var(--clay-dark);
}
.lib-dd-foot label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.lib-dd-foot input { margin: 0; accent-color: var(--accent); cursor: pointer; }

.pkey .ch-line { height: 6px; margin-top: 3px; background: var(--ch-line-empty); border-radius: 1px; }
.pkey .ch-line.c1 { background: var(--ch1); }
.pkey .ch-line.c2 { background: var(--ch2); }
.pkey .ch-line.c3 { background: var(--ch3); }
.click-help { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.ch-leg { display: inline-flex; align-items: center; gap: 6px; }
.ch-leg i { display: inline-block; width: 16px; height: 6px; border-radius: 1px; }
.ch-leg i.c1 { background: var(--ch1); }
.ch-leg i.c2 { background: var(--ch2); }
.ch-leg i.c3 { background: var(--ch3); }

.sheet-wrap { width: 100%; }
.sheet { display: flex; flex-wrap: wrap; gap: 18px 28px; align-items: stretch; }
/* Center the card grid as ONE aligned block: the sheet is sized down to a
   whole number of card slots (fixed 240px card + 28px trailing margin =
   268px per card), so full rows fill it exactly, every row keeps the same
   left edge, and .sec-head names align with the grid's left side. The max()
   guard falls back to full width when less than one slot fits (tiny phones),
   where round(down, ...) would floor to 0. The plain width above is the
   fallback for engines without round() (they drop the second width line):
   the old full-width left-aligned block, kept implicit-proof even if other
   rules ever set a width on this same selector. */
.sheet:not(.scroll):not(.live) {
  width: 100%; /* round()-less fallback */
  width: max(calc(round(down, 100%, 268px) - 28px), min(100%, 240px));
  margin-inline: auto;
}
.sheet.live { justify-content: center; gap: 0; }
.sheet.live .card.live {
  flex: 0 1 420px; width: min(420px, 100%); min-width: 220px; max-width: 420px;
  margin: 0;
}
.card.live .compact { position: relative; line-height: 1; }
.card.live.is-rest .live-oca { opacity: .5; }
.card.live .rest-over {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 42px; font-weight: 800; letter-spacing: .04em; color: var(--rest-ink); line-height: 1;
  pointer-events: none; text-shadow: 0 0 12px var(--oca-bg);
}
.card.rest .nm,
.card.live.is-rest .nm {
  color: var(--rest-ink);
}
/* Scroll band. 30px of top padding is the flip-up zone where section names
   ride (see .sheet.scroll .tab-bar.has-note::after): the horizontally
   scrolling sheet-wrap computes overflow-y to auto, so anything above the
   sheet box would clip — the names must live INSIDE the sheet's own box. */
.sheet.scroll { flex-wrap: nowrap; overflow: visible; padding: 30px 16px 12px; }
.sheet-wrap:has(.scroll) {
  overflow-x: auto; padding: 8px 0;
  /* Center (don't stretch) the sheet inside the fullscreen flex wrap too —
     kept here because the ID-level fullscreen rule would outrank any plain
     class rule; the real fullscreen case is also declared below. */
  align-items: center;
}
.sheet.scroll .card { flex: 0 0 260px; width: 260px; }
/* Scroll-band bars span exactly the card's vertical extent and rise together
   through the flip-up zone: EVERY bar line starts at the flip-up zone's inner
   edge (-25px, one shared line top, 5px below the sheet edge), matching the
   named bars — plain bars read identically tall to titled ones. A named bar
   (has-note) additionally paints its section title at that same height: the
   ::after dot, repurposed via content: attr(title), LEFT-ALIGNED WITH the
   first card after the bar — the zero-width bar adjoins that card's left
   edge, so left: 0 is the card edge — with equal 5px breathing room above
   and below the label in the 30px flip-up zone. */
.sheet.scroll .tab-bar::before { top: -25px; bottom: 0; }
.sheet.scroll .tab-bar.has-note::after {
  content: attr(title);
  /* The zero-width bar rides one 28px column-gap LEFT of the card it leads
     (see .tab-bar's negative margin) — re-align the title with the card's
     left edge. */
  position: absolute; left: 28px; top: -25px;
  width: auto; height: auto; margin: 0; padding: 0 12px 0 0;
  border-radius: 0; background: none;
  font-size: 12px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap; line-height: 20px;
  color: var(--clay-dark);
}
.card.now { box-shadow: 0 0 0 3px var(--ink); }
.key.now { box-shadow: inset 0 0 0 3px var(--key-now-ring); background: var(--key-now); }

/* Fullscreen tablature */
#tabPanel:fullscreen,
#tabPanel:-webkit-full-screen,
body.zen-fallback #tabPanel:not(.focus) {
  background: var(--paper); border-radius: 0; margin: 0;
  border: 0; box-shadow: none;
  padding: 20px 28px; overflow: auto;
  display: flex; flex-direction: column;
}
/* ?oot: the paper fill above is a light cream, but the theme's type and
   chrome are white-on-dark — give fullscreen the same dark translucent glass
   as a normal panel so white text and cream cards keep their contrast
   (the blurred Hyrule field photo faintly shows through, as on page). */
html[data-theme="oot"] #tabPanel:fullscreen:not(.focus),
html[data-theme="oot"] #tabPanel:-webkit-full-screen:not(.focus),
html[data-theme="oot"] body.zen-fallback #tabPanel:not(.focus) {
  background: var(--panel);
}
/* CSS fallback for devices without the Fullscreen API (e.g. iPhone Safari) */
body.zen-fallback { overflow: hidden; }
/* The sticky app header stacks at z 5 at the root level, and in the oot
   theme .wrap gets its own stacking context (z 1, to clear the background
   image) — the fixed z-9999 panel *inside* it can never rise above the
   header, so the app bar would float over the zen view. Nothing but the tab
   panel belongs on screen in the fallback: drop the chrome. */
body.zen-fallback header.app { display: none; }
body.zen-fallback #tabPanel {
  position: fixed; inset: 0; z-index: 9999;
  width: 100vw; height: 100vh; height: 100dvh;
}
#tabPanel:fullscreen .sheet-wrap,
body.zen-fallback #tabPanel .sheet-wrap { flex: 1 1 auto; display: flex; min-height: 0; }
/* In fullscreen the wrap is a flex row: center (don't stretch) the scroll
   sheet, or the sheet fills the panel and the cards float centered inside a
   line that is taller than they are — then bar lines can never meet the
   cards' top/bottom edge. (Normal view's wrap is a block, no stretch.) */
#tabPanel:fullscreen .sheet-wrap:has(.scroll),
body.zen-fallback #tabPanel .sheet-wrap:has(.scroll) {
  align-items: center;
}
#tabPanel:fullscreen .sheet,
body.zen-fallback #tabPanel .sheet { align-content: flex-start; width: 100%; }
/* ...but the plain grid keeps its centered-block sizing in fullscreen too
   (the 100% width above would otherwise force rows back to left-aligned). */
#tabPanel:fullscreen .sheet:not(.scroll):not(.live),
body.zen-fallback #tabPanel .sheet:not(.scroll):not(.live) {
  width: 100%; /* round()-less fallback */
  width: max(calc(round(down, 100%, 268px) - 28px), min(100%, 240px));
  margin-inline: auto;
}
#tabPanel:fullscreen .sheet.scroll,
body.zen-fallback #tabPanel .sheet.scroll { align-items: center; }

/* One-ocarina fills the screen */
#tabPanel:fullscreen .sheet.live,
body.zen-fallback #tabPanel .sheet.live {
  flex: 1 1 auto; align-items: center; justify-content: center; width: 100%;
}
#tabPanel:fullscreen .sheet.live .card.live,
body.zen-fallback #tabPanel .sheet.live .card.live {
  flex: 0 0 auto;
  width: min(90vmin, 900px); min-width: 0; max-width: min(90vmin, 900px);
}
#tabPanel:fullscreen .sheet.live .card.live .meta,
body.zen-fallback #tabPanel .sheet.live .card.live .meta { padding: 18px 24px 22px; }
#tabPanel:fullscreen .sheet.live .card.live .nm,
body.zen-fallback #tabPanel .sheet.live .card.live .nm { font-size: 44px; }
#tabPanel:fullscreen .sheet.live .card.live .dur,
body.zen-fallback #tabPanel .sheet.live .card.live .dur { font-size: 52px; }
#tabPanel:fullscreen .sheet.live .card.live .badge,
body.zen-fallback #tabPanel .sheet.live .card.live .badge { font-size: 20px; padding: 6px 18px; }
#tabPanel:fullscreen .sheet.live .card.live .rest-over,
body.zen-fallback #tabPanel .sheet.live .card.live .rest-over { font-size: 88px; }
.key.black.now { background: var(--key-now-black); box-shadow: inset 0 0 0 3px var(--key-now-black-ring); }

/* Focus mode (fullscreen + one-ocarina): distraction-free practice */
.focus-strip, .focus-bar { display: none; }

#tabPanel.focus {
  padding: 2.5vh 3vw; align-items: center; gap: 0;
  background: var(--zen); color: var(--paper);
  overflow: hidden; position: relative;
  display: flex; flex-direction: column; border: 0; border-radius: 0; margin: 0;
}
/* Mellow ambient glow that breathes with each played note. Hue is set from
   the note's pitch (--glow-hue). Brightness is driven by `opacity` and reach
   by `transform: scale()` — real animatable properties, so the swell actually
   interpolates (transitioning a var() inside `background` would not).
   The light is a HALO that is transparent behind the centered ocarina card and
   brightens toward the screen edges, so it's never hidden by the card and reads
   well on any aspect ratio (pools in whatever margins the screen has). */
#tabPanel.focus::before {
  content: "";
  position: absolute;
  /* A SQUARE element keeps the radial halo a true circle on any aspect ratio;
     centered on the fingering card (--glow-x/--glow-y, set from JS). Sized in
     vmin (the SMALLER viewport dimension) so on wide screens the ring stays
     close to the card instead of pushing far out into the side margins. */
  width: 120vmin; height: 120vmin;
  left: var(--glow-x, 50%); top: var(--glow-y, 52%);
  margin-left: -60vmin; margin-top: -60vmin;
  pointer-events: none; z-index: 0;
  background: radial-gradient(circle at 50% 50%,
    transparent 0%,
    transparent 24%,
    hsla(var(--glow-hue, 30), var(--glow-sat, 70%), var(--glow-light, 50%), 0.55) 42%,
    hsla(var(--glow-hue, 30), var(--glow-sat, 72%), calc(var(--glow-light, 45%) - 5%), 0.3) 58%,
    transparent 72%);
  filter: blur(55px);
  opacity: var(--glow-alpha, 0);
  transform: scale(var(--glow-scale, 0.5));
  transform-origin: 50% 50%;
  transition: opacity var(--glow-fade, 1.6s) ease-out,
              transform var(--glow-fade, 1.6s) ease-out;
  will-change: opacity, transform;
}
#tabPanel.focus > * { position: relative; z-index: 1; }

/* Small screens: the piano must squeeze inside the viewport instead of
   pushing keys past it (28 white keys + flexible flex widths), and shrink its
   label/height a touch. */
@media (max-width: 760px) {
  .kb .oct { height: 92px; }
  .key { height: 76px; }
  .key.black { height: 48px; }
  .key .n { font-size: 7px; }
  .pkey .ch-line { height: 4px; margin-top: 2px; }
  #pianoBlock .kb { padding: 8px 8px 4px; }
}

#tabPanel.focus .tab-tools,
#tabPanel.focus .zen-row,
#tabPanel.focus .fs-corner,
#tabPanel.focus #stats { display: none !important; }

#tabPanel.focus h2#title {
  flex: 0 0 auto; margin: 0 0 1.5vh; text-align: center;
  font-size: clamp(22px, 4.2vh, 44px); font-weight: 700;
  color: var(--paper);
}
#tabPanel.focus .sheet-wrap { flex: 1 1 auto; align-items: center; min-height: 0; overflow: hidden; }
#tabPanel.focus .sheet.live { height: 100%; overflow: hidden; }
#tabPanel.focus .sheet.live .card.live {
  display: flex; flex-direction: column;
  width: min(72vmin, 760px); max-width: 100%;
  height: 100%; max-height: 100%;
  box-shadow: 0 10px 40px rgba(0,0,0,.55);
}
#tabPanel.focus .sheet.live .card.live .compact {
  flex: 1 1 auto; min-height: 0; aspect-ratio: auto;
}
#tabPanel.focus .sheet.live .card.live .live-oca { width: 100%; height: 100%; }
#tabPanel.focus .sheet.live .card.live svg.ocarina { width: 100%; height: 100%; }
#tabPanel.focus .sheet.live .card.live .meta { flex: 0 0 auto; }
#tabPanel.focus .card.live { color: var(--ink); }

/* Single-line scrub strip */
#tabPanel.focus .focus-strip {
  display: block; flex: 0 0 auto; width: 100%;
  margin: 1.2vh 0 0; padding: 6px 0;
  border-top: 1px solid rgba(255,255,255,.12);
}
#tabPanel.focus .focus-tokens {
  display: flex; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; gap: 6px;
  padding: 20px 0; align-items: center;
  scrollbar-width: none; -ms-overflow-style: none;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
}
#tabPanel.focus .focus-tokens::-webkit-scrollbar { display: none; }
#tabPanel.focus .focus-tokens::before,
#tabPanel.focus .focus-tokens::after { content: ""; flex: 0 0 50%; }
#tabPanel.focus .focus-tokens .tok { flex: 0 0 auto; transition: transform .12s ease; }
#tabPanel.focus .focus-tokens .tok.now {
  transform: scale(1.5); z-index: 1;
  box-shadow: 0 0 0 2px var(--zen), 0 0 0 4px #fff, 0 4px 16px rgba(0,0,0,.5);
}

/* Transport bar */
#tabPanel.focus .focus-bar {
  display: flex; flex: 0 0 auto; flex-direction: column; align-items: center;
  gap: 1.4vh; padding: 1.5vh 0 calc(0.5vh + env(safe-area-inset-bottom));
  width: 100%; flex-shrink: 0;
}
#tabPanel.focus .focus-transport {
  display: flex; align-items: center; justify-content: center; gap: 22px;
}
#tabPanel.focus .focus-settings {
  display: flex; align-items: center; justify-content: center; gap: 18px; flex-wrap: wrap;
}
.focus-btn {
  width: 64px; height: 64px; border-radius: 50%;
  background: var(--ink); color: var(--paper); border: 0;
  font-size: 26px; line-height: 1; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
#tabPanel.focus .focus-btn { background: var(--zen-btn); color: var(--paper); }
.focus-btn:hover { filter: brightness(1.12); }
/* Play button: dark when idle/paused, green while running (see the
   symmetric practice rules below). Base size matches practice in zen. */
.focus-btn.play {
  width: 92px; height: 92px; font-size: 40px; background: var(--zen-btn);
}
#tabPanel.focus .focus-btn.play { background: var(--zen-btn); }
#tabPanel.focus .focus-btn.play.is-playing { background: var(--good); }
/* Transport glyphs are inline SVG in currentColor: the ▶/⏸/⏹ characters are
   emoji-default and phones painted them in the color emoji font (orange),
   ignoring the button color. Owning the shapes means exact geometry and
   centering: play idle → triangle (optically nudged right); running → the
   pause bars (the .is-playing class flips which svg shows). */
.focus-btn > svg { display: none; width: .85em; height: .85em; fill: currentColor; }
#focusPlay:not(.is-playing) svg.g-play { display: block; transform: translateX(-.03em); }
#focusPlay.is-playing svg.g-pause { display: block; }
#focusStop svg.g-stop { display: block; }

/* Loop toggle in the transport row */
#tabPanel.focus .focus-btn.loop {
  font-size: 31px;
  background: rgba(255,255,255,.14); color: var(--paper);
  border: 1px solid rgba(255,255,255,.5);
}
/* practice glyph: ~half the play button's icon width, so the mode symbols
   read alike */
#tabPanel.focus .focus-btn#practiceFocusBtn { font-size: 50px; }
.focus-btn.loop.on { background: var(--good); color: var(--paper); border-color: var(--good); }
#tabPanel.focus .focus-btn.loop.on { background: var(--good); color: #fff; border-color: var(--good); }

/* Normal-mode mirror of the zen transport: same four controls in the same
   order and with the same STATE colors (running = --good green, engaged
   practice = --ch2, loop on = --good). The resting look is the inactive
   vocabulary (ghost outline) — black stays reserved for "activated" — and
   the row floats centered over the WHOLE header (static flow fallback on
   tight screens). */
/* Sub-section titles that live inside a header row (Song Library in the
   input head, Playback Control over the transport rounds): important but
   compact, with a short accent rule under the words — the shared look both
   headers wear. */
.head-label {
  font-size: 13px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--clay-dark); line-height: 1.2;
}
.head-label::after {
  content: ""; display: block; height: 2px; width: 2.4em;
  margin: 4px auto 0; background: var(--accent); border-radius: 1px;
}

#playback > .box-head { position: relative; }
/* Playback Control + its round buttons sit in the CENTER column of a
   1fr-auto-1fr grid: both side columns are equal, so the bundle's midline
   really is the header's midline — while the header itself grows to contain
   it (no more poking past the section edges like an absolute float did). */
#playback > .box-head {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 12px;
}
.head-left { display: flex; align-items: center; gap: 10px; }
/* The perf button's left cluster: glyph-only Audio Performance, the playback
   tick button riding to its right. */
.head-perf-col {
  display: flex; flex-direction: row; align-items: center; gap: 10px;
}
.t-wrap {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
#playback .tempo-lab { justify-self: end; }
@media (max-width: 760px) {
  #playback > .box-head { display: flex; flex-wrap: wrap; }
  .t-wrap { margin-inline: auto; }
  #barTools { flex-wrap: wrap; }
}
#barTools {
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.t-btn {
  width: 44px; height: 44px; border-radius: 50%;
  background: transparent; color: var(--ink); border: 1px solid var(--line);
  font-size: 18px; line-height: 1; padding: 0;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.t-btn:hover { filter: brightness(1.12); }
.t-btn.play { width: 56px; height: 56px; font-size: 26px; }
.t-btn.practice { width: 56px; height: 56px; font-size: 30px; }
.t-btn.play.is-playing { background: var(--good); color: var(--paper); border-color: var(--good); }
.t-btn.practice.on { background: var(--ch2); color: var(--paper); border-color: var(--ch2); }
.t-btn.loop.on { background: var(--good); color: var(--paper); border-color: var(--good); }
.t-btn.play, .t-btn.practice, .t-btn.stop { box-shadow: 0 1px 0 rgba(0,0,0,.06); }
/* OoT theme: inactive round glyphs join the other buttons' white-ink look
   (ghost vocabulary); active states keep their own colors underneath. */
html[data-theme="oot"] .t-btn {
  color: #fff; border-color: rgba(255, 255, 255, 0.45);
}
/* SVG transport glyphs (same shapes as zen): play idle → triangle, running
   → pause bars; Stop always its square. */
.t-btn > svg { display: none; width: .85em; height: .85em; fill: currentColor; }
#mirrorPlay:not(.is-playing) svg.g-play { display: block; transform: translateX(-.03em); }
#mirrorPlay.is-playing svg.g-pause { display: block; }
#mirrorStop svg.g-stop { display: block; }

/* Exit button: fixed top-right, fades in on mouse move */
.focus-exit { display: none; }
#tabPanel.focus .focus-exit {
  display: inline-flex; align-items: center; justify-content: center;
  position: fixed; top: 18px; right: 22px; z-index: 50;
  width: 48px; height: 48px; border-radius: 50%; font-size: 22px; cursor: pointer;
  background: rgba(0,0,0,.35); color: var(--paper);
  border: 1px solid rgba(255,255,255,.28);
  opacity: 0; pointer-events: none; transition: opacity .35s ease;
}
#tabPanel.focus.ui-visible .focus-exit { opacity: 1; pointer-events: auto; }
#tabPanel.focus .focus-exit:hover { background: rgba(0,0,0,.55); }

/* Performance / headroom dropdown */
.perf-dd { position: relative; display: inline-flex; }
.perf-btn {
  width: 40px; height: 40px; border-radius: 50%; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.perf-btn .perf-ico { font-size: 19px; line-height: 1; }
.perf-hr { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--muted); }
.perf-hr { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--muted); }
.perf-pop {
  position: absolute; top: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  z-index: 70; width: 340px; max-width: 92vw;
  background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0,0,0,.22);
  padding: 10px 12px 8px;
}
.perf-tbl { width: 100%; border-collapse: collapse; }
.perf-tbl td {
  padding: 3px 4px; border-bottom: 1px dashed var(--line); color: var(--ink); font-size: 12.5px;
}
.perf-tbl td:first-child { color: var(--muted); }
.perf-tbl td:last-child {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 600;
}
.perf-tbl td.perf-ok { color: var(--good) !important; }
.perf-tbl td.perf-warn { color: #a15c00 !important; }
.perf-tbl td.perf-bad { color: var(--accent) !important; }
.perf-cap { margin: 7px 0 0; font-size: 11px; line-height: 1.5; color: var(--muted); }
/* Lite switch strip inside the meters pop: plain when idle, accent when the
   counters show trouble (the point: switch right where the problem shows). */
.perf-act {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 9px; padding-top: 8px; border-top: 1px dashed var(--line);
}
.perf-act-tip { font-size: 11px; color: var(--muted); }
/* State colors reuse the app's vocabulary: the button is a .toggle-btn, so
   ".on" gives the engaged ink fill; "issues detected" borrows the
   .range-warn accent (paper-red tint + accent border) as usual. */
.perf-act-state { font-size: 12.5px; font-weight: 400; color: var(--muted); }
.perf-act.warn #perfActLite {
  background: var(--warn-bg); border-color: var(--accent); color: var(--warn-ink);
}

/* Zen: the perf button sits fixed top-left, mirroring the ✕ in the top-right */
#tabPanel.focus .perf-dd { position: static; }
#tabPanel.focus .perf-btn {
  display: inline-flex; align-items: center; justify-content: center;
  position: fixed; top: 18px; left: 22px; z-index: 50;
  width: 48px; height: 48px; border-radius: 50%; font-size: 22px; cursor: pointer;
  background: rgba(0,0,0,.35); color: var(--paper);
  border: 1px solid rgba(255,255,255,.28);
  opacity: 0; pointer-events: none; transition: opacity .35s ease;
}
#tabPanel.focus .perf-btn .perf-hr { display: none; }
#tabPanel.focus.ui-visible .perf-btn { opacity: 1; pointer-events: auto; }
#tabPanel.focus .perf-btn:hover { background: rgba(0,0,0,.55); }
#tabPanel.focus .perf-pop {
  position: fixed; top: 76px; left: 22px; transform: none;
  width: min(320px, 80vw); z-index: 80;
}

/* Audio-glitch alert: the perf button pulses red (forced visible in Zen too)
   and a fixed toast proposes Lite mode */
@keyframes perfPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(139,61,47,.55); }
  50% { box-shadow: 0 0 0 6px rgba(139,61,47,.30); }
}
.perf-btn.alerted {
  color: var(--accent); border-color: var(--accent);
  animation: perfPulse 1.1s ease-in-out infinite;
}
#tabPanel.focus .perf-btn.alerted {
  background: rgba(139,61,47,.6); color: #fff; border-color: rgba(255,255,255,.45);
  opacity: 1; pointer-events: auto;
  animation: perfPulse 1.1s ease-in-out infinite;
}
.glitch-toast {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 22px; z-index: 95;
  display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center;
  max-width: 92vw;
  background: var(--warn-bg); color: var(--warn-ink); font-weight: 600; font-size: 14px;
  border: 1.5px solid var(--accent); border-left-width: 5px; border-radius: 10px;
  padding: 10px 12px 10px 16px;
  box-shadow: 0 8px 24px rgba(0,0,0,.25);
}
.glitch-toast .gt-txt { white-space: nowrap; }
.glitch-toast button.ghost { border-color: rgba(122,42,31,.45); color: var(--warn-ink); }
.glitch-toast button.ghost:hover { background: rgba(139,61,47,.08); }
.glitch-toast .gt-x {
  background: rgba(122,42,31,.08); border: 0; color: var(--warn-ink);
  font-size: 15px; padding: 5px 8px; cursor: pointer; border-radius: 6px;
}
.glitch-toast.in-zen { bottom: auto; top: 84px; }

/* Practice tuner (js/practice.js): needle + in-tune zone + sliding fill bar.
   Lives INSIDE #tabPanel in both layouts; fixed at top-center (below the
   transport icons in Zen, under the sticky header in normal view). Dark ink
   face works on both backgrounds; the zen blanket rule needs undoing. */
.prac-panel {
  position: fixed; top: 70px; left: 50%; transform: translateX(-50%);
  z-index: 90; width: min(340px, 92vw);
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--clay-dark); border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0,0,0,.4);
  padding: 10px 14px 12px; margin: 0;
  display: flex; flex-direction: column; gap: 8px;
  cursor: default;
}
.prac-row1 { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.prac-note { font-size: 26px; font-weight: 750; letter-spacing: .01em; }
.prac-status { flex: 1 1 0; min-width: 0; font-size: 12.5px; opacity: .9;
  line-height: 1.45; min-height: 2.9em; }
/* Engine-state glyph (Ready ● / Hold ● / restart ⟳ / rest ‖): a large dot on
   the right of the row (always empty otherwise), vertically centered against
   the note label — baseline-aligned small dots read like a dotted-note mark. */
.prac-status.prac-glyph {
  font-size: 26px; line-height: 1;
  align-self: center; text-align: right;
  min-height: 0; /* the 2-line reserve is for prose only — em here would resolve against the 26px glyph font */
  transition: color .15s linear;
}
.prac-scale {
  position: relative; height: 10px; border-radius: 6px;
  background: linear-gradient(90deg, rgba(255,255,255,.16) 0 12%, rgba(255,255,255,.10) 12% 88%, rgba(255,255,255,.16) 88%);
  transition: opacity .18s linear;
}
.prac-zone {
  position: absolute; top: 0; bottom: 0; border-radius: 6px;
  background: var(--good); /* solid in-tune green — must read at a glance */
  box-shadow: 0 0 0 1px rgba(255,255,255,.4) inset, 0 0 9px rgba(47,107,79,.6);
}
.prac-mark {
  position: absolute; top: -3px; bottom: -3px; width: 3px; border-radius: 2px;
  background: var(--paper); left: 50%;
  transform: translateX(-50%);
  transition: left .08s linear;
}
/* Unregistered input (silence / room noise): the whole tuning bar stays out
   of the picture and fades back in the moment the mic registers a pitch —
   the in/out itself is the registered/not-registered feedback. */
.prac-scale.prac-idle { opacity: 0; }
.prac-track {
  position: relative; height: 12px; border-radius: 7px;
  background: rgba(255,255,255,.13); overflow: hidden;
}
.prac-fill { height: 100%; width: 0%; border-radius: 7px; background: var(--ch1); transition: width .1s linear; }
/* author display beats the UA [hidden] rule — hiding must actually hide */
.prac-panel[hidden] { display: none; }
/* Integrated tuner: the live card's meta band hosts the tuner BETWEEN the
   note letter and the duration symbol — the card's own nm shows the note,
   the chamber background is the meta's own. The strip is a center flex
   child: two stacked bars scaled between the text columns; the state glyph
   rides to their right (order 3, just before the dur symbol). The floating
   overlay remains for grid/scroll layouts. */
.prac-panel.in-card,
#tabPanel.focus .prac-panel.in-card { /* beats the zen overlay tuning (top/width/padding) */
  position: static; inset: auto; transform: none;
  width: auto; z-index: auto; flex: 1 1 auto; min-width: 0;
  display: grid;
  grid-template-columns: minmax(44px, 1fr) auto;
  gap: 3px 7px; align-items: center;
  padding: 0; margin-inline: 8px; /* breathing room from nm and the dur symbol */
  border: 0; border-radius: 0; box-shadow: none;
  color: inherit; background: none;
}
.prac-panel.in-card .prac-row1 { display: contents; }
.prac-panel.in-card .prac-note { display: none; }
.prac-panel.in-card .prac-status { display: none; } /* no glyph in the meta band */
.prac-panel.in-card .prac-scale { grid-column: 1; grid-row: 1; }
.prac-panel.in-card .prac-track { grid-column: 1; grid-row: 2; }
/* the in-card display rules would beat [hidden] (the zen seat rule carries
   an ID, which outranks any class selector) — hiding must actually hide */
.prac-panel.in-card[hidden],
#tabPanel.focus .prac-panel.in-card[hidden] { display: none; }
.prac-panel.in-card .prac-mark { background: currentColor; } /* chamber: white; plain meta: ink */
/* Symmetric band: with the tuner seated, the meta becomes a 3-column grid
   with EQUAL side columns, so the note letter and the duration symbol keep
   identical geometry and the bars sit on the band's center line (reverts
   when the tuner dis-seats). */
.card.live:has(.prac-panel.in-card) .meta {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(96px, 3fr) minmax(0, 1fr);
  align-items: center;
}
/* Wipe signal without the glyph: the empty hold rail takes the restart tint
   while the pin lasts (HOLD_MSG_MS). */
.prac-panel.in-card .prac-track.prac-restart { background: rgba(232,160,122,.5); }
.prac-panel button.ghost { background: transparent; color: var(--paper); border-color: rgba(255,255,255,.35); }
.prac-panel button.ghost:hover { background: rgba(255,255,255,.12); }
#tabPanel.focus .prac-panel {
  top: 92px; width: min(460px, 92vw);
  padding: 14px 18px 16px; gap: 10px;
}
#tabPanel.focus .prac-note { font-size: 34px; }
#tabPanel.focus .prac-status { font-size: 14px; }
#tabPanel.focus .prac-status.prac-glyph { font-size: 36px; }
#tabPanel.focus .prac-track { height: 16px; }
/* undo the zen blanket `#tabPanel.focus > *` (position:relative, z-index:1):
   same specificity, later rule, so it keeps the fixed anchor + elevation */
#tabPanel.focus > .prac-panel { position: fixed; z-index: 90; }
/* sliding fill on the highlighted token / live card */
.prac-tok-fill {
  position: absolute; left: 0; bottom: -3px; height: 4px; border-radius: 2px;
  width: 0%; pointer-events: none;
  transition: width .1s linear;
}
.prac-pos-rel { position: relative; }
/* The dbgPanel and the toast are reparented into #tabPanel while Zen's
   fullscreen is active — undo the blanket `#tabPanel.focus > *` rule
   (position:relative, z-index:1), which would break their fixed layouts.
   The panel sits below the ✕ (right side); both ride above perf-pop (z-80). */
#tabPanel.focus > #dbgPanel {
  position: fixed; top: 76px; right: 10px; z-index: 120;
  width: min(350px, calc(100vw - 20px)); max-height: calc(100vh - 96px);
}
#tabPanel.focus > .glitch-toast {
  position: fixed;
  /* NO bottom here: with both top (from .in-zen) and bottom set, a fixed
     element stretches across them — the toast grew to ~500px tall. .in-zen
     (stronger than the base rule) keeps it top-centered with height=auto. */
  font-size: 12.5px;
  gap: 8px;
  padding: 8px 10px 8px 13px;
  max-width: min(92vw, 380px);
}
#tabPanel.focus > .glitch-toast button.ghost { padding: 5px 9px; }

/* Collapsible section: the top-left chevron folds the body away */
.block.collapsed > :not(.box-head) { display: none; }
.block.collapsed .collapse-btn { transform: rotate(-90deg); }
/* Section collapse chevron, drawn in CSS: the ▾ character is a plain
   triangle like the play buttons. Expanded = points down, collapsed =
   right (the button's own -90° rotation carries the shape along).
   A fixed square face keeps the chevron at equal distances from all
   edges (including the left). */
.collapse-btn {
  width: 30px; height: 30px; padding: 0; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
}
.collapse-btn::before {
  content: ""; display: inline-block; width: .5em; height: .5em;
  border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  margin-top: -0.16em; /* the rotated L hangs low — lift to center */
}
/* Collapsed, the box-head IS the block: round its bottom corners too, so
   they don't poke past the block's rounded border at the bottom. */
.block.collapsed > .box-head { border-radius: 7px 7px 7px 7px; }

.focus-tempo {
  display: flex; align-items: center; gap: 10px;
  font-weight: 700; font-size: 15px; color: var(--ink);
}
/* The playback-only dials dim while practice owns the transport — opacity
   only: no layout change (the zen bar must not jump), ghosted and inert. */
.tempo-grp, .focus-tempo { transition: opacity .2s linear; }
.dial-row.dial-off, .focus-tempo.dial-off { opacity: .18; pointer-events: none; }
#tabPanel.focus .focus-tempo,
#tabPanel.focus .focus-tempo span,
#tabPanel.focus .focus-tempo label { color: var(--paper); }
.focus-tempo input[type=range] { width: min(38vw, 340px); }
#focusTempoVal { min-width: 3ch; text-align: right; font-variant-numeric: tabular-nums; }

/* ---- Dev: audio debug panel (console: DEBUG=1) ---- */
#dbgPanel {
  display: none;
  position: fixed; top: 10px; right: 10px; z-index: 90;
  width: 350px; max-height: calc(100vh - 20px); overflow: auto;
  background: var(--zen); color: var(--paper);
  border: 1px solid var(--clay-dark); border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0,0,0,.45);
  font: 12px/1.35 ui-monospace, SFMono-Regular, Consolas, monospace;
  padding: 10px 12px 12px;
}
#dbgPanel.open { display: block; }
#dbgPanel .dbg-head {
  display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px;
  /* The header is the drag handle: grab cursor, no text selection, and
     touch-action none so touch-dragging isn't hijacked by scrolling. */
  cursor: move; user-select: none; -webkit-user-select: none; touch-action: none;
}
#dbgPanel .dbg-head b {
  font-size: 13px; letter-spacing: .08em; color: var(--clay);
}
#dbgPanel .dbg-head .dbg-hint { opacity: .55; flex: 1; }
#dbgPanel .dbg-x {
  background: transparent; color: var(--paper); opacity: .7;
  border: 0; padding: 2px 6px; font: inherit; cursor: pointer;
}
#dbgPanel .dbg-x:hover { opacity: 1; }
#dbgPanel .dbg-tests {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 8px; margin-bottom: 8px;
  background: rgba(255,255,255,.06); border-radius: 8px;
}
#dbgPanel .dbg-testcap { opacity: .6; }
#dbgPanel .dbg-note {
  background: var(--clay-dark); color: var(--paper);
  border: 0; border-radius: 6px; padding: 3px 8px;
  font: 11px/1 inherit; cursor: pointer;
}
#dbgPanel .dbg-note:hover { background: var(--clay); color: var(--ink); }
#dbgPanel .dbg-btn {
  background: rgba(255,255,255,.1); color: var(--paper);
  border: 1px solid rgba(255,255,255,.25); border-radius: 6px;
  padding: 3px 8px; font: 11px/1 inherit; cursor: pointer;
}
#dbgPanel .dbg-btn:hover { background: rgba(255,255,255,.22); }
#dbgPanel .dbg-group {
  margin-bottom: 4px; border-top: 1px solid rgba(255,255,255,.14);
  padding-top: 4px;
}
#dbgPanel .dbg-group summary {
  cursor: pointer; padding: 3px 0; opacity: .85;
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  user-select: none; list-style: none;
}
#dbgPanel .dbg-group summary::-webkit-details-marker { display: none; }
#dbgPanel .dbg-group summary::before { content: "▸ "; opacity: .5; }
#dbgPanel .dbg-group[open] summary::before { content: "▾ "; }
#dbgPanel .dbg-group[open] summary { color: var(--clay); }
#dbgPanel .dbg-rows { padding: 2px 0 4px; }
#dbgPanel .dbg-row {
  display: grid; grid-template-columns: 108px 1fr 52px 18px;
  align-items: center; gap: 6px; padding: 1px 0 2px;
}
#dbgPanel .dbg-row.mod .dbg-lab { color: var(--clay); font-weight: 700; }
#dbgPanel .dbg-row.mod::after {
  content: "\2731"; /* ✱ tweaked-from-default marker */
  color: var(--clay); justify-self: start; font-size: 11px;
}
#dbgPanel .dbg-lab {
  font-size: 11px; opacity: .8; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; cursor: default;
}
#dbgPanel .dbg-row input[type=range] { width: 100%; height: 12px; margin: 0; padding: 0; accent-color: var(--clay); }
#dbgPanel .dbg-num {
  width: 100%; background: rgba(255,255,255,.08); color: var(--paper);
  border: 1px solid rgba(255,255,255,.2); border-radius: 4px;
  font: 10px/1.4 inherit; padding: 1px 3px;
  -moz-appearance: textfield;
}
#dbgPanel .dbg-num::-webkit-outer-spin-button,
#dbgPanel .dbg-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#dbgPanel .dbg-rst {
  background: transparent; color: rgba(255,255,255,.55);
  border: 0; padding: 0; font: 12px/1 inherit; cursor: pointer;
}
#dbgPanel .dbg-rst:hover { color: var(--paper); }
#dbgPanel .dbg-foot {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  border-top: 1px solid rgba(255,255,255,.14); margin-top: 8px; padding-top: 8px;
}
#dbgPanel .dbg-tip { opacity: .45; font-size: 10px; }
@media print { #dbgPanel { display: none !important; } }


/* Library notice pill: saves/loads that need one line of explanation */
.lib-toast {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 22px; z-index: 95;
  max-width: 92vw; overflow-wrap: break-word; text-align: center;
  background: var(--paper-2); color: var(--ink); font-weight: 600; font-size: 14px;
  border: 1.5px solid var(--line); border-left-width: 5px; border-radius: 10px;
  padding: 10px 16px;
  box-shadow: 0 8px 24px rgba(0,0,0,.18);
}
#tabPanel.focus .lib-toast { z-index: 95; }

/* In-page dialogs (prompt/confirm replacements for sandboxed views) */
.lib-dialog {
  position: fixed; inset: 0; z-index: 120; display: flex;
  align-items: center; justify-content: center;
  background: rgba(30,18,10,.45); padding: 16px;
}
.lib-dialog-card {
  background: var(--paper); color: var(--ink); border: 1.5px solid var(--line);
  border-radius: 12px; box-shadow: 0 14px 40px rgba(0,0,0,.35);
  padding: 16px 18px; width: min(340px, 92vw);
  display: flex; flex-direction: column; gap: 10px;
}
.lib-dialog-title { font-weight: 700; font-size: 14px; overflow-wrap: break-word; }
.lib-dialog-input {
  font: inherit; font-size: 15px; padding: 8px 10px;
  border: 1.5px solid var(--line); border-radius: 8px; background: var(--panel); color: inherit;
}
.lib-dialog-input:focus { outline: none; border-color: var(--accent); }
.lib-dialog-row { display: flex; gap: 8px; justify-content: flex-end; }
.lib-dialog-row button {
  font: inherit; font-weight: 700; font-size: 13px; cursor: pointer;
  padding: 7px 14px; border-radius: 8px; border: 1.5px solid var(--line);
  background: var(--panel); color: var(--ink);
}
.lib-dialog-row button.lib-dialog-ok { background: var(--accent); border-color: var(--accent); color: var(--paper); }
.lib-dialog-row button:hover { filter: brightness(1.05); }
@media print { .lib-dialog { display: none !important; } }

/* OS-level motion gate, kept at file end so none of the animation rules
   above can win a later-position tie against it. */
@media (prefers-reduced-motion: reduce) {
  #tabPanel.focus::before {
    /* Skip the animated scale swell (the only real movement) but keep the
       full opacity bloom — the halo still rises and fades with each note. */
    display: block;
    transform: none;
    transition: opacity var(--glow-fade, 1.6s) ease-out;
  }
  /* Every other decorative motion: pulses die (the banner's left band and
     the alert's colour still mark them), the wave keeps an opacity-only fade
     without sweeping across the screen, and the focus-token swell freezes
     (ring highlight and box-shadow stay). */
  .range-warn { animation: none; }
  .zen-wave { animation: zen-wave-reduce 1s ease-out forwards; }
  .perf-btn.alerted,
  #tabPanel.focus .perf-btn.alerted { animation: none; }
  #tabPanel.focus .focus-tokens .tok { transition: none; }
  #tabPanel.focus .focus-tokens .tok.now { transform: none; }
  @keyframes zen-wave-reduce {
    0%   { opacity: 0; }
    22%  { opacity: .85; }
    100% { opacity: 0; }
  }
}

/* Screen-reader-only helper blocks referenced by aria-describedby (the
   gesture hints for notation chips / piano keys). */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Keyboard-shortcut help overlay: a paper card over a dimming backdrop; the
   library dialog family's look is shared via its variables where possible. */
.help-dialog {
  position: fixed; inset: 0; z-index: 85;
  display: none; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--ink, #1a120c) 45%, transparent);
  padding: 20px;
}
.help-dialog.open { display: flex; }
.help-card {
  background: var(--panel, #fff);
  color: var(--ink, #1a120c);
  border: 1px solid var(--line, #d8c9b4);
  border-radius: 14px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .35);
  max-width: 520px; width: 100%;
  max-height: min(84vh, 640px); overflow: auto;
  padding: 16px 18px 12px;
}
html[data-theme="oot"] .help-card { --panel: #fff; }
.help-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.help-x { border: 0; background: none; color: inherit; font-size: 18px; line-height: 1; cursor: pointer; padding: 6px 8px; }
.help-rows { margin: 0; display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px; font-size: 14px; }
.help-rows dt { font-weight: 700; color: var(--accent, #8b3d2f); white-space: nowrap; }
.help-rows dd { margin: 0; }

/* Practice history line: one muted run summary under the tuner track. */
.prac-history {
  margin-top: 4px;
  font-size: 11.5px;
  opacity: .8;
  text-align: center;
}

/* Page-title-bar tools: the theme switch + shortcut help live at the far
   right of the sticky header grid (its third 1fr column); ghost styling
   relights for the dark bar in both themes. */
.head-tools {
  display: flex; align-items: center; gap: 8px;
  justify-self: end; align-self: center;
}
header.app button.ghost,
html[data-theme="oot"] header.app button.ghost {
  color: var(--paper);
  border-color: rgba(255, 255, 255, .35);
}
