/* ============================================================
     THEMES — four: "paper" (warm), "light" (pro), "dark" (pro),
                    "sepia" (aged-paper reading mode, site-wide)
     ============================================================ */
  html[data-theme="paper"] {
    --bg: #fdfbf7;
    --bg-2: #f6f1e7;
    --bg-3: #ece6d6;
    --rule: #e6dfca;
    --rule-soft: #efebd8;
    --ink: #1f1b16;
    --ink-soft: #4d4537;
    --ink-mute: #8a8071;
    --ink-quiet: #b6ad9a;
    --accent: #6e7d61;
    --warn: #b07c2c;
    --bad: #a4513b;
    --highlight: rgba(42, 109, 244, 0.15);
    --highlight-stroke: #2a6df4;
    --score-bg: #fdfbf7;
    --score-ink: #1f1b16;
    --score-tint: 0.28;   /* Feature #58 — warm the engraved music to the paper */
    --shadow: 0 1px 2px rgba(31,27,22,0.05), 0 6px 18px rgba(31,27,22,0.05), 0 28px 56px rgba(31,27,22,0.06);
  }
  html[data-theme="light"] {
    --bg: #ffffff;
    --bg-2: #f5f5f6;
    --bg-3: #ebebed;
    --rule: #e2e2e5;
    --rule-soft: #efeff1;
    --ink: #0d0d0e;
    --ink-soft: #3a3a3d;
    --ink-mute: #76767a;
    --ink-quiet: #b3b3b8;
    --accent: #2a6df4;
    --warn: #d97706;
    --bad: #dc2626;
    --highlight: rgba(42, 109, 244, 0.15);
    --highlight-stroke: #2a6df4;
    --score-bg: #ffffff;
    --score-ink: #0d0d0e;
    --score-tint: 0;      /* Feature #58 — pro light keeps crisp black ink */
    --shadow: 0 1px 2px rgba(15,15,17,0.04), 0 4px 14px rgba(15,15,17,0.06), 0 24px 48px rgba(15,15,17,0.06);
  }
  html[data-theme="dark"] {
    /* Sober editorial dark — neutral graphite with restrained blue accent.
       NO orange, NO neon. Inspired by Linear / Vercel / Apple-dark. */
    --bg: #0d0f12;
    --bg-2: #14171c;
    --bg-3: #1c2027;
    --rule: #232830;
    --rule-soft: #181c22;
    --ink: #ecedef;
    --ink-soft: #b9bdc4;
    --ink-mute: #7a8089;
    --ink-quiet: #4a4f57;
    --accent: #5b8def;          /* restrained editorial blue */
    --warn: #d8b56a;
    --bad: #e57373;
    --highlight: rgba(91, 141, 239, 0.16);
    --highlight-stroke: #5b8def;
    --score-bg: #f7f7f5;        /* near-white paper — high contrast for sheet music in dark UI */
    --score-ink: #0a0a0a;
    --score-tint: 0;            /* Feature #58 — dark UI uses near-white paper, ink stays black */
    --shadow: 0 1px 2px rgba(0,0,0,0.5), 0 6px 18px rgba(0,0,0,0.55), 0 28px 56px rgba(0,0,0,0.6);
  }
  html[data-theme="sepia"] {
    /* Sepia — aged-paper reading mode. Warm tan ground, brown ink; the
       score itself sits on bright cream sheet-music stock. Applies
       site-wide via data-theme, like the other themes. */
    --bg: #f1e5c9;
    --bg-2: #e9dbb9;
    --bg-3: #ddcb9f;
    --rule: #d2bd92;
    --rule-soft: #e3d4ac;
    --ink: #3b2c18;
    --ink-soft: #5f4828;
    --ink-mute: #897048;
    --ink-quiet: #b29c73;
    --accent: #9c6a31;
    --warn: #b07c2c;
    --bad: #a4513b;
    --highlight: rgba(166, 92, 54, 0.20);
    --highlight-stroke: #a65c36;
    --score-bg: #f7eed6;
    --score-ink: #3b2c18;
    --score-tint: 0.62;   /* Feature #58 — sepia reading mode browns the engraved music */
    --shadow: 0 1px 2px rgba(59,44,24,0.06), 0 6px 18px rgba(59,44,24,0.08), 0 28px 56px rgba(59,44,24,0.10);
  }

  :root {
    --easing: cubic-bezier(0.32, 0.72, 0, 1);
    --easing-fast: cubic-bezier(0.4, 0, 0.2, 1);
    --radius-lg: 14px;
    --radius-md: 8px;
    --radius-sm: 4px;
  }

  * { box-sizing: border-box; }
  html, body {
    margin: 0; padding: 0; min-height: 100dvh;
    background: var(--bg); color: var(--ink);
    font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
    font-size: 13.5px; line-height: 1.5;
    -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
    transition: background 200ms var(--easing), color 200ms var(--easing);
    /* Mobile/iPad: prevent rubber-band scroll outside the app, hide tap highlight */
    overscroll-behavior: none;
    -webkit-tap-highlight-color: transparent;
    -webkit-touch-callout: none;
  }
  body {
    display: flex; flex-direction: column; min-height: 100dvh;
    /* Honour iOS safe areas (notch, home indicator) */
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
    padding-bottom: env(safe-area-inset-bottom);
  }
  /* iPad/touch device tweaks — WCAG AA 2.5.5 wants 44×44 minimum touch targets.
     Previously capped at 40×40 "so the topbar doesn't overflow" but on actual
     iPads the topbar still has headroom; comply with the standard. */
  @media (pointer: coarse) {
    .icon-btn { min-width: 44px; min-height: 44px; }
    #play-btn { min-height: 44px; padding: 6px 14px; }
    #auth-chip { min-height: 44px; padding: 8px 14px; }
    .src-btn { min-height: 44px; padding: 8px 14px; }
    .top-meta { font-size: 11px; }
    .conf-pill { font-size: 11px; padding: 6px 12px; min-height: 44px; box-sizing: border-box; }
    /* The lab tab strip — these are the score/sources/theory/bootleg tabs.
       30px tall fights the 44px guideline; ease them. */
    .lab-tab { min-height: 44px; padding: 0 12px; }
  }
  /* Tablet portrait + small laptops (iPad 768–1024px portrait):
     drop the dense JetBrains-Mono telemetry strip from the topbar — the same
     numbers live in the right-side lab panel and the score reader. */
  @media (max-width: 1024px) {
    .top-meta { display: none; }
    .top-divider { margin: 0 2px; }
    .conf-pill .conf-label { display: none; }
    .conf-pill { padding: 5px 9px; }
    .brand sub { display: none; }      /* the v0.2 badge — wastes pixels on small viewports */
    .topbar { gap: 4px; padding: 0 10px; }
  }
  /* iPhone portrait: collapse the rest down to PLAY + LIBRARY essentials */
  @media (max-width: 480px) {
    .conf-pill .conf-label { display: none; }
    .conf-pill { padding: 6px; }
    .theme-switch { display: none; }   /* theme is settable from the lab drawer */
  }

  /* ============================================================
     TOP BAR
     ============================================================ */
  .topbar {
    position: relative; z-index: 30;
    min-height: 48px; flex-shrink: 0;
    display: flex; align-items: center; gap: 4px;
    padding: 0 14px;
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
    /* Stay a single row — a nav bar must not wrap. The brand shrinks + ellipsis-
       truncates and the spacer collapses on cramped widths; the phone breakpoint
       below drops the power tools entirely. Wrapping dropped the sign-in button
       onto its own line at intermediate widths, which read as broken. */
    flex-wrap: nowrap;
  }
  .topbar > * { flex-shrink: 0; }
  .topbar .spacer { flex-shrink: 1; }
  .brand {
    display: flex; align-items: center; gap: 9px; padding-right: 14px;
    font-size: 14px; font-weight: 600; letter-spacing: -0.01em;
    color: var(--ink);
    flex-shrink: 1; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .brand .mark { width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; }
  .brand .mark svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
  .brand sub {
    font-family: "JetBrains Mono", monospace;
    font-size: 9px; color: var(--ink-mute);
    text-transform: uppercase; letter-spacing: 0.16em;
    margin-left: 4px; vertical-align: middle;
    border-left: 1px solid var(--rule); padding-left: 8px;
    font-weight: 400;
  }

  .icon-btn {
    appearance: none; border: 1px solid transparent; background: transparent;
    color: var(--ink-soft); cursor: pointer;
    width: 30px; height: 30px; border-radius: var(--radius-sm);
    display: inline-flex; align-items: center; justify-content: center;
    transition: background 180ms var(--easing-fast), color 180ms var(--easing-fast), border-color 180ms var(--easing-fast), transform 80ms var(--easing-fast);
  }
  .icon-btn:hover { background: var(--bg-2); color: var(--ink); }
  .icon-btn:active { transform: translateY(1px); }
  #trainer-btn { order: 30; }
  #anchor-btn { order: 31; }
  #studio-btn { order: 32; }
  #rec-pick { order: 33; }
  #source-toggle { order: 120; }
  #play-btn { order: 121; }
  #auth-slot {
    order: 200;
    margin-left: auto !important;
    position: sticky;
    right: 8px;
    z-index: 2;
  }
  /* Held / armed state — e.g. the Anchor button while "anchor mode" is on. */
  .icon-btn[aria-pressed="true"] {
    background: var(--ink); color: var(--bg); border-color: var(--ink);
  }
  .icon-btn[aria-pressed="true"]:hover { background: var(--ink); color: var(--bg); }
  .icon-btn svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
  /* While anchor mode is armed: crosshair over the score + a soft outline so
     it's obvious the next click lands an anchor. */
  body.anchor-mode #page-stage { cursor: crosshair; }
  body.anchor-mode #page-stage::after {
    content: ""; position: absolute; inset: 4px; border-radius: 6px;
    border: 1.5px dashed var(--accent, #c2410c);
    pointer-events: none; opacity: 0.55; z-index: 6;
  }
  /* Anchor button pulses while armed so it's obvious the click did something
     (audit M2 — the crosshair was too subtle on its own). */
  #anchor-btn.armed {
    color: var(--accent, #c2410c);
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, #c2410c) 60%, transparent);
    animation: anchor-armed-pulse 1.6s ease-out infinite;
  }
  @keyframes anchor-armed-pulse {
    0%   { box-shadow: 0 0 0 0    color-mix(in srgb, var(--accent, #c2410c) 60%, transparent); }
    70%  { box-shadow: 0 0 0 8px  color-mix(in srgb, var(--accent, #c2410c)  0%, transparent); }
    100% { box-shadow: 0 0 0 0    color-mix(in srgb, var(--accent, #c2410c)  0%, transparent); }
  }
  @media (prefers-reduced-motion: reduce) {
    #anchor-btn.armed { animation: none; outline: 2px solid var(--accent, #c2410c); }
  }
  /* F4 -- low-confidence page hold: while the live aligner is unsure the page
     doesn't auto-flip; the anchor button glows so it's obvious to tap a bar. */
  body.lowconf-hold #anchor-btn {
    color: var(--accent, #c2410c);
    animation: lowconf-hold-pulse 1.1s ease-in-out infinite;
  }
  @keyframes lowconf-hold-pulse {
    0%, 100% { box-shadow: 0 0 0 0   color-mix(in srgb, var(--accent, #c2410c) 55%, transparent); }
    55%      { box-shadow: 0 0 0 7px color-mix(in srgb, var(--accent, #c2410c)  0%, transparent); }
  }
  @media (prefers-reduced-motion: reduce) {
    body.lowconf-hold #anchor-btn { animation: none; outline: 2px solid var(--accent, #c2410c); }
  }

  .top-divider { width: 1px; height: 18px; background: var(--rule); margin: 0 6px; }

  .top-meta {
    display: inline-flex; align-items: baseline; gap: 5px;
    color: var(--ink-mute);
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; letter-spacing: 0.02em;
    font-feature-settings: "tnum"; text-transform: lowercase;
  }
  .top-meta b { color: var(--ink); font-weight: 500; min-width: 1ch; }
  .top-meta .sep { color: var(--ink-quiet); }
  /* The page/measure/audio strip + the confidence pill are operator chrome —
     they only carry meaning while the AI is actually following. Hidden when
     nothing is playing so the navbar reads clean (the em-dash placeholders
     looked broken). `body.playing` is toggled in play()/stop(). */
  body:not(.playing) .top-meta,
  body:not(.playing) .conf-pill { display: none; }

  /* Confidence pill — visible AI trust indicator */
  .conf-pill {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 10px 3px 8px;
    border-radius: 999px;
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; font-weight: 500;
    background: var(--bg-2);
    color: var(--ink-mute);
    border: 1px solid var(--rule);
    transition: all 240ms var(--easing);
    white-space: nowrap;
    user-select: none;
  }
  .conf-pill .conf-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--ink-quiet);
    box-shadow: none;
    transition: all 240ms var(--easing);
  }
  .conf-pill[data-state="locked"] {
    color: #6ee7b7; border-color: rgba(110,231,183,0.35); background: rgba(110,231,183,0.08);
  }
  .conf-pill[data-state="locked"] .conf-dot {
    background: #6ee7b7;
    box-shadow: 0 0 8px rgba(110,231,183,0.65);
    animation: conf-pulse 1.4s ease-in-out infinite;
  }
  .conf-pill[data-state="uncertain"] {
    color: #fbbf77; border-color: rgba(251,191,119,0.35); background: rgba(251,191,119,0.08);
  }
  .conf-pill[data-state="uncertain"] .conf-dot {
    background: #fbbf77;
    box-shadow: 0 0 8px rgba(251,191,119,0.6);
  }
  .conf-pill[data-state="lost"] {
    color: #fca5a5; border-color: rgba(252,165,165,0.4); background: rgba(252,165,165,0.1);
  }
  .conf-pill[data-state="lost"] .conf-dot {
    background: #fca5a5;
    box-shadow: 0 0 8px rgba(252,165,165,0.6);
  }
  @keyframes conf-pulse {
    0%,100% { box-shadow: 0 0 8px rgba(110,231,183,0.65); }
    50% { box-shadow: 0 0 14px rgba(110,231,183,0.95); }
  }

  .spacer { flex: 1; }

  .status {
    font-family: "JetBrains Mono", monospace;
    font-size: 9.5px; padding: 4px 9px; border-radius: 99px;
    background: var(--bg-2); color: var(--ink-soft);
    border: 1px solid var(--rule);
    text-transform: uppercase; letter-spacing: 0.1em;
    font-weight: 500;
    transition: all 200ms var(--easing-fast);
  }
  .status.live { background: var(--accent); color: var(--bg); border-color: transparent; }
  .status.warn { background: var(--warn); color: var(--bg); border-color: transparent; }
  .status.bad { background: var(--bad); color: var(--bg); border-color: transparent; }
  .status.playing { background: var(--ink); color: var(--bg); border-color: transparent; }

  /* Build identity stamp — tiny commit SHA in the topbar so the user can
     verify which deploy they're looking at. Clicking opens /api/health.
     Populated via the X-Sheet-Turner-Commit response header (see /api/health). */
  .build-stamp {
    font-family: "JetBrains Mono", monospace;
    font-size: 9.5px; color: var(--ink-quiet);
    text-decoration: none; padding: 2px 4px;
    border-radius: 3px; transition: color 160ms;
  }
  .build-stamp:hover { color: var(--ink-mute); background: var(--bg-2); }

  /* ============================================================
     THEME SWITCH (4-way pill, like channels.html toggles)
     ============================================================ */
  .theme-switch {
    display: inline-flex; padding: 2px;
    background: var(--bg-2); border: 1px solid var(--rule);
    border-radius: 99px; gap: 0;
  }
  .theme-switch button {
    appearance: none; border: none; background: transparent;
    width: 24px; height: 22px; border-radius: 99px;
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer; color: var(--ink-mute);
    transition: background 200ms var(--easing-fast), color 200ms var(--easing-fast);
  }
  .theme-switch button:hover { color: var(--ink); }
  .theme-switch button.active {
    background: var(--ink); color: var(--bg);
  }
  .theme-switch svg { width: 12px; height: 12px; stroke: currentColor; fill: none; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

  /* ============================================================
     STAGE — score viewport
     ============================================================ */
  .stage {
    /* Flex container so .frame can use `flex: 1` to fill the remaining
       height. We can't use `height: 100%` on .frame because body uses
       `min-height: 100dvh` (not `height`), so percentage heights don't
       resolve — .frame collapses to its content height (~57px) and the
       score panel disappears. Padding is tight on purpose: the score IS the
       product, give it the pixels. */
    flex: 1; position: relative; overflow: hidden;
    padding: 8px clamp(8px, 1.4vw, 16px);
    display: flex;
  }
  .frame {
    position: relative;
    flex: 1 1 auto; min-width: 0;
    background: var(--bg-2);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    padding: 4px;
    box-shadow: var(--shadow);
    /* Flex column so #page-stage gets remaining space below the meta-bar
       and inspector. Without this, page-stage collapses to 0 — leaving
       the score invisible. */
    display: flex; flex-direction: column;
  }
  .frame > #page-stage { flex: 1 1 auto; min-height: 0; }

  /* AI cursor overlay on PDF pages — translucent BLUE BOX framing the current measure,
     position driven by alignment ticks (score_time_quarter → measure index). */
  .pdf-cursor {
    position: absolute;
    pointer-events: none;
    background: rgba(59, 130, 246, 0.16);
    border: 1.5px solid rgba(59, 130, 246, 0.85);
    border-radius: 4px;
    box-shadow: 0 0 14px rgba(59,130,246,0.45), 0 0 4px rgba(0,0,0,0.25);
    z-index: 5; opacity: 0;
    transition: opacity 220ms, top 180ms ease-out, left 180ms ease-out, width 180ms ease-out, height 180ms ease-out;
  }
  .pdf-cursor.live { opacity: 0.95; }
  .pdf-cursor::before {
    /* Élie 2026-06-03 — was 'AI · m' attr(data-m): the stray 'm' concatenated onto
       the word in data-m ("anchor" -> "AI · manchor", "measure 7" -> "AI · mmeasure 7").
       data-m always holds a full word, so the literal 'm' is parasitic. */
    content: 'AI · ' attr(data-m);
    position: absolute;
    top: -20px; left: -1px;
    font-family: "JetBrains Mono", monospace;
    font-size: 10px; font-weight: 700; color: #fff;
    background: rgba(59, 130, 246, 0.95); padding: 1px 6px; border-radius: 3px 3px 0 0;
    line-height: 1.2;
    white-space: nowrap;
  }
  @keyframes pdf-cursor-pulse {
    0% { box-shadow: 0 0 22px rgba(59,130,246,0.85), 0 0 4px rgba(0,0,0,0.25); }
    100% { box-shadow: 0 0 14px rgba(59,130,246,0.45), 0 0 4px rgba(0,0,0,0.25); }
  }
  .pdf-cursor.pulse { animation: pdf-cursor-pulse 0.5s ease-out; }

  #page-stage {
    position: relative; height: 100%; width: 100%;
    background: var(--score-bg);
    border-radius: 10px;
    overflow: hidden;
  }
  /* Page transitions: subtle slide + fade. Uses position:absolute so pages can overlap during the swap.
     Élie 2026-05-25: "quand on go back vers la page precedente la nimatin
     est pareil a page suivante." Forward and back now use opposite
     directions — forward = new enters from RIGHT, old exits LEFT (the
     prior default). Back = new enters from LEFT, old exits RIGHT. The
     direction is read from `#page-stage[data-dir]` (set by showPage()).
     `[data-dir="back"]` flips both the incoming prep transform and the
     exit translate so a back nav reads as a real page-flip backwards. */
  /* Élie 2026-06-03 — perspective:1400px REMOVED. The page turn is pure 2D
     (translate3d X + scale, Z is always 0; no rotateX/Y/translateZ/preserve-3d
     anywhere), so perspective was visually inert — but it forced every .page into
     a 3D rendering context, where WebKit/Mac tile-drops large Verovio SVGs under
     memory pressure = "des lignes d'un systeme disparaissent". Removing it is a
     zero-visual-change probe for that bug. */
  #page-stage { }
  .page {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    padding: clamp(6px, 1vw, 14px);
    box-sizing: border-box;
    text-align: center;
    overflow: hidden;            /* one page = one screen: never scroll to read it */
    background: var(--score-bg);
    /* Élie 2026-06-03 — KILL THE CROSSFADE FLASH. Root cause of "ça flash de le
       tourne page" (proven live in Chrome): the page turn cross-faded OPACITY —
       outgoing page 1->0 while incoming page 0->1 AT THE SAME TIME. For ~320ms
       BOTH pages were semi-transparent and stacked, so you saw a ghosted
       DOUBLE-IMAGE of two different score pages (page 1's notes + page 2's notes
       superimposed at 50%). That blurry double-exposure IS the flash.
       Fix: pages are now OPAQUE at all times (no opacity transition). The
       incoming page covers the outgoing — two different pages are never both
       translucent. Inactive pages are hidden with `visibility` (which does NOT
       alpha-composite, so it can't ghost). The directional slide STAYS (the
       animation Élie asked to keep, not delete) — it's transform-only now.
       `will-change: transform` only (opacity off): the old `will-change: opacity`
       + perspective layer was also a WebKit demotion-flash source on Mac. */
    opacity: 1;
    visibility: hidden;          /* non-current pages: fully hidden, never ghost */
    transform: translate3d(14px, 0, 0) scale(0.996);
    pointer-events: none;
    transition: transform 420ms var(--easing);
    /* Élie 2026-06-03 — will-change MOVED off the base .page (it permanently
       promoted all 3 heavy Verovio SVG pages to GPU layers; Safari/Mac tile-drops
       them = vanishing system lines). It is now scoped (rule below) to ONLY the
       pages that actually move during the turn. contain:paint keeps a repaint of
       one page from invalidating a sibling. The overlay (#measure-frame, #play-*,
       #ai-cursor-badge) are SIBLINGS of .page under #page-stage, not children, so
       contain:paint here cannot clip them. */
    contain: paint;
    display: flex;
    align-items: center;         /* whole page fits → centre it in the panel */
    justify-content: center;
  }
  /* will-change only on pages mid-turn — keeps the slide on the GPU but lets WebKit
     drop the backing store of the 1-2 idle pages (~66% less layer memory). */
  #page-stage .page.visible,
  #page-stage .page.prep-fwd,
  #page-stage .page.prep-back,
  #page-stage .page.exit-left,
  #page-stage .page.exit-right { will-change: transform; }
  /* Back direction: incoming pages prep from the LEFT. The :not() guard is
     critical — the previous version targeted ALL .page descendants while
     [data-dir="back"] was set, and CSS specificity (ID+attr+tag) beat the
     bare .exit-right class, so the exiting page got dragged back to -6px
     instead of flying to +10. Net effect: forward + back looked identical,
     which is exactly what Élie reported ("tourne page precedente animation
     est encore la meme animation que page suivante"). */
  #page-stage[data-dir="back"] .page:not(.visible):not(.exit-left):not(.exit-right) {
    transform: translate3d(-14px, 0, 0) scale(0.996);
  }
  /* Élie 2026-05-26 round 2 ("la page go back animation est encore le meme
     que page suivante"): the [data-dir=back] prep selector alone doesn't
     work because showPage() flips data-dir and adds .visible in the SAME
     tick. The browser never paints the -14px prep transform — it
     transitions from the previously-computed +14px (default prep) straight
     to 0, which reads identically to a forward flip. Fix: explicit
     prep-back / prep-fwd classes applied by JS BEFORE the visible class,
     with a forced reflow between them so the prep transform is committed
     as the transition "from" state. Higher specificity (#id + class) so
     they survive any future [data-dir] attribute rule changes. */
  /* Incoming page preps OPAQUE off to the side (no opacity:0 fade anymore) and
     slides to 0 over the top (z-index 3 > the outgoing's z 1). Opaque the whole
     way, so it COVERS instead of cross-fading — zero ghost. */
  #page-stage .page.prep-fwd {
    transform: translate3d(28px, 0, 0) scale(0.992);
    visibility: visible;
    opacity: 1;
    z-index: 3;
  }
  #page-stage .page.prep-back {
    transform: translate3d(-28px, 0, 0) scale(0.992);
    visibility: visible;
    opacity: 1;
    z-index: 3;
  }
  .page.visible {
    opacity: 1;
    visibility: visible;
    transform: translate3d(0, 0, 0) scale(1);
    pointer-events: auto;
    z-index: 2;
  }
  /* Outgoing page: HIDDEN immediately (Élie 2026-06-03 — "page 2 casse tout" on
     Safari/Mac). The previous version left the exiting page visibility:VISIBLE,
     opacity:1 lingering beneath the incoming, relying on the incoming's opaque
     background to cover it. Chromium covered it fine; on WebKit a permanently-
     visible page underneath can bleed through (and its measure elements confuse
     the cursor/box overlay) = the score looks broken on page 2+. Fix: the moment
     a page exits, hide it. Only ONE page is ever visible. The incoming (opaque,
     prep -> visible) still slides in over the score-bg, so the turn animation is
     intact and there is still no opacity crossfade (= no ghost). */
  #page-stage .page.exit-left,
  #page-stage .page.exit-right {
    visibility: hidden;
    z-index: 1;
  }
  /* Reduced motion: instant swap */
  @media (prefers-reduced-motion: reduce) {
    .page { transition: none; transform: none; }
  }
  /* Verovio SVG / PDF canvas — FIT THE PAGE: scale down (preserving aspect
     ratio via the viewBox) so the whole page is visible at once, no scroll.
     `width/height: auto` + `max-*: 100%` lets the browser pick the largest
     size that fits the .page box. */
  .page svg {
    /* Fill the .page box; the SVG's own viewBox + preserveAspectRatio
       (xMidYMid meet) scales the engraving to fit, centred. Explicit
       100%/100% is REQUIRED — a viewBox-only SVG with `width:auto` renders
       at the 300x150 CSS default size in WebKit (the iPad WKWebView), so the
       score came out tiny. Chrome auto-grew it; WebKit does not. */
    display: block; margin: auto;
    width: 100%; height: 100%;
    /* Feature #58 (Élie 2026-05-27): "les partitions ne changent pas leur
       teint quand on est en mode sépia, faut que ça change." Verovio paints
       near-black glyphs/stafflines on a transparent SVG, so a sepia paper bg
       alone left the music stark black. Warm the engraving toward sepia ink
       in step with the paper, scaled by the per-theme `--score-tint` (0 =
       light/dark, crisp black; 0.62 = sepia). A plain `sepia()` greys the
       noteheads; this stack keeps them deep warm brown and fully opaque.
       Pinned to the SVG element so the smart-annotation `fill:!important`
       tints (set on inner glyphs) still render — the filter only shifts the
       composited raster's hue, the coloured marks stay clearly distinct. */
    filter: sepia(calc(var(--score-tint, 0) * 0.62))
            saturate(calc(1 + var(--score-tint, 0) * 0.55))
            hue-rotate(calc(var(--score-tint, 0) * -8deg))
            brightness(calc(1 - var(--score-tint, 0) * 0.10));
  }
  .page canvas {
    display: block; margin: auto;
    width: auto; height: auto;
    max-width: 100%; max-height: 100%;
  }
  /* iPad app (?native=ios): the score IS the product — full-bleed, no desktop
     "card" frame (border / radius / shadow / stage + page insets). */
  body.native-ios .stage { padding: 0; }
  body.native-ios .frame { border: 0; border-radius: 0; box-shadow: none; padding: 0; }
  body.native-ios #page-stage { border-radius: 0; }
  body.native-ios .page { padding: 4px; }
  /* In dark mode the score area uses warm cream paper (set in --score-bg).
     No filter needed — sheet music stays readable as black-on-cream just like real life. */

  /* The measure highlight is now drawn as a real overlay rectangle
     (#measure-frame, below) positioned from the bar's screen bbox — reliable
     regardless of whether Verovio emits internal boundingBox rects. Neutralise
     any Verovio-emitted rect so we don't double up. */
  .page g.measure rect.boundingBox { fill: transparent; stroke: none; }

  /* Newzik-style measure frame: a rounded rectangle wrapping the WHOLE bar
     currently playing, across both staves of a grand staff (it's pinned to
     g.measure's bounding box, which spans the system). Translucent fill +
     visible border in the theme accent. Only transform/opacity animate (GPU);
     left/top/width/height get a short ease so it glides bar→bar. */
  #measure-frame {
    position: absolute; left: 0; top: 0; width: 0; height: 0;
    z-index: 6; opacity: 0;
    pointer-events: none;
    box-sizing: border-box;
    border-radius: 6px;
    border: 2px solid color-mix(in srgb, var(--highlight-stroke) 85%, transparent);
    background: var(--highlight);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--highlight-stroke) 25%, transparent),
                0 1px 6px color-mix(in srgb, var(--highlight-stroke) 30%, transparent);
    transition: opacity 200ms var(--easing),
                left 240ms var(--easing), top 240ms var(--easing),
                width 240ms var(--easing), height 240ms var(--easing);
    will-change: left, top, width, height;
  }
  #measure-frame.on { opacity: 1; }
  /* F3 — next-page preview: a faded thumbnail of the upcoming page, bottom-right. */
  #next-page-peek {
    position: absolute; right: 12px; bottom: 12px;
    width: 21%; max-width: 146px; aspect-ratio: 2100 / 2970;
    background: var(--score-bg);
    border: 1px solid color-mix(in srgb, var(--highlight-stroke) 45%, transparent);
    border-radius: 6px;
    box-shadow: 0 5px 18px rgba(0, 0, 0, 0.24);
    z-index: 5; overflow: hidden;
    padding: 4px; box-sizing: border-box;
    opacity: 0; transform: translate3d(0, 10px, 0) scale(0.98);
    transition: opacity 280ms var(--easing), transform 280ms var(--easing);
    pointer-events: none;
  }
  #next-page-peek.on { opacity: 0.94; transform: translate3d(0, 0, 0) scale(1); }
  #next-page-peek svg { width: 100%; height: 100%; display: block; }
  #next-page-peek .npp-label {
    position: absolute; top: 4px; left: 6px;
    font-size: 8px; letter-spacing: 0.07em; text-transform: uppercase;
    color: var(--ink-mute);
    background: color-mix(in srgb, var(--score-bg) 82%, transparent);
    padding: 1px 5px; border-radius: 3px;
  }
  body.native-ios #next-page-peek { right: 8px; bottom: 8px; }
  @media (prefers-reduced-motion: reduce) {
    #next-page-peek { transition: opacity 280ms var(--easing); transform: none; }
    #next-page-peek.on { transform: none; }
  }
  /* Soft entrance pulse when the frame jumps to a new bar (occasional — fine). */
  @keyframes measure-frame-enter {
    from { box-shadow: 0 0 0 3px color-mix(in srgb, var(--highlight-stroke) 45%, transparent), 0 1px 10px color-mix(in srgb, var(--highlight-stroke) 40%, transparent); }
    to   { box-shadow: 0 0 0 1px color-mix(in srgb, var(--highlight-stroke) 25%, transparent), 0 1px 6px color-mix(in srgb, var(--highlight-stroke) 30%, transparent); }
  }
  #measure-frame.bump { animation: measure-frame-enter 360ms var(--easing); }
  @media (prefers-reduced-motion: reduce) {
    #measure-frame { transition: opacity 120ms ease; }
    #measure-frame.bump { animation: none; }
  }

  /* Playback cursor — the thin vertical bar that sweeps through the current
     bar at the pace of the music ("la barre qui suit la mesure"). It spans the
     full system height (both staves of a grand staff) because it's pinned to
     the measure's bounding box. Updated every frame from interpolated score
     time, so it glides; only `transform`/`opacity` animate (GPU). */
  #play-cursor {
    position: absolute; left: 0; top: 0;
    width: 3.5px; height: 0;
    z-index: 7; opacity: 0;
    pointer-events: none;
    transform: translate(-200px, 0);
    border-radius: 3px;
    background: linear-gradient(to bottom,
      color-mix(in srgb, var(--highlight-stroke) 85%, white),
      var(--highlight-stroke) 70%,
      color-mix(in srgb, var(--highlight-stroke) 80%, transparent));
    box-shadow: 0 0 10px color-mix(in srgb, var(--highlight-stroke) 65%, transparent),
                0 0 3px color-mix(in srgb, var(--highlight-stroke) 85%, transparent),
                0 0 2px rgba(0,0,0,0.22);
    transition: opacity 160ms var(--easing);
    will-change: transform;
  }
  #play-cursor.on { opacity: 0.96; }
  #play-cursor .pc-cap {
    position: absolute; top: -6px; left: 50%;
    width: 10px; height: 10px; margin-left: -5px;
    border-radius: 50%;
    background: var(--highlight-stroke);
    box-shadow: 0 0 10px color-mix(in srgb, var(--highlight-stroke) 75%, transparent),
                0 0 0 2px var(--score-bg);
  }
  @media (prefers-reduced-motion: reduce) { #play-cursor { transition: opacity 120ms ease; } }

  /* The bouncing dot — snaps onto the notehead currently sounding. Position is
     read from Verovio's timemap (qstamp → note <g>), re-measured every frame so
     zoom / resize / page-turn self-correct. It glides between notes (short
     ease-out on transform — never instant, never bouncy). Pure transform/opacity
     so it stays on the GPU. The translate already includes a -50%/-50% recentre
     baked into the JS coordinates, so transform-origin is irrelevant here. */
  #play-dot {
    position: absolute; left: 0; top: 0;
    width: 7px; height: 7px;
    margin: -3.5px 0 0 -3.5px;          /* recentre on the (cx,cy) we translate to */
    z-index: 8; opacity: 0;
    pointer-events: none;
    border-radius: 50%;
    background: #161616;                 /* small black dot on the notehead */
    box-shadow: 0 0 0 1.5px var(--score-bg);   /* thin paper-coloured halo so it reads against staff lines */
    transform: translate(-9999px, -9999px) scale(0.6);
    transition: transform 110ms cubic-bezier(0.23, 1, 0.32, 1), opacity 150ms var(--easing);
    will-change: transform;
  }
  #play-dot.on { opacity: 1; }
  /* When the dot first appears (and across a page turn) we don't want it to
     "fly in" from its parked off-screen position — JS adds .snap for one frame
     to kill the transform transition, then removes it so note→note still glides. */
  #play-dot.snap { transition: opacity 150ms var(--easing); }
  @media (prefers-reduced-motion: reduce) { #play-dot { transition: opacity 120ms ease; } }

  /* Hide the noisy floating AI badge in production view (still in DOM for debug) */
  #ai-cursor-badge { display: none; }
  /* Per-note glow — the active notehead lights up as it sounds, on top of
     the measure frame + floating play-dot. 2026-05-27 architecte (GH #53):
     restored after `updatePlaybackBar` re-acquired the lastNoteEls hook.
     The previous "hide" rule (`stroke: inherit; fill: inherit; filter: none`)
     was added when ai-current was orphaned (assignment site disappeared in
     a refactor) — now that the dot's notemap lookup also assigns the class,
     the glow visually marks every note Sheet Turner thinks is sounding. */
  .page svg .ai-current,
  .page svg .ai-current * {
    fill: var(--accent, #3b82f6) !important;
    stroke: var(--accent, #3b82f6) !important;
    filter: drop-shadow(0 0 4px var(--accent, #3b82f6));
    transition: fill 80ms ease, stroke 80ms ease;
  }
  /* C3 wiring enquête (2026-05-30): note-MATCH feedback. When the live aligner
     reports a detected onset, applyNotesGlow paints the matched score note green
     (played pitch matched what the score expects at that beat) or red (miss).
     Transient — cleared on the next onset — so the score visibly "reacts" to the
     performance, distinct from the steady blue position glow (.ai-current) above.
     Wins the cascade over .ai-current via source order + !important. */
  .page svg .ai-match,
  .page svg .ai-match * {
    fill: var(--good, #22c55e) !important;
    stroke: var(--good, #22c55e) !important;
    filter: drop-shadow(0 0 5px var(--good, #22c55e));
    transition: fill 80ms ease, stroke 80ms ease;
  }
  .page svg .ai-miss,
  .page svg .ai-miss * {
    fill: var(--bad, #ef4444) !important;
    stroke: var(--bad, #ef4444) !important;
    filter: drop-shadow(0 0 5px var(--bad, #ef4444));
    transition: fill 80ms ease, stroke 80ms ease;
  }

  /* Vertical progress rail at left of stage */
  .progress-rail {
    position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
    background: var(--rule-soft); z-index: 5;
  }
  .progress-fill {
    position: absolute; left: 0; top: 0; right: 0;
    background: var(--accent);
    height: 0%;
    transition: height 200ms linear;
  }

  /* Page indicator dots */
  .page-dots {
    position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%);
    display: flex; gap: 5px;
    background: var(--bg-2);
    padding: 5px 9px; border-radius: 99px;
    border: 1px solid var(--rule); z-index: 8;
    max-width: 80%; overflow-x: auto; scrollbar-width: none;
  }
  .page-dots::-webkit-scrollbar { display: none; }
  .page-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--ink-quiet);
    transition: background 240ms var(--easing-fast), transform 200ms var(--easing-fast);
    cursor: pointer; flex-shrink: 0;
  }
  .page-dot:hover { background: var(--ink-mute); transform: scale(1.3); }
  .page-dot.active { background: var(--accent); transform: scale(1.4); }

  /* Edge tap-zone arrows — 2026-05-26 (Élie, KI récurrent).
     CACHÉS par défaut. RÉVÉLÉS uniquement en mode édition (`html.pt-calibrating`
     ou `html.annotating`) pour aider la navigation pendant marker placement.
     `.next` repositionné verticalement AU-DESSUS du bouton LAB pour éviter
     overlap (LAB est à top:50%, on met page-next à top:22%). */
  .page-nav {
    position: absolute; top: 50%; transform: translateY(-50%);
    z-index: 6; width: 36px; height: 64px;
    background: var(--bg);
    border: 1px solid var(--rule); border-radius: 0 8px 8px 0;
    align-items: center; justify-content: center;
    cursor: pointer; color: var(--ink-soft);
    opacity: 0.85;
    display: none;
  }
  .page-nav.prev { left: 0; border-left: none; }
  .page-nav.next { right: 0; left: auto; border-right: none; border-radius: 8px 0 0 8px; }
  .page-nav svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.6; }
  .page-nav[hidden] { display: none !important; }
  html.pt-calibrating .page-nav:not([hidden]),
  html.annotating .page-nav:not([hidden]) { display: flex; }
  /* Stack .next vertically above LAB edge tab (LAB sits at top:50%).
     Translate -50% keeps it centered on its anchor; top:22% lifts it
     well above LAB's vertical center. */
  html.pt-calibrating .page-nav.next,
  html.annotating .page-nav.next { top: 22%; }
  html.pt-calibrating .page-nav:hover,
  html.annotating .page-nav:hover { background: var(--bg-3); color: var(--ink); opacity: 1; }
  @media (pointer: coarse) {
    html.pt-calibrating .page-nav:not([hidden]),
    html.annotating .page-nav:not([hidden]) { width: 44px; height: 72px; opacity: 0.85; }
  }

  /* States */
  .state {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
    height: 100%; padding: 60px 32px; text-align: center; color: var(--ink-mute);
  }
  .state .eyebrow {
    font-family: "JetBrains Mono", monospace;
    font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.18em;
    color: var(--ink-quiet);
    padding: 4px 10px; border: 1px solid var(--rule); border-radius: 99px;
    background: var(--bg-2);
  }
  .state h2 {
    margin: 0; font-weight: 600;
    font-size: clamp(22px, 2.6vw, 28px); line-height: 1.2;
    letter-spacing: -0.015em; color: var(--ink); max-width: 32ch;
  }
  .state .hint { font-size: 13px; max-width: 42ch; color: var(--ink-soft); line-height: 1.55; }
  .state kbd {
    font-family: "JetBrains Mono", monospace;
    background: var(--bg-2); border: 1px solid var(--rule);
    padding: 1px 5px; border-radius: 4px; font-size: 11px;
    color: var(--ink);
  }
  .errbox {
    color: var(--bad); padding: 14px 18px; border-left: 3px solid var(--bad);
    background: color-mix(in srgb, var(--bad) 8%, transparent);
    font-size: 13px; max-width: 640px; margin: 60px auto;
    border-radius: 8px;
  }

  /* ============================================================
     DRAWER (library)
     ============================================================ */
  /* ============================================================
     LIBRARY DRAWER — redesigned 2026-05-10
     IDs and class names are preserved (JS still works); the visual
     treatment is what changed. Killed: two pill-chip stripes,
     gradient REAL badge, bordered card around results, grey strip
     era headers, four competing UPPERCASE+mono labels.
     ============================================================ */
  .drawer-bd {
    position: fixed; inset: 0;
    background: rgba(20, 16, 10, 0.42);
    backdrop-filter: blur(2px);
    opacity: 0; pointer-events: none;
    transition: opacity 320ms var(--easing); z-index: 80;
  }
  .drawer-bd.open { opacity: 1; pointer-events: auto; }
  .drawer {
    /* Default width is a CSS var so the resize handle can mutate it
       cheaply. Persists in localStorage as pt-drawer-w. */
    --drawer-w: 440px;
    position: fixed; top: 0; left: 0; bottom: 0;
    width: clamp(320px, var(--drawer-w), 92vw);
    background: var(--bg);
    box-shadow:
      1px 0 0 var(--rule),
      24px 0 64px -24px rgba(31, 27, 22, 0.18);
    transform: translateX(-101%);
    transition: transform 360ms var(--easing);
    z-index: 90; display: flex; flex-direction: column;
  }
  .drawer.open { transform: translateX(0); }
  .drawer.resizing { transition: none; }
  body.drawer-resizing,
  body.drawer-resizing * {
    cursor: col-resize !important;
    user-select: none;
  }

  /* Header: title left, close X right. Asymmetric — inset the title
     a touch from the left so it sits inside the visual rhythm rather
     than flush against the edge. */
  .drawer header {
    padding: 22px 16px 14px 22px;
    display: flex; align-items: baseline; justify-content: space-between;
    background: var(--bg);
  }
  .drawer h2 {
    margin: 0; font-size: 22px; font-weight: 700;
    letter-spacing: -0.025em; color: var(--ink);
    line-height: 1;
  }
  .drawer h2::after {
    /* Tiny mono caption under the title — "n pieces · n composers".
       Filled at runtime by refreshFilterCounts(); harmless if absent. */
    content: attr(data-summary);
    display: block; margin-top: 4px;
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; font-weight: 500;
    color: var(--ink-mute); letter-spacing: 0;
  }
  .drawer .body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 14px 22px 24px; }
  .drawer .body section { margin-bottom: 22px; }
  .drawer-section-label {
    /* Was: 9.5px UPPERCASE mono. Now: quiet sentence-case label. */
    display: block; font-family: inherit;
    font-size: 11px; font-weight: 500;
    color: var(--ink-mute); letter-spacing: 0;
    margin-bottom: 8px;
  }

  /* Resize handle on the right edge.
     - 6px hit zone (visual), padded to a generous 14px touch target via ::before
     - cursor:col-resize on desktop; touch-action:none so it does not scroll the body
     - shows a 1px accent line on hover/drag */
  .drawer-resize {
    position: absolute; top: 0; right: -3px; bottom: 0;
    width: 6px;
    cursor: col-resize;
    z-index: 95;
    touch-action: none;
    background: transparent;
  }
  .drawer-resize::before {
    /* Visible 1px line, fades in on hover */
    content: "";
    position: absolute; top: 0; bottom: 0; left: 2px; width: 2px;
    background: var(--ink-quiet); opacity: 0;
    transition: opacity 160ms var(--easing-fast), background 160ms var(--easing-fast);
    border-radius: 2px;
  }
  .drawer-resize::after {
    /* Larger touch hit zone for fingers, invisible */
    content: "";
    position: absolute; inset: 0 -6px; /* 18px wide effective tap area */
  }
  .drawer-resize:hover::before,
  .drawer.resizing .drawer-resize::before {
    opacity: 1; background: var(--accent);
  }
  /* Touch devices: handle is bigger so it's findable with a finger */
  @media (pointer: coarse) {
    .drawer-resize { width: 12px; right: -6px; }
    .drawer-resize::after { inset: 0 -8px; }
  }
  /* Search input: no card, no border-radius pill. A single hairline
     under-rule that thickens on focus. Kills the "input-on-grey-card"
     AI tell while staying obviously editable. */
  .input {
    width: 100%; padding: 12px 4px;
    background: transparent; border: 0;
    border-bottom: 1px solid var(--rule);
    border-radius: 0;
    color: var(--ink); font-size: 16px; font-weight: 500;
    font-family: inherit;
    letter-spacing: -0.005em;
    transition: border-color 200ms var(--easing-fast);
  }
  .input::placeholder { color: var(--ink-quiet); font-weight: 400; }
  .input:focus {
    outline: none;
    border-bottom-color: var(--ink);
  }
  /* One filter row, not two. Chips are quieter — text + count, with a
     subtle underline on the active state instead of a black fill. */
  .filters {
    display: flex; flex-wrap: wrap; gap: 4px 14px;
    margin-top: 14px;
  }
  .chip {
    flex: 0 0 auto; appearance: none;
    padding: 6px 0; border: 0;
    background: transparent; color: var(--ink-mute); cursor: pointer;
    border-radius: 0;
    font-size: 12.5px; font-family: inherit; font-weight: 500;
    letter-spacing: 0;
    border-bottom: 1.5px solid transparent;
    transition: color 160ms var(--easing-fast), border-color 160ms var(--easing-fast);
  }
  .chip:hover:not(.active) { color: var(--ink); }
  .chip:active { transform: translateY(1px); }
  .chip.active {
    color: var(--ink); font-weight: 600;
    border-bottom-color: var(--ink);
    background: transparent;
  }
  .chip .count {
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; margin-left: 5px; opacity: 0.55;
    font-weight: 400;
  }
  /* Format chips — second row, even quieter. They are advanced filters,
     not primary actions. Smaller text, no underline accent on active —
     just the active chip becomes ink. */
  .filters.filters-fmt {
    margin-top: 8px;
    gap: 2px 12px;
    padding-top: 8px;
    border-top: 1px dashed var(--rule-soft);
  }
  .filters.filters-fmt .chip {
    font-size: 11.5px; font-weight: 400;
    color: var(--ink-quiet); padding: 4px 0;
    border-bottom: 0;
  }
  .filters.filters-fmt .chip:hover { color: var(--ink-soft); }
  .filters.filters-fmt .chip.active {
    color: var(--ink); font-weight: 600;
    border-bottom: 0;
  }

  /* Save-to-Personal star — quiet by default, fades in on row hover.
     Stays subtle so it never competes with the title for attention. */
  .result .save-personal {
    appearance: none; border: 0; background: transparent;
    cursor: pointer; font-size: 15px; line-height: 1;
    color: var(--ink-quiet);
    padding: 4px 6px; margin-left: 4px;
    border-radius: 999px;
    opacity: 0;
    transition: opacity 200ms var(--easing-fast),
                color 160ms var(--easing-fast),
                transform 200ms cubic-bezier(0.32, 0.72, 0, 1);
  }
  .result:hover .save-personal,
  .result .save-personal:focus-visible,
  .result .save-personal[aria-pressed="true"] { opacity: 1; }
  .result .save-personal:hover {
    color: var(--ink);
    transform: scale(1.15);
  }
  .result .save-personal[aria-pressed="true"] { color: #c8941a; }
  .result.selected .save-personal { color: rgba(255,255,255,0.6); opacity: 1; }
  .result.selected .save-personal:hover { color: #ffffff; }
  .result.selected .save-personal[aria-pressed="true"] { color: #f0c350; }

  /* Tabs — sentence case, generous spacing, single hairline rule.
     Drop the uppercase + letter-spacing tracking that screamed
     "AI dashboard widget". */
  .lib-tabs {
    display: flex; gap: 2px;
    border-bottom: 1px solid var(--rule);
    padding: 0 14px;
    background: var(--bg);
    /* 5 tabs (Main / Piano solo / Lieder / Personal / Lab samples) don't
       fit on a phone-width drawer — let the row scroll instead of wrap. */
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -ms-overflow-style: none;
  }
  .lib-tabs::-webkit-scrollbar { display: none; }
  .lib-tab {
    appearance: none; border: none; background: transparent;
    color: var(--ink-mute); cursor: pointer;
    padding: 14px 11px 12px;
    font: 500 13px/1 inherit;
    letter-spacing: 0;
    text-transform: none; white-space: nowrap;
    flex: 0 0 auto;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color 200ms var(--easing-fast),
                border-color 200ms var(--easing-fast);
    display: inline-flex; align-items: baseline; gap: 6px;
  }
  .lib-tab:hover { color: var(--ink); }
  .lib-tab.active {
    color: var(--ink);
    border-bottom-color: var(--ink);
    font-weight: 600;
  }
  .lib-tab .count {
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; opacity: 0.5;
    text-transform: none; font-weight: 400;
  }
  .lib-tab.active .count { opacity: 0.75; }

  /* Personal tab — uploaded pieces */
  .personal-result {
    padding: 10px 12px; border-bottom: 1px solid var(--rule-soft); cursor: pointer;
    display: flex; flex-direction: column; gap: 4px;
  }
  .personal-result:hover { background: var(--bg-2); }
  .personal-result:last-child { border-bottom: none; }
  .omr-cta {
    margin-top: 6px;
    appearance: none; cursor: pointer;
    border: 1px solid var(--rule);
    background: var(--bg-2); color: var(--ink-soft);
    border-radius: var(--radius-sm);
    padding: 6px 10px; font: 500 11px/1 inherit;
    transition: all 160ms var(--easing-fast);
    align-self: flex-start;
  }
  .omr-cta:hover:not(:disabled) { background: var(--ink); color: var(--bg); border-color: var(--ink); }
  .omr-cta:disabled { opacity: 0.6; cursor: progress; }

  /* Full-bleed list — no bordered card around the whole scroller.
     The list IS the surface; typography and hairlines do the work. */
  .results {
    margin-top: 18px;
    margin-left: -22px;     /* extend to drawer edges */
    margin-right: -22px;
    border: 0; border-top: 1px solid var(--rule);
    border-radius: 0;
    background: transparent;
    max-height: 64vh; overflow-y: auto;
  }
  /* ------------------------------------------------------------------
     LIBRARY SECTION HEADERS — Era > Composer > Work type > pieces.
     Rules (from user feedback):
       1. Headers are LEFT-ALIGNED — flush with the list edge, just past
          the disclosure caret. They are NEVER "tabbed into the middle".
       2. No header level is smaller than the piece-row size (13.5px).
       3. Only the leaf PIECE rows step inward; that step is what shows
          they belong under the header above them.
       4. Strong delimiters: Era = full-width tinted band + rules above
          and below; Composer = full-width hairline + bold; Work type =
          hairline + bold + a small indent (it genuinely nests under the
          composer).
     Default is collapsed below the Era level, so the everyday view is a
     short list of Era bands, each opening to a clean list of Composers.
     ------------------------------------------------------------------ */

  /* --- Level 1: ERA — a full-width tinted band -------------------- */
  .lib-group { border-bottom: 0; }
  .lib-group-head {
    appearance: none; border: 0; width: 100%;
    background: var(--bg-2);
    padding: 15px 20px 14px 20px;
    display: flex; align-items: baseline; gap: 9px;
    cursor: pointer; text-align: left;
    font-family: inherit; font-size: 16px;
    color: var(--ink); font-weight: 700;
    letter-spacing: -0.012em;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    transition: background 180ms var(--easing-fast);
  }
  /* When a group sits right under the shelves (or is the first child of
     the scroller) the .results border-top already draws the line. */
  .results > .lib-group:first-child .lib-group-head { border-top: 0; }
  .lib-group-head:hover { background: var(--rule-soft); }
  .lib-group-head:hover .group-meta { color: var(--ink-soft); }
  .lib-group-head .caret {
    width: 11px; height: 11px; flex-shrink: 0;
    color: var(--ink-quiet);
    transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1),
                color 180ms var(--easing-fast);
  }
  .lib-group-head:hover .caret { color: var(--ink); }
  .lib-group.collapsed .caret { transform: rotate(-90deg); }
  .lib-group-head .group-name {
    flex: 1 1 auto; min-width: 2.5rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .lib-group-head .group-meta {
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; color: var(--ink-mute); font-weight: 400;
    letter-spacing: 0;
    flex: 0 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    transition: color 180ms var(--easing-fast);
  }
  .lib-group-head .group-meta b { color: var(--ink); font-weight: 600; }
  /* Era body: plain show/hide. (The old grid-template-rows 1fr->0fr collapse
     animation re-laid-out the entire era subtree — dozens of composers, up to
     hundreds of piece rows — on every frame of the 320ms transition: that was
     the lag/jank on collapse/expand. Instant display:none = zero reflow cost,
     matching the composer/work-type levels. The caret still rotates.) */
  .lib-group-body { display: block; }
  .lib-group.collapsed .lib-group-body { display: none; }

  /* --- Level 2: COMPOSER — flush-left, bold, hairline above ------- */
  .lib-subgroup { display: block; }
  .lib-subgroup-head {
    appearance: none; width: 100%;
    border: 0; border-top: 1px solid var(--rule-soft);
    padding: 11px 20px 9px 20px;
    font-size: 14px; color: var(--ink); font-weight: 600;
    font-family: inherit; letter-spacing: -0.005em; text-transform: none;
    background: transparent; cursor: pointer; text-align: left;
    display: flex; align-items: baseline; gap: 8px;
    transition: color 160ms var(--easing-fast), background 160ms var(--easing-fast);
  }
  /* the composer that opens an era doesn't double the band's bottom rule */
  .lib-group-body > .lib-subgroup:first-child > .lib-subgroup-head { border-top: 0; }
  .lib-subgroup-head:hover { background: var(--rule-soft); }
  .lib-subgroup-head .sub-caret {
    width: 9px; height: 9px; color: var(--ink-quiet); flex-shrink: 0;
    transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1),
                color 160ms var(--easing-fast);
  }
  .lib-subgroup-head:hover .sub-caret { color: var(--ink); }
  .lib-subgroup.collapsed .sub-caret { transform: rotate(-90deg); }
  .lib-subgroup-head .sub-name {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .lib-subgroup-head .sub-count {
    font-size: 10px; opacity: 0.55; flex: 0 0 auto; align-self: center;
    font-family: "JetBrains Mono", monospace; font-weight: 400; margin-left: 4px;
  }
  .lib-subgroup-body { display: block; }
  .lib-subgroup.collapsed .lib-subgroup-body { display: none; }

  /* --- Level 3: WORK TYPE — small indent (nests under composer) --- */
  .lib-worktype { display: block; }
  .lib-worktype-head {
    appearance: none; width: 100%;
    border: 0; border-top: 1px solid var(--rule-soft);
    padding: 9px 20px 8px 32px;
    font-size: 13.5px; color: var(--ink-soft); font-weight: 600;
    font-family: inherit; letter-spacing: -0.003em;
    background: transparent; cursor: pointer; text-align: left;
    display: flex; align-items: baseline; gap: 7px;
    transition: color 160ms var(--easing-fast), background 160ms var(--easing-fast);
  }
  .lib-subgroup-body > .lib-worktype:first-child > .lib-worktype-head { border-top: 0; }
  .lib-worktype-head:hover { color: var(--ink); background: var(--rule-soft); }
  .lib-worktype-head .sub-caret {
    width: 8px; height: 8px; color: var(--ink-quiet); flex-shrink: 0;
    transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1),
                color 160ms var(--easing-fast);
  }
  .lib-worktype-head:hover .sub-caret { color: var(--ink-soft); }
  .lib-worktype.collapsed .sub-caret { transform: rotate(-90deg); }
  .lib-worktype-head .sub-name {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .lib-worktype-head .sub-count {
    font-size: 9.5px; opacity: 0.5; flex: 0 0 auto; align-self: center;
    font-family: "JetBrains Mono", monospace; font-weight: 400;
  }
  .lib-worktype-body { display: block; }
  .lib-worktype.collapsed .lib-worktype-body { display: none; }

  /* --- Leaf piece rows: the ONLY thing that steps inward ---------- */
  .lib-subgroup-body > .result { padding-left: 36px; }   /* directly under a composer */
  .lib-worktype-body > .result { padding-left: 48px; }   /* under a work type        */
  /* no double rule where a header's border-top meets a row's border-bottom */
  .lib-subgroup-body > .result:last-child,
  .lib-worktype-body > .result:last-child { border-bottom: 0; }

  /* Piece metadata badges shown when a piece is selected */
  .piece-meta-bar {
    display: flex; flex-wrap: wrap; gap: 6px;
    padding: 8px 16px; align-items: center;
    border-bottom: 1px solid var(--rule-soft);
    background: var(--bg-2);
    font-size: 11px; font-family: "JetBrains Mono", monospace;
    color: var(--ink-mute);
  }
  .piece-meta-bar .pm-key { color: var(--ink-quiet); margin-right: 3px; }
  .piece-meta-bar .pm-val { color: var(--ink-soft); font-weight: 500; }
  .piece-meta-bar .pm-pill {
    background: var(--bg); border: 1px solid var(--rule);
    padding: 2px 7px; border-radius: 99px;
    font-size: 10px; letter-spacing: 0.04em;
  }
  .piece-meta-bar .pm-pill.real { border-color: #1d8b59; color: #0f7a4d; font-weight: 700; }
  .piece-meta-bar .pm-pill.synth { color: var(--ink-mute); }
  /* Performer chip: who actually played the recording. Treat as a primary
     piece of info, not telemetry — slightly bolder, separated by a soft pill. */
  .piece-meta-bar .pm-performer {
    display: inline-flex; align-items: center; gap: 5px;
    background: var(--bg); border: 1px solid var(--rule);
    padding: 3px 10px 3px 8px; border-radius: 99px;
    font-family: "Plus Jakarta Sans", inherit;
    font-size: 11.5px; font-weight: 600; color: var(--ink);
    letter-spacing: 0;
    /* Don't break the performer name mid-word — keep it on one line, ellipsize
       if it's absurdly long (full hover title still shows it all). */
    white-space: nowrap; max-width: min(46ch, 100%);
    overflow: hidden; text-overflow: ellipsis;
  }
  .piece-meta-bar .pm-performer > svg { flex: 0 0 auto; }
  /* Inspector toggle on the meta bar — pushes right via margin-left auto.
     Quiet: text link with a thin underline on hover. */
  .piece-meta-bar .pm-toggle {
    margin-left: auto;
    appearance: none; border: 0; background: transparent;
    cursor: pointer; color: var(--ink-mute);
    font: 500 11px/1 inherit; letter-spacing: 0;
    padding: 4px 6px; border-radius: 4px;
    transition: color 160ms var(--easing-fast), background 160ms var(--easing-fast);
  }
  .piece-meta-bar .pm-toggle:hover {
    color: var(--ink); background: var(--rule-soft);
  }
  .piece-meta-bar .pm-toggle[aria-pressed="true"] {
    color: var(--ink); font-weight: 600;
  }
  /* Collapse / expand the whole meta bar so the score gets the pixels. */
  .piece-meta-bar.collapsed { padding: 2px 16px; gap: 0; }
  .piece-meta-bar .pm-collapse-btn,
  .piece-meta-bar .pm-mini-toggle {
    appearance: none; border: 0; background: transparent; cursor: pointer;
    color: var(--ink-mute); display: inline-flex; align-items: center; gap: 5px;
    font: 500 11px/1 inherit; letter-spacing: 0; padding: 4px 6px; border-radius: 4px;
    transition: color 160ms var(--easing-fast), background 160ms var(--easing-fast);
  }
  .piece-meta-bar .pm-collapse-btn { margin-left: 4px; }
  .piece-meta-bar .pm-collapse-btn:hover,
  .piece-meta-bar .pm-mini-toggle:hover { color: var(--ink); background: var(--rule-soft); }
  .piece-meta-bar .pm-collapse-btn svg,
  .piece-meta-bar .pm-mini-toggle svg { width: 9px; height: 9px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
  /* Result row: typography-led, full-bleed, with a left-edge accent
     rule that slides in on hover. Selected state = thicker accent +
     bolder title. We keep the heavy black-fill .selected only on
     keyboard-focused or active-press for tactile feedback. */
  .result {
    position: relative;
    padding: 11px 22px 11px 22px;
    border-bottom: 1px solid var(--rule-soft);
    cursor: pointer;
    display: flex; align-items: center; gap: 10px;
    font-size: 13.5px;
    transition: background 200ms var(--easing-fast),
                padding-left 240ms cubic-bezier(0.32, 0.72, 0, 1);
  }
  .result::before {
    /* Left accent rule — invisible by default, animates in on hover/selected */
    content: "";
    position: absolute; left: 0; top: 8px; bottom: 8px;
    width: 2px; border-radius: 2px;
    background: var(--ink);
    transform: scaleY(0);
    transform-origin: 50% 50%;
    transition: transform 240ms cubic-bezier(0.32, 0.72, 0, 1);
  }
  .result:last-child { border-bottom: none; }
  .result:hover {
    background: linear-gradient(90deg,
      rgba(31, 27, 22, 0.04) 0%,
      rgba(31, 27, 22, 0.015) 70%,
      transparent 100%);
  }
  .result:hover::before { transform: scaleY(1); }
  .result.selected::before { transform: scaleY(1); width: 3px; }
  .result.selected {
    background: linear-gradient(90deg,
      rgba(31, 27, 22, 0.06) 0%,
      rgba(31, 27, 22, 0.02) 80%,
      transparent 100%);
  }
  .result.selected .title { font-weight: 700; }
  .result.selected .composer { color: var(--ink-soft); }
  /* F4 — follow-state dot: a small dot that carries the piece's 3-state
     follow-readiness (ready / follows-your-recording / manual). Colour =
     state; the full honest label is in the title attr (a11y + hover). */
  .result .audio-dot {
    width: 5px; height: 5px; border-radius: 50%;
    background: var(--ink-quiet); flex-shrink: 0;
    opacity: 0.9;
    transition: opacity 200ms var(--easing-fast),
                background 200ms var(--easing-fast);
  }
  .result[data-fstate="ready"]  .audio-dot { background: #1d8b59; }
  .result[data-fstate="record"] .audio-dot { background: #d97706; }
  .result[data-fstate="manual"] .audio-dot { background: var(--ink-quiet); opacity: 0.5; }
  /* REAL badge: kill the gradient. Replace with a quiet typographic
     mark — ink dot + the word "real" in small caps mono. Reads as
     a metadata cue, not a sticker. */
  .result .real-badge {
    font-family: "JetBrains Mono", monospace;
    font-size: 9.5px; font-weight: 600;
    background: transparent;
    color: var(--ink-soft);
    padding: 0; border-radius: 0;
    letter-spacing: 0.06em; text-transform: lowercase;
    flex-shrink: 0;
    position: relative;
    padding-left: 8px;
  }
  .result .real-badge::before {
    content: "";
    position: absolute; left: 0; top: 50%; transform: translateY(-50%);
    width: 4px; height: 4px; border-radius: 50%;
    background: #4f7a3a;
  }
  .result .title {
    flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-weight: 500;
    letter-spacing: -0.005em;
    color: var(--ink);
  }
  /* Composer: drop the uppercase + mono + 84px truncate.
     Now: same body font, smaller, ink-mute, normal case.
     Right-aligned in normal sentence form. */
  .result .composer {
    color: var(--ink-mute); font-size: 12px;
    font-family: inherit; font-weight: 400;
    flex-shrink: 0; max-width: 120px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    text-transform: none; letter-spacing: 0;
  }

  /* Personal-tab upload zone: dashed inner core sitting in a quiet
     outer shell. Concentric radii + subtle inner highlight on hover
     simulate physical material. */
  .file-input-wrap {
    position: relative; display: block;
    border: 1px dashed var(--rule);
    border-radius: 16px;
    padding: 22px 18px; text-align: center;
    transition: border-color 200ms var(--easing-fast),
                background 200ms var(--easing-fast),
                box-shadow 280ms cubic-bezier(0.32, 0.72, 0, 1);
    cursor: pointer;
    background: linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 100%);
  }
  .file-input-wrap:hover {
    border-color: var(--ink-mute);
    background: var(--bg);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.4),
                0 4px 14px -8px rgba(31,27,22,0.12);
  }
  .file-input-wrap input[type="file"] {
    position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%;
  }
  .file-input-wrap .label {
    font-size: 13.5px; color: var(--ink); font-weight: 600;
    letter-spacing: -0.005em;
  }
  .file-input-wrap .sub {
    font-size: 11px; color: var(--ink-mute); margin-top: 6px;
    font-family: "JetBrains Mono", monospace;
    text-transform: none; letter-spacing: 0;
  }

  /* ---------- Library: empty + loading + skeleton states ---------- */
  .lib-empty {
    padding: 36px 24px; text-align: left;
    color: var(--ink-mute);
  }
  .lib-empty h3 {
    margin: 0 0 6px; font-size: 16px; font-weight: 600;
    letter-spacing: -0.01em; color: var(--ink);
  }
  .lib-empty p {
    margin: 0 0 14px; font-size: 13px; line-height: 1.55;
    max-width: 36ch; color: var(--ink-soft);
  }
  .lib-empty .quick-picks {
    display: flex; flex-wrap: wrap; gap: 6px;
  }
  .lib-empty .quick-picks button {
    appearance: none; cursor: pointer;
    background: transparent;
    border: 1px solid var(--rule);
    color: var(--ink-soft); font: 500 12px/1 inherit;
    padding: 7px 12px; border-radius: 99px;
    transition: all 180ms var(--easing-fast);
  }
  .lib-empty .quick-picks button:hover {
    border-color: var(--ink); color: var(--ink);
    transform: translateY(-1px);
  }
  .lib-empty .quick-picks button:active { transform: translateY(0); }

  .lib-skeleton { padding: 4px 0 0; }
  .lib-skeleton .row {
    height: 36px;
    margin: 0 22px;
    border-bottom: 1px solid var(--rule-soft);
    position: relative; overflow: hidden;
  }
  .lib-skeleton .row::after {
    content: "";
    position: absolute; left: 0; right: 0; top: 11px;
    height: 14px; width: 60%;
    background: linear-gradient(90deg,
      var(--rule-soft) 0%,
      var(--rule) 35%,
      var(--rule-soft) 70%);
    background-size: 200% 100%;
    border-radius: 4px;
    animation: pt-shimmer 1400ms linear infinite;
  }
  .lib-skeleton .row:nth-child(odd)::after { width: 72%; }
  .lib-skeleton .row:nth-child(3n)::after { width: 48%; }
  @keyframes pt-shimmer {
    0%   { background-position: 100% 0; }
    100% { background-position: -100% 0; }
  }

  /* Subtle paper grain on the drawer surface — Editorial Luxury vibe.
     Fixed pseudo-element, no scroll repaint cost. Only on the warm
     paper theme; dark theme stays clean glass. */
  html[data-theme="paper"] .drawer::after {
    content: "";
    position: absolute; inset: 0;
    pointer-events: none;
    background-image: radial-gradient(rgba(31,27,22,0.025) 1px, transparent 1px);
    background-size: 3px 3px;
    opacity: 0.5;
    mix-blend-mode: multiply;
    z-index: 0;
  }
  /* Float drawer content above the paper-grain overlay (z-index 0).
     CRITICAL: do NOT override .drawer-resize's position: absolute — only
     bump its z-index. Was 'position: relative; z-index: 1' which broke
     the right-edge handle's absolute positioning and made the drawer
     appear unresizable. */
  .drawer header,
  .drawer .lib-tabs,
  .drawer .body { position: relative; z-index: 1; }
  .drawer-resize { z-index: 95; }

  @media (prefers-reduced-motion: reduce) {
    .drawer, .drawer-bd,
    .lib-group-head .caret,
    .lib-group-body,
    .result, .result::before,
    .lib-skeleton .row::after,
    .audio-hud, .insp-tags input, .lib-shelf-row, .shelf-card {
      transition-duration: 1ms !important;
      animation-duration: 1ms !important;
    }
  }

  /* ============================================================
     SMART LIBRARY — forScore-inspired shelves + inspector + HUD
     Built on per-piece localStorage (pt-meta).
     No rating system (per Keymaster: not the point of the app).
     ============================================================ */

  /* --- SHELVES (top of Main pane) ---------------------------------
     Compact bands of horizontally-scrolling cards.
     Lets the user jump back to the last piece they opened in 1 tap. */
  /* "Show only with a recording" filter pill — sits at the very top of a
     grouped library list. Subtle until armed, then a filled accent pill. */
  .lib-rec-bar { padding: 4px 22px 12px; }
  .lib-rec-toggle {
    appearance: none; cursor: pointer;
    display: inline-flex; align-items: center; gap: 7px;
    border: 1px solid var(--rule); border-radius: 99px;
    background: var(--bg-2); color: var(--ink-soft);
    padding: 6px 12px; font: 500 11.5px/1 inherit; letter-spacing: 0;
    transition: background 160ms var(--easing-fast), color 160ms var(--easing-fast), border-color 160ms var(--easing-fast);
  }
  .lib-rec-toggle:hover { color: var(--ink); border-color: var(--ink-mute); }
  .lib-rec-toggle .rb-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--ink-quiet); flex-shrink: 0;
  }
  .lib-rec-toggle .rb-count {
    font-family: "JetBrains Mono", monospace; font-size: 10px;
    opacity: 0.7; padding-left: 2px;
  }
  .lib-rec-toggle.on {
    background: var(--ink); color: var(--bg); border-color: var(--ink);
  }
  .lib-rec-toggle.on:hover { background: var(--ink); color: var(--bg); }
  .lib-rec-toggle.on .rb-dot { background: #4f9d6b; }
  .lib-rec-toggle.on .rb-count { opacity: 0.85; }

  .lib-shelves { margin: 6px 0 18px; padding: 0; }
  .lib-shelf { margin-bottom: 16px; }
  .lib-shelf-head {
    display: flex; align-items: baseline; gap: 8px;
    padding: 0 22px 7px;
    font-size: 11px; font-weight: 600; color: var(--ink-soft);
  }
  .lib-shelf-head .shelf-name { color: var(--ink); }
  .lib-shelf-head .shelf-count {
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; color: var(--ink-mute); font-weight: 400;
  }
  .lib-shelf-row {
    display: flex; gap: 9px;
    overflow-x: auto;
    padding: 2px 22px 9px;
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
  }
  /* Trailing flex spacer: guarantees breathing room on the right edge
     even when the row is scrolled all the way (Chromium drops the
     padding-inline-end on flex scroll containers). */
  .lib-shelf-row::after {
    content: ""; flex: 0 0 14px; align-self: stretch;
  }
  .lib-shelf-row::-webkit-scrollbar { height: 6px; }
  .lib-shelf-row::-webkit-scrollbar-thumb {
    background: var(--rule); border-radius: 99px;
  }
  .shelf-card {
    flex: 0 0 auto; min-width: 168px; max-width: 240px;
    padding: 10px 12px;
    background: var(--bg-2); border: 1px solid var(--rule);
    border-radius: 10px; cursor: pointer;
    text-align: left;
    scroll-snap-align: start;
    transition: transform 240ms cubic-bezier(0.32, 0.72, 0, 1),
                border-color 200ms var(--easing-fast),
                background 200ms var(--easing-fast);
  }
  .shelf-card:hover {
    transform: translateY(-2px);
    border-color: var(--ink-mute);
    background: var(--bg);
  }
  .shelf-card .sc-title {
    display: block; font-size: 12.5px; font-weight: 600; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    letter-spacing: -0.005em;
  }
  .shelf-card .sc-meta {
    display: block; margin-top: 4px;
    font-size: 11px; color: var(--ink-mute);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* --- INSPECTOR (per-piece detail under the meta bar) -----------
     Collapsible panel revealed when a piece is selected.
     Lists every format, every performance, source/license, and
     editable tags + notes that persist in pt-meta. */
  .piece-inspector {
    border-bottom: 1px solid var(--rule);
    background: linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 100%);
    overflow: hidden;
    transition: max-height 360ms cubic-bezier(0.32, 0.72, 0, 1),
                border-color 280ms var(--easing-fast);
  }
  .piece-inspector[hidden] { display: block !important; max-height: 0; border-bottom-color: transparent; }
  .piece-inspector:not([hidden]) { max-height: 380px; }
  .insp-grid {
    display: grid; grid-template-columns: 1.2fr 0.8fr;
    gap: 0;
    padding: 14px 22px 16px;
  }
  .insp-col { min-width: 0; }
  .insp-col + .insp-col {
    padding-left: 18px; margin-left: 18px;
    border-left: 1px solid var(--rule-soft);
  }
  .insp-label {
    font-size: 10.5px; font-weight: 500;
    color: var(--ink-mute); margin: 0 0 6px;
  }
  .insp-formats { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 12px; }
  .insp-fmt {
    font-family: "JetBrains Mono", monospace;
    font-size: 11px; font-weight: 500;
    padding: 3px 8px; border-radius: 99px;
    background: var(--bg); border: 1px solid var(--rule);
    color: var(--ink-soft);
  }
  .insp-fmt.primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
  .insp-fmt-btn { cursor: pointer; transition: transform 140ms cubic-bezier(0.23,1,0.32,1), background 140ms ease, border-color 140ms ease; }
  .insp-fmt-btn:hover { border-color: var(--ink); }
  .insp-fmt-btn:active { transform: scale(0.96); }
  .insp-fmt-btn:disabled { opacity: 0.5; cursor: progress; }
  /* OMR converter row (PDF → smart MusicXML, audit feature). */
  .insp-omr { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 2px; }
  .insp-omr-go { background: var(--accent, #c2410c); color: white; border-color: var(--accent, #c2410c); }
  .insp-omr-go:hover { background: color-mix(in srgb, var(--accent, #c2410c) 88%, black); border-color: color-mix(in srgb, var(--accent, #c2410c) 88%, black); }
  .insp-omr-go.working { position: relative; padding-right: 22px; }
  .insp-omr-go.working::after {
    content: ""; position: absolute; right: 7px; top: 50%; transform: translateY(-50%);
    width: 9px; height: 9px; border-radius: 50%;
    border: 1.5px solid currentColor; border-top-color: transparent;
    animation: src-spin 0.9s linear infinite;
  }
  .insp-omr-status { font-size: 11px; line-height: 1.4; color: var(--ink-mute); flex: 1; min-width: 140px; }
  .insp-omr-status.working { color: var(--ink-soft); }
  .insp-omr-status.ok { color: #15803d; }
  .insp-omr-status.bad { color: #b91c1c; }
  .insp-perfs {
    display: flex; flex-direction: column; gap: 6px;
    max-height: 110px; overflow-y: auto; margin-bottom: 12px;
  }
  .insp-perf {
    display: flex; justify-content: space-between; gap: 8px;
    font-size: 12px; color: var(--ink-soft);
  }
  .insp-perf .perf-name { font-weight: 500; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    flex: 1; min-width: 0; }
  .insp-perf .perf-real {
    font-family: "JetBrains Mono", monospace; font-size: 10px;
    color: #4f7a3a; padding: 1px 6px;
    border: 1px solid #4f7a3a; border-radius: 99px; flex-shrink: 0;
  }
  .insp-source {
    display: grid; grid-template-columns: auto 1fr; gap: 4px 10px;
    font-size: 11.5px; line-height: 1.5; margin: 0;
  }
  .insp-source dt { color: var(--ink-mute); font-weight: 500; }
  .insp-source dd { margin: 0; color: var(--ink); font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The title can be long — let it wrap instead of getting clipped. */
  .insp-source dd.t { white-space: normal; overflow: visible; overflow-wrap: anywhere; }

  /* Properties dash — enriched fields from the About research (/api/piece-context). */
  .insp-props { margin-top: 2px; }
  .insp-props .insp-props-dl { margin-bottom: 7px; }
  .insp-props .insp-props-dl dd { white-space: normal; overflow: visible; overflow-wrap: anywhere; }

  .insp-tags input,
  .insp-notes textarea {
    width: 100%; appearance: none;
    background: var(--bg); border: 1px solid var(--rule);
    border-radius: 6px; color: var(--ink);
    font: inherit; font-size: 12px;
    padding: 7px 9px;
    transition: border-color 200ms var(--easing-fast);
  }
  .insp-tags input:focus,
  .insp-notes textarea:focus { outline: none; border-color: var(--ink); }
  .insp-notes textarea { min-height: 50px; resize: vertical; line-height: 1.45; }
  .insp-tags input { margin-bottom: 8px; }
  .insp-stats {
    display: flex; gap: 14px; margin-top: 10px;
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; color: var(--ink-mute);
  }
  .insp-stats b { color: var(--ink-soft); font-weight: 600; }

  /* --- AUDIO HUD (live during playback) --------------------------
     Floats just under the toolbar. Reads WAV header on first play
     to surface sample rate + bitrate. Live runtime data per
     DESIGN.md: distinguish config from selected from live runtime. */
  .audio-hud {
    position: fixed; left: 50%; top: 76px;
    transform: translateX(-50%) translateY(-12px);
    background: rgba(20, 16, 10, 0.92);
    color: #f6f1e7;
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 14px;
    padding: 10px 16px 10px 14px;
    box-shadow: 0 12px 36px -12px rgba(0,0,0,0.45),
                inset 0 1px 0 rgba(255,255,255,0.06);
    backdrop-filter: blur(14px);
    display: flex; align-items: center; gap: 14px;
    font-size: 12px;
    opacity: 0; pointer-events: none;
    z-index: 70;
    transition: opacity 280ms var(--easing-fast),
                transform 360ms cubic-bezier(0.32, 0.72, 0, 1);
    max-width: min(720px, calc(100vw - 32px));
  }
  .audio-hud.show {
    opacity: 1; pointer-events: auto;
    transform: translateX(-50%) translateY(0);
  }
  .audio-hud .ah-pulse {
    width: 8px; height: 8px; border-radius: 50%;
    background: #4ade80; flex-shrink: 0;
    box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.6);
    animation: ah-pulse 1.6s ease-out infinite;
  }
  @keyframes ah-pulse {
    0%   { box-shadow: 0 0 0 0  rgba(74, 222, 128, 0.5); }
    100% { box-shadow: 0 0 0 12px rgba(74, 222, 128, 0); }
  }
  .audio-hud .ah-block { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .audio-hud .ah-key {
    font-size: 9.5px; font-weight: 500;
    color: rgba(246,241,231,0.55); letter-spacing: 0.04em;
    text-transform: uppercase;
  }
  .audio-hud .ah-val {
    font-family: "JetBrains Mono", monospace;
    font-size: 12px; color: #fdfbf7; font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .audio-hud .ah-val.performer {
    font-family: "Plus Jakarta Sans", inherit; font-weight: 600;
    white-space: nowrap; max-width: 22ch; overflow: hidden; text-overflow: ellipsis;
  }
  /* Only the source block may shrink — its provenance string is long; the
     performer name + time keep their natural width (no "Kimik...", "0:0..."). */
  .audio-hud .ah-block { min-width: 0; flex-shrink: 0; }
  .audio-hud .ah-block-src { flex: 1 1 auto; }
  .audio-hud .ah-block-src .ah-key {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .audio-hud .ah-sep {
    width: 1px; height: 28px; background: rgba(255,255,255,0.08); flex-shrink: 0;
  }
  .audio-hud .ah-time { font-feature-settings: "tnum"; }
  .audio-hud .ah-close {
    appearance: none; background: transparent; border: 0;
    color: rgba(246,241,231,0.6); cursor: pointer;
    width: 22px; height: 22px; border-radius: 99px;
    display: inline-flex; align-items: center; justify-content: center;
    transition: color 160ms, background 160ms;
  }
  .audio-hud .ah-close:hover {
    color: #fdfbf7; background: rgba(255,255,255,0.08);
  }
  @media (max-width: 720px) {
    .audio-hud {
      top: auto; bottom: 92px;
      left: 12px; right: 12px;
      transform: translateY(12px);
      max-width: none; padding: 9px 12px;
      gap: 10px; flex-wrap: wrap;
    }
    .audio-hud.show { transform: translateY(0); }
    .audio-hud .ah-sep { display: none; }
  }
  @media (prefers-reduced-motion: reduce) {
    .audio-hud .ah-pulse { animation: none; }
  }

  /* ============================================================
     TRANSPORT
     ============================================================ */
  /* Topbar Play CTA — primary action lives at the top, persistent + always reachable.
     White-space:nowrap keeps the longer "Play recording" label on one line. */
  .play-cta-top {
    appearance: none; cursor: pointer; white-space: nowrap;
    display: inline-flex; align-items: center; gap: 7px;
    padding: 5px 10px; min-height: 28px;
    background: #6e7d61; color: white;
    border: 1px solid #35412f; border-radius: 99px;
    font-family: inherit; font-size: 11px; font-weight: 500;
    letter-spacing: 0; text-transform: none;
    transition: background 180ms var(--easing-fast), transform 80ms var(--easing-fast);
  }
  .play-cta-top:hover:not(:disabled) { background: #627156; }
  .play-cta-top:active:not(:disabled) { transform: translateY(1px); }
  .play-cta-top:disabled { opacity: 0.28; cursor: not-allowed; }
  .play-cta-top.listening {
    background: #6e7d61; border-color: #35412f; color: white;
    box-shadow: 0 0 0 0 color-mix(in srgb, #6e7d61 35%, transparent);
    animation: breathe 2s ease-in-out infinite;
  }
  @keyframes breathe {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, #6e7d61 35%, transparent); }
    50%      { box-shadow: 0 0 0 6px color-mix(in srgb, #6e7d61 0%, transparent); }
  }

  /* LAB mode toggle (testing harness — clearly distinguished from production use) */
  /* Source toggle: mic | demo, segmented control next to PLAY */
  .source-toggle {
    display: inline-flex; align-items: center;
    background: var(--bg-2); border: 1px solid var(--rule);
    border-radius: 99px; padding: 2px;
    margin-right: 8px;
    overflow: hidden;
  }
  .source-toggle .src-btn {
    appearance: none; border: none;
    background: transparent; color: var(--ink-mute);
    font: inherit; font-size: 11px; font-weight: 500;
    padding: 5px 10px; border-radius: 99px;
    display: inline-flex; align-items: center; gap: 5px;
    cursor: pointer;
    transition: all 160ms var(--easing-fast);
  }
  .source-toggle .src-btn:hover:not(:disabled) { color: var(--ink); }
  .source-toggle .src-btn:disabled { opacity: 0.35; cursor: not-allowed; }
  .source-toggle .src-btn svg { width: 12px; height: 12px; }
  .source-toggle .src-btn.active {
    background: #6e7d61; color: white;
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
  }
  .source-toggle .src-btn[data-src="demo"].active {
    background: #6e7d61; color: white;
  }
  /* Loading state on Demo pill: dim the icon and show a small inline spinner
     so the user sees the recording is being fetched (audit H2). Pairs with
     the Play button's "Loading…" label and disabled state in applyLabMode(). */
  .source-toggle .src-btn.loading {
    position: relative;
    color: color-mix(in srgb, currentColor 70%, transparent);
  }
  .source-toggle .src-btn.loading::after {
    content: ""; position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    width: 10px; height: 10px; border-radius: 50%;
    border: 1.5px solid currentColor; border-top-color: transparent;
    animation: src-spin 0.9s linear infinite;
  }
  @keyframes src-spin { to { transform: translateY(-50%) rotate(360deg); } }
  /* Play button disabled+loading: subtle pulse so it's obviously waiting,
     not broken (audit C2 / Pattern #1). */
  #play-btn.loading { opacity: 0.55; cursor: progress; }
  #play-btn.loading #play-label { font-style: italic; }
  /* Mic-silent watchdog: when no audio energy arrives within 3s of Listen, the
     play button glows amber so the user knows something is wrong (paired with
     a concrete error message in #ins-status). Cleared as soon as audio arrives. */
  #play-btn.mic-warn { box-shadow: 0 0 0 2px #ef9d2a, 0 0 18px rgba(239,157,42,0.55); animation: mic-warn-pulse 1.4s ease-in-out infinite; }
  @keyframes mic-warn-pulse { 0%,100% { box-shadow: 0 0 0 2px #ef9d2a, 0 0 12px rgba(239,157,42,0.45); } 50% { box-shadow: 0 0 0 2px #ef9d2a, 0 0 22px rgba(239,157,42,0.70); } }
  @media (prefers-reduced-motion: reduce) {
    .source-toggle .src-btn.loading::after { animation: none; opacity: 0.6; }
  }

  .lab-toggle {
    width: auto !important; padding: 0 8px;
    gap: 5px;
    border: 1px solid var(--rule) !important;
    color: var(--ink-mute) !important;
  }
  .lab-toggle .lab-label {
    font-family: "JetBrains Mono", monospace;
    font-size: 9px; font-weight: 600;
    letter-spacing: 0.16em; text-transform: uppercase;
  }
  .lab-toggle svg { width: 13px; height: 13px; }
  .lab-toggle.active {
    background: var(--accent) !important; color: white !important;
    border-color: var(--accent) !important;
  }
  /* Demo mode used to wrap the whole app in a green "testing harness" banner +
     border + bottom strip. Removed — demo is a normal mode, not a lab. The
     active state is shown by the green "demo" pill in the source toggle. */
  body.lab-mode .topbar { margin-top: 0; }


  /* Audio level meter, slim bar inside topbar */
  .meter-top {
    width: 56px; height: 3px;
    background: var(--rule); border-radius: 99px; overflow: hidden;
  }
  .meter-top > div {
    height: 100%; background: var(--accent); width: 0%;
    transition: width 80ms var(--easing-fast);
  }

  /* Live alignment Inspector — floating right, dev/test panel + user trust signal */
  .inspector {
    position: fixed; right: 18px; top: 88px; z-index: 40;
    width: 220px;
    background: var(--bg);
    border: 1px solid var(--rule); border-radius: 12px;
    padding: 14px 16px;
    box-shadow: 0 8px 24px color-mix(in srgb, var(--ink) 8%, transparent);
    font-family: "JetBrains Mono", monospace;
    font-size: 11px;
  }
  .inspector.hidden { display: none; }
  .ins-close {
    position: absolute; top: 6px; right: 6px;
    width: 20px; height: 20px; border-radius: 99px;
    background: transparent; border: none;
    color: var(--ink-mute); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
  }
  .ins-close:hover { background: var(--bg-2); color: var(--ink); }
  /* Restore button when hidden */
  #show-inspector {
    position: fixed; right: 14px; top: 84px; z-index: 39;
    background: var(--bg); border: 1px solid var(--rule);
    border-radius: 99px; padding: 6px 10px;
    cursor: pointer; color: var(--ink-soft);
    font-family: "JetBrains Mono", monospace;
    font-size: 9px; font-weight: 600;
    letter-spacing: 0.12em; text-transform: uppercase;
    display: none;
  }
  #show-inspector.visible { display: block; }
  .ins-row {
    display: flex; justify-content: space-between; align-items: baseline;
    padding: 5px 0;
    border-bottom: 1px solid color-mix(in srgb, var(--rule) 50%, transparent);
  }
  .ins-row:last-of-type { border-bottom: none; }
  .ins-k { color: var(--ink-mute); text-transform: uppercase; letter-spacing: 0.08em; font-size: 9.5px; }
  .ins-row b { color: var(--ink); font-weight: 600; font-size: 12.5px; }
  .ins-status {
    margin-top: 10px; padding-top: 10px;
    border-top: 1px solid var(--rule);
    color: var(--ink-mute); font-size: 10.5px; line-height: 1.4;
    font-family: var(--font-sans, system-ui);
  }
  .inspector.live { border-color: var(--accent); }
  .inspector.live .ins-status { color: var(--accent); }

  /* ============================================================
     TIMED-MODE BANNER -- persistent amber bar when the clock-driven
     fallback is active. Tells the user the app is NOT listening, so
     a wrong page-turn doesn't feel like a tracker bug.
     ============================================================ */
  .timed-mode-banner {
    position: sticky; top: 0; z-index: 1000;
    display: flex; align-items: center; gap: 10px;
    padding: 8px 14px;
    background: #fff4d4;
    border-bottom: 1px solid #d4a800;
    color: #2a2200;
    font-family: var(--font-sans, system-ui);
    font-size: 13px; line-height: 1.3;
  }
  .timed-mode-banner[hidden] { display: none; }
  .timed-mode-banner .tmb-icon { font-size: 16px; }
  .timed-mode-banner .tmb-text { flex: 1; }
  .timed-mode-banner .tmb-stop {
    background: #2a2200; color: #fff4d4; border: 0;
    padding: 4px 10px; border-radius: 4px; cursor: pointer;
    font-size: 12px; font-weight: 600;
  }
  .timed-mode-banner .tmb-stop:hover { background: #443500; }

  /* ============================================================
     HELP TIP
     ============================================================ */
  /* Help modal — full overlay explainer */
  /* DEAD: the old dev-facing "First-run wizard" (#wizard / .wiz-*). The init JS
     was removed; this CSS + its HTML are inert. `.wizard` sets display:flex so a
     plain `hidden` attribute (UA `display:none`) loses on specificity — pin it
     off explicitly so the markup, if still present, never shows. (TODO: delete
     the #wizard HTML block + all .wiz-* CSS in a cleanup pass.) */
  .wizard, .wizard[hidden] { display: none !important; }
  .wizard {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
    z-index: 11000;
    display: flex; align-items: center; justify-content: center;
    padding: 16px;
    animation: wiz-fade 320ms var(--easing);
  }
  @keyframes wiz-fade { from { opacity: 0; } to { opacity: 1; } }
  .wiz-card {
    position: relative;
    width: 100%; max-width: 520px;
    background: var(--bg);
    border: 1px solid var(--rule);
    border-radius: 18px;
    padding: 36px 40px 30px;
    box-shadow: var(--shadow);
    color: var(--ink);
  }
  .wiz-step h2 {
    margin: 8px 0 14px; font-size: 22px; font-weight: 600; letter-spacing: -0.01em;
    color: var(--ink);
  }
  .wiz-step p {
    margin: 0 0 22px; color: var(--ink-soft); font-size: 14.5px; line-height: 1.55;
  }
  .wiz-num {
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; letter-spacing: 0.16em;
    color: var(--ink-mute); text-transform: uppercase;
  }
  .wiz-illus {
    display: flex; align-items: center; justify-content: center; gap: 14px;
    margin: 18px 0 26px;
    padding: 24px;
    border-radius: 12px;
    background: var(--bg-2);
    border: 1px solid var(--rule);
    min-height: 90px;
    font-size: 22px;
  }
  .wiz-arrow { color: var(--ink-mute); font-size: 22px; line-height: 1; }
  .wiz-svg {
    width: 100%; max-width: 360px; height: auto;
    color: var(--ink-soft);
    display: block;
  }
  .wiz-svg-label, .wiz-svg-label-mono, .wiz-svg-label-sm, .wiz-svg-label-faded {
    font-family: "JetBrains Mono", monospace;
    fill: currentColor;
  }
  .wiz-svg-label { font-size: 9px; opacity: 0.7; }
  .wiz-svg-label-sm { font-size: 8px; opacity: 0.5; }
  .wiz-svg-label-mono { font-size: 9px; opacity: 0.85; }
  .wiz-svg-label-faded { font-size: 8.5px; opacity: 0.45; }
  .wiz-svg-arrow { color: var(--ink-mute); }
  .wiz-pill {
    display: inline-block;
    padding: 1px 7px;
    border: 1px solid rgba(91,141,239,0.5);
    color: var(--accent);
    border-radius: 999px;
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px;
    font-weight: 500;
  }
  .wiz-card kbd {
    display: inline-block;
    padding: 1px 6px;
    background: var(--bg-3);
    border: 1px solid var(--rule);
    border-bottom-width: 2px;
    border-radius: 4px;
    font-family: "JetBrains Mono", monospace;
    font-size: 10.5px;
    color: var(--ink);
  }
  .wiz-stage {
    display: inline-flex; flex-direction: column; align-items: center; gap: 4px;
    min-width: 80px;
    padding: 10px 14px;
    background: var(--bg-3);
    border: 1px solid var(--rule);
    border-radius: 10px;
  }
  .wiz-stage b { font-size: 13px; font-weight: 600; color: var(--ink); }
  .wiz-stage small { font-size: 10.5px; color: var(--ink-mute); font-family: "JetBrains Mono", monospace; }
  .wiz-mock-lib { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-family: "JetBrains Mono", monospace; color: var(--ink-soft); }
  .wiz-mock-row { display: flex; align-items: center; gap: 8px; }
  .wiz-blue-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 8px var(--accent); }
  .wiz-mock-page { position: relative; width: 180px; height: 100px; background: #f7f7f5; border-radius: 6px; overflow: hidden; }
  .wiz-mock-staff { position: absolute; left: 12px; right: 12px; height: 14px; background: repeating-linear-gradient(0deg, transparent 0 2px, #555 2px 3px, transparent 3px 5px); }
  .wiz-mock-staff:nth-child(1) { top: 18px; }
  .wiz-mock-staff:nth-child(2) { top: 60px; }
  .wiz-mock-box { position: absolute; left: 38px; top: 12px; width: 56px; height: 24px; border: 1.5px solid #5b8def; background: rgba(91,141,239,0.18); border-radius: 3px; animation: wiz-box-glide 2.6s ease-in-out infinite; }
  @keyframes wiz-box-glide {
    0% { left: 38px; top: 12px; }
    50% { left: 96px; top: 12px; }
    51% { left: 38px; top: 54px; }
    100% { left: 124px; top: 54px; }
  }
  .wiz-btn {
    appearance: none; -webkit-appearance: none;
    border: 1px solid var(--rule);
    background: var(--bg-2); color: var(--ink);
    padding: 10px 22px;
    border-radius: 999px;
    font-family: inherit; font-size: 13px; font-weight: 500;
    cursor: pointer;
    transition: all 200ms var(--easing);
    margin-right: 8px;
  }
  .wiz-btn.primary {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
  }
  .wiz-btn.primary:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(91,141,239,0.3); }
  .wiz-btn.ghost:hover { background: var(--bg-3); }
  .wiz-skip {
    position: absolute; top: 14px; right: 18px;
    appearance: none; -webkit-appearance: none;
    background: transparent; border: 0; padding: 6px 10px;
    color: var(--ink-mute); font-family: inherit; font-size: 11px;
    cursor: pointer; letter-spacing: 0.1em; text-transform: uppercase;
  }
  .wiz-skip:hover { color: var(--ink-soft); }
  .wiz-close {
    position: absolute; top: 10px; right: 10px;
    appearance: none; -webkit-appearance: none;
    background: var(--bg-2); border: 1px solid var(--rule);
    width: 32px; height: 32px; padding: 0;
    border-radius: 50%; color: var(--ink-mute);
    cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
    transition: all 200ms var(--easing);
  }
  .wiz-close:hover { background: var(--bg-3); color: var(--ink); }

  .helptip {
    position: fixed; inset: 0; z-index: 200;
    display: flex; align-items: center; justify-content: center;
    background: rgba(15, 17, 22, 0.46);
    opacity: 0; pointer-events: none;
    transition: opacity 200ms var(--easing);
  }
  .helptip.show { opacity: 1; pointer-events: auto; }
  .helptip > .panel {
    width: min(720px, calc(100vw - 32px));
    max-height: calc(100vh - 80px);
    overflow: auto;
    background: var(--bg); border: 1px solid var(--rule);
    border-radius: var(--radius-md); box-shadow: var(--shadow);
    padding: 22px 26px 24px;
    transform: translateY(8px) scale(0.985);
    transition: transform 240ms var(--easing);
  }
  .helptip.show > .panel { transform: translateY(0) scale(1); }
  .helptip .heading {
    font-weight: 600; font-size: 16px; color: var(--ink);
    display: flex; justify-content: space-between; align-items: center;
    padding-bottom: 14px; margin-bottom: 16px;
    border-bottom: 1px solid var(--rule);
  }
  .helptip h4 {
    font-size: 11px; font-weight: 700; letter-spacing: 0.12em;
    color: var(--ink-mute); text-transform: uppercase;
    margin: 14px 0 6px;
  }
  .helptip h4:first-of-type { margin-top: 0; }
  .helptip p { margin: 0 0 8px; font-size: 13px; color: var(--ink-soft); line-height: 1.55; }
  .helptip ul, .helptip ol { margin: 0 0 4px; padding-left: 20px; font-size: 13px; color: var(--ink-soft); line-height: 1.6; }
  .helptip li { margin-bottom: 3px; }
  .helptip li b { color: var(--ink); font-weight: 600; }
  .helptip kbd {
    font-family: "JetBrains Mono", monospace;
    background: var(--bg-2); border: 1px solid var(--rule);
    padding: 1px 6px; border-radius: 4px; font-size: 11px;
    color: var(--ink);
  }
  .helptip .mode-grid {
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
    margin: 4px 0 6px;
  }
  .helptip .mode-card {
    border: 1px solid var(--rule); border-radius: 8px; padding: 10px 12px;
    background: var(--bg-2);
  }
  .helptip .mode-card.lab { border-color: var(--accent); }
  .helptip .mode-card .tag {
    display: inline-block; font-size: 10px; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase;
    background: var(--ink); color: var(--bg);
    padding: 2px 7px; border-radius: 3px; margin-bottom: 6px;
  }
  .helptip .mode-card.lab .tag { background: var(--accent); color: white; }
  .helptip .mode-card p { font-size: 12px; margin: 0; }
  .helptip .close-tip {
    appearance: none; border: none; background: transparent;
    cursor: pointer; color: var(--ink-mute); padding: 4px;
    line-height: 1; border-radius: 4px;
  }
  .helptip .close-tip:hover { color: var(--ink); background: var(--bg-2); }
  @media (max-width: 540px) {
    .helptip .mode-grid { grid-template-columns: 1fr; }
  }

  /* ---- Recording studio dock ---- */
  .studio-overlay {
    position: fixed; right: 14px; bottom: 76px; z-index: 64;
    display: block; background: transparent;
    opacity: 0; pointer-events: none;
    transition: opacity 180ms var(--easing), transform 180ms var(--easing);
    transform: translateY(10px);
  }
  .studio-overlay.show { opacity: 1; transform: translateY(0); }
  .studio-overlay > .panel {
    width: min(460px, calc(100vw - 28px));
    max-height: min(660px, calc(100vh - 96px)); overflow: auto;
    background: var(--bg); border: 1px solid var(--rule);
    border-radius: var(--radius-md); box-shadow: var(--shadow);
    padding: 18px 18px 20px;
  }
  /* Only the OPEN studio panel takes pointer events. Without the .show gate the
     panel kept pointer-events:auto while the overlay was closed (opacity:0) — an
     invisible click-trap over the lab + score (the "half the site unresponsive"
     bug: a closed overlay swallowing every click in its 460x556 footprint). */
  .studio-overlay.show > .panel { pointer-events: auto; }
  .studio-overlay .heading {
    font-weight: 600; font-size: 16px; color: var(--ink);
    display: flex; justify-content: space-between; align-items: center;
    padding-bottom: 14px; margin-bottom: 4px;
    border-bottom: 1px solid var(--rule);
  }
  .studio-overlay .close-tip {
    appearance: none; border: none; background: transparent;
    cursor: pointer; color: var(--ink-mute); padding: 4px;
    line-height: 1; border-radius: 4px;
  }
  .studio-overlay .close-tip:hover { color: var(--ink); background: var(--bg-2); }
  .studio-piece {
    font-size: 12.5px; color: var(--ink-soft); margin: 12px 0 4px;
    line-height: 1.5;
  }
  .studio-piece b { color: var(--ink); font-weight: 600; }
  .studio-stage {
    display: flex; flex-direction: column; align-items: center; gap: 12px;
    padding: 22px 0 16px; margin: 8px 0;
    border: 1px solid var(--rule); border-radius: 10px; background: var(--bg-2);
  }
  .rec-dot-btn {
    appearance: none; border: 1px solid var(--rule);
    background: var(--bg); color: var(--ink);
    width: 64px; height: 64px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; transition: all 200ms var(--easing);
  }
  .rec-dot-btn:hover:not(:disabled) { border-color: #d23b3b; }
  .rec-dot-btn:disabled { opacity: 0.4; cursor: not-allowed; }
  .rec-dot-btn .dot { width: 24px; height: 24px; border-radius: 50%; background: #d23b3b; transition: all 200ms var(--easing); }
  .rec-dot-btn.recording { border-color: #d23b3b; animation: recPulse 1.4s ease-in-out infinite; }
  .rec-dot-btn.recording .dot { border-radius: 5px; width: 22px; height: 22px; }
  @keyframes recPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(210,59,59,0.0); }
    50% { box-shadow: 0 0 0 8px rgba(210,59,59,0.18); }
  }
  .rec-timer {
    font-family: "JetBrains Mono", monospace; font-size: 22px;
    color: var(--ink); letter-spacing: 0.03em; font-variant-numeric: tabular-nums;
  }
  .rec-hint { font-size: 12px; color: var(--ink-mute); text-align: center; max-width: 320px; line-height: 1.5; }
  .studio-playback { width: 100%; margin: 4px 0 0; }
  .studio-playback audio { width: 100%; display: block; }
  .tracker-trainer {
    border: 1px solid var(--rule);
    border-radius: 8px;
    background: var(--bg-2);
    padding: 10px;
  }
  .train-player-row {
    display: grid;
    grid-template-columns: auto minmax(120px, 1fr) auto;
    gap: 9px;
    align-items: center;
  }
  .train-play {
    appearance: none; border: 1px solid #556047; background: #6e7d61; color: #fff;
    height: 28px; min-width: 104px; border-radius: 14px;
    font-size: 11px; font-weight: 600; cursor: pointer;
  }
  .train-play:hover:not(:disabled) { background: #647258; }
  .train-play:disabled { opacity: 0.45; cursor: not-allowed; }
  .train-seek { width: 100%; accent-color: #6e7d61; }
  .train-viz {
    display: grid; grid-template-columns: minmax(0, 1fr) 86px; gap: 8px;
    margin-top: 9px; align-items: stretch;
  }
  .train-viz canvas {
    width: 100%; height: 48px; display: block;
    border: 1px solid var(--rule); border-radius: 6px; background: var(--bg);
  }
  .train-viz canvas#train-match { height: 48px; }
  .train-calib {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    margin-top: 9px; font-size: 10.5px; color: var(--ink-mute);
  }
  .train-calib button {
    appearance: none; border: 1px solid var(--rule); background: var(--bg);
    color: var(--ink); border-radius: 999px; height: 24px; padding: 0 9px;
    font-size: 10.5px; font-weight: 650; cursor: pointer; white-space: nowrap;
  }
  .train-calib button:hover:not(:disabled) { background: var(--bg-3); }
  .train-calib button:disabled { opacity: .5; cursor: progress; }
  .train-clock {
    font-family: "JetBrains Mono", monospace; font-size: 11px;
    color: var(--ink-mute); white-space: nowrap; font-variant-numeric: tabular-nums;
  }
  .train-actions {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px; margin-top: 10px;
  }
  .train-actions button {
    appearance: none; border: 1px solid var(--rule); background: var(--bg);
    color: var(--ink); border-radius: 7px; height: 30px;
    font-size: 11.5px; font-weight: 600; cursor: pointer;
  }
  .train-actions button:hover:not(:disabled) { background: var(--bg-3); }
  .train-actions button.primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
  .train-actions button:disabled { opacity: 0.42; cursor: not-allowed; }
  .train-layers {
    display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px;
  }
  .train-layer {
    border: 1px solid var(--rule); border-radius: 999px;
    padding: 3px 7px; font-size: 10.5px; color: var(--ink-mute);
    background: var(--bg);
  }
  .train-layer.on { color: var(--ink); border-color: #9cab8f; background: rgba(110,125,97,0.12); }
  .train-event-list {
    margin-top: 10px; display: flex; flex-direction: column; gap: 5px;
    max-height: 108px; overflow: auto;
  }
  .train-event {
    display: grid; grid-template-columns: 44px 1fr auto; gap: 7px; align-items: center;
    font-size: 11px; color: var(--ink-soft);
    border-top: 1px solid rgba(0,0,0,0.05); padding-top: 5px;
  }
  .train-event b { color: var(--ink); font-weight: 600; }
  .train-event button {
    appearance: none; border: 0; background: transparent; color: var(--ink-mute);
    cursor: pointer; font-size: 14px; line-height: 1;
  }
  .train-empty { font-size: 11.5px; color: var(--ink-mute); margin: 9px 2px 0; }
  .studio-actions {
    display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px;
  }
  .studio-actions button, .studio-actions a.btn {
    appearance: none; cursor: pointer; font-size: 12.5px; font-weight: 500;
    padding: 7px 14px; border-radius: 7px; border: 1px solid var(--rule);
    background: var(--bg-2); color: var(--ink); text-decoration: none;
    transition: all 160ms var(--easing);
  }
  .studio-actions button:hover, .studio-actions a.btn:hover { background: var(--bg-3); }
  .studio-actions button.primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
  .studio-actions button.primary:hover { opacity: 0.88; }
  .studio-actions button:disabled { opacity: 0.4; cursor: not-allowed; }
  .studio-actions button.danger:hover { border-color: #d23b3b; color: #d23b3b; background: var(--bg-2); }
  /* Studio is three labelled steps: 1 Record -> 2 Your take -> Published recording (destructive, set apart). */
  .studio-group { margin-top: 16px; }
  .studio-group:first-of-type { margin-top: 8px; }
  .studio-group-h {
    font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-mute); margin: 0 0 8px;
  }
  .studio-group.studio-danger { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--rule); }
  .studio-note { font-size: 11.5px; color: var(--ink-mute); line-height: 1.55; margin: 10px 2px 0; }
  .studio-note b { color: var(--ink-soft); font-weight: 600; }
  .studio-status { font-size: 12px; color: var(--ink-soft); margin-top: 14px; min-height: 16px; line-height: 1.5; }
  .studio-status.err { color: #d23b3b; }
  .studio-status.ok { color: #1f9d55; }
  @media (max-width: 640px) {
    .studio-overlay { left: 10px; right: 10px; bottom: 66px; }
    .studio-overlay > .panel { width: auto; }
    .train-player-row { grid-template-columns: 1fr; }
    .train-actions { grid-template-columns: 1fr; }
  }

  /* ---- Trainer modal (F1 calibrate this piece) ---- */
  .trainer-overlay {
    position: fixed; left: 50%; bottom: 76px; z-index: 62;
    display: block; background: transparent;
    opacity: 0; pointer-events: none;
    transform: translate(-50%, 10px);
    transition: opacity 180ms var(--easing), transform 180ms var(--easing);
  }
  .trainer-overlay.show { opacity: 1; transform: translate(-50%, 0); }
  .trainer-overlay > .panel {
    width: min(880px, calc(100vw - 28px));
    max-height: min(520px, calc(100vh - 98px));
    overflow: auto;
    background: var(--bg); border: 1px solid var(--rule);
    border-radius: var(--radius-md); box-shadow: var(--shadow);
    padding: 16px 18px 18px;
  }
  /* Same invisible-click-trap fix as the studio overlay: the trainer panel is
     interactive only while the overlay is open. */
  .trainer-overlay.show > .panel { pointer-events: auto; }
  .trainer-head {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
    padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--rule);
  }
  .trainer-head h3 { margin: 0; font-size: 16px; font-weight: 650; color: var(--ink); }
  .trainer-head p { margin: 4px 0 0; font-size: 12px; line-height: 1.5; color: var(--ink-mute); }
  .trainer-close {
    appearance: none; border: 0; background: transparent; cursor: pointer;
    color: var(--ink-mute); padding: 4px; border-radius: 4px;
  }
  .trainer-close:hover { color: var(--ink); background: var(--bg-2); }
  .trainer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
  .trainer-step {
    border: 1px solid var(--rule); border-radius: 8px; background: var(--bg-2);
    padding: 12px; min-height: 176px; display: flex; flex-direction: column; gap: 8px;
  }
  .trainer-step .k {
    font: 700 10px/1 "JetBrains Mono", monospace; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--ink-mute);
  }
  .trainer-step h4 { margin: 0; font-size: 13.5px; color: var(--ink); font-weight: 650; }
  .trainer-step p { margin: 0; font-size: 11.7px; line-height: 1.45; color: var(--ink-soft); }
  .trainer-step .trainer-action { margin-top: auto; }
  .trainer-action {
    appearance: none; cursor: pointer; border: 1px solid var(--rule); border-radius: 7px;
    background: var(--bg); color: var(--ink); padding: 7px 10px;
    font: 600 11.5px/1.2 inherit; transition: all 160ms var(--easing-fast);
  }
  .trainer-action:hover:not(:disabled) { border-color: var(--ink); background: var(--bg-3); }
  .trainer-action.primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
  .trainer-action:disabled { opacity: 0.45; cursor: not-allowed; }
  .trainer-status {
    margin-top: 12px; padding: 10px 12px; border: 1px solid var(--rule);
    border-radius: 8px; background: var(--bg-2); color: var(--ink-soft);
    font-size: 12px; line-height: 1.5;
  }
  .trainer-status.ok { border-color: rgba(31,157,85,.35); color: #137343; }
  .trainer-status.err { border-color: rgba(210,59,59,.35); color: #b32626; }
  .trainer-setting {
    margin-top: 8px; display: flex; gap: 7px; align-items: flex-start;
    font-size: 11.5px; line-height: 1.35; color: var(--ink-soft);
  }
  .trainer-setting input { margin-top: 1px; }
  .trainer-setting b { color: var(--ink); font-weight: 650; }
  .trainer-sync-list {
    margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px;
    font: 10.5px/1 "JetBrains Mono", monospace; color: var(--ink-mute);
  }
  .trainer-sync-list span {
    border: 1px solid var(--rule); border-radius: 99px; padding: 4px 7px; background: var(--bg);
  }
  .trainer-map-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: auto; }
  .trainer-map-actions .trainer-action { margin-top: 0; }
  .system-map-bar {
    position: fixed; left: 50%; bottom: 76px; transform: translateX(-50%);
    z-index: 63; display: flex; align-items: center; gap: 6px;
    max-width: calc(100vw - 28px); padding: 8px;
    border: 1px solid rgba(255,255,255,0.12); border-radius: 12px;
    background: rgba(24,27,34,0.94); color: #e7e9ef;
    box-shadow: 0 12px 34px rgba(0,0,0,0.26);
  }
  .system-map-bar[hidden] { display: none; }
  .system-map-bar .map-label {
    font-size: 11px; color: #aeb6c5; padding: 0 5px; white-space: nowrap;
  }
  .system-map-bar button {
    appearance: none; border: 0; border-radius: 8px; cursor: pointer;
    background: transparent; color: #d7dce6; padding: 7px 9px;
    font: 600 11.5px/1 system-ui, sans-serif;
  }
  .system-map-bar button:hover { background: rgba(255,255,255,0.1); color: #fff; }
  .system-map-bar button.primary { background: #eef2f7; color: #171a22; }
  .system-map-bar button.danger:hover { background: rgba(220,60,60,0.86); color: #fff; }
  .system-map-box {
    position: absolute; z-index: 24; border: 2px solid rgba(24,112,168,0.85);
    background: rgba(70,153,206,0.24); border-radius: 4px;
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.32);
    pointer-events: auto; cursor: move; touch-action: none;
  }
  .system-map-box.selected {
    border-color: #0d5f91; background: rgba(55,141,198,0.34);
    box-shadow: 0 0 0 2px rgba(255,255,255,0.82), 0 8px 24px rgba(15,32,46,0.22), inset 0 0 0 1px rgba(255,255,255,0.42);
  }
  .system-map-box::before {
    content: attr(data-label);
    position: absolute; left: 6px; top: 5px;
    font: 700 10px/1 "JetBrains Mono", monospace;
    color: #0f4f78; background: rgba(255,255,255,0.82);
    border: 1px solid rgba(24,112,168,0.35); border-radius: 4px;
    padding: 3px 5px;
  }
  .system-map-box::after {
    content: ""; position: absolute; right: 7px; bottom: 7px;
    width: 20px; height: 20px; pointer-events: none;
    border-right: 3px solid rgba(255,255,255,0.82);
    border-bottom: 3px solid rgba(255,255,255,0.82);
  }
  .system-map-box .system-map-remove {
    position: absolute; right: 6px; top: 5px; width: 22px; height: 22px;
    border: 1px solid rgba(15,79,120,0.32); border-radius: 5px;
    background: rgba(255,255,255,0.88); color: #0f4f78;
    font: 700 15px/18px system-ui, sans-serif; cursor: pointer;
  }
  .system-map-box .system-map-resize {
    position: absolute; right: 0; bottom: 0; width: 34px; height: 34px;
    cursor: nwse-resize;
  }
  .listening-point {
    position: absolute; z-index: 26; width: 18px; height: 18px;
    border-radius: 50%; transform: translate(-50%, -50%);
    background: #f8fafc; border: 2px solid #7c3aed;
    box-shadow: 0 3px 12px rgba(24,20,42,0.24);
    pointer-events: auto; cursor: pointer;
  }
  .listening-point::after {
    content: attr(data-label); position: absolute; left: 17px; top: -8px;
    font: 700 10px/1 "JetBrains Mono", monospace; color: #4c1d95;
    background: rgba(255,255,255,0.9); border: 1px solid rgba(124,58,237,0.26);
    border-radius: 99px; padding: 3px 5px; white-space: nowrap;
  }
  body.listening-point-mode #page-stage { cursor: crosshair; }
  .voice-dot {
    position: absolute; z-index: 23; width: 12px; height: 12px;
    border-radius: 50%; transform: translate(-50%, -50%);
    border: 2px solid rgba(255,255,255,0.92);
    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
    pointer-events: none; opacity: 0; transition: opacity 120ms ease, transform 90ms ease;
  }
  .voice-dot.on { opacity: 0.96; }
  .voice-dot.v1 { background: #2563eb; }
  .voice-dot.v2 { background: #dc2626; }
  .voice-dot.v3 { background: #16a34a; }
  .voice-dot.v4 { background: #d97706; }
  @media (max-width: 760px) {
    .trainer-grid { grid-template-columns: 1fr; }
    .trainer-overlay { left: 10px; right: 10px; bottom: 66px; transform: translateY(10px); }
    .trainer-overlay.show { transform: translateY(0); }
    .trainer-overlay > .panel { width: auto; }
  }

  /* Navbar "Recordings" menu — list/switch the recordings of the current piece. */
  .rec-pick { position: relative; flex-shrink: 0; }
  .rec-pick-menu {
    position: absolute; top: calc(100% + 6px); left: 0; min-width: 240px; max-width: 340px;
    background: var(--bg); border: 1px solid var(--rule); border-radius: 10px;
    box-shadow: 0 12px 34px rgba(0,0,0,0.18); padding: 6px; z-index: 70;
    font-size: 12.5px; max-height: 62vh; overflow-y: auto;
  }
  .rec-pick-menu .rp-head { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-mute); padding: 6px 8px 5px; }
  .rec-pick-item { display: flex; align-items: flex-start; gap: 8px; padding: 7px 8px; border-radius: 7px; cursor: pointer; color: var(--ink); line-height: 1.4; }
  .rec-pick-item:hover, .rec-pick-item.active { background: var(--bg-2); }
  .rec-pick-item .rp-dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 5px; flex: 0 0 auto; background: var(--rule); }
  .rec-pick-item.active .rp-dot { background: var(--ink); }
  .rec-pick-item .rp-body { min-width: 0; flex: 1; }
  .rec-pick-item .rp-name { font-weight: 500; }
  .rec-pick-item .rp-sub { font-size: 11px; color: var(--ink-mute); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .rec-pick-item .rp-badge { font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.05em; color: #15803d; white-space: nowrap; }
  .rec-pick-item .rp-del { color: var(--ink-mute); flex: 0 0 auto; font-size: 15px; line-height: 1; padding: 0 3px; opacity: 0; }
  .rec-pick-item:hover .rp-del { opacity: 1; }
  .rec-pick-item .rp-del:hover { color: #d23b3b; }
  .rec-train-panel {
    border-top: 1px solid var(--rule);
    margin-top: 6px; padding: 8px 7px 6px;
  }
  .rec-train-title {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    font-size: 11px; color: var(--ink-mute); margin-bottom: 7px;
  }
  .rec-train-title b { color: var(--ink); font-size: 12px; font-weight: 600; }
  .rec-train-row {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px;
  }
  .rec-train-row + .rec-train-row { margin-top: 6px; }
  .rec-train-btn {
    appearance: none; border: 1px solid var(--rule); border-radius: 7px;
    background: var(--bg-2); color: var(--ink);
    min-height: 29px; padding: 5px 7px;
    font-size: 11px; font-weight: 600; cursor: pointer;
  }
  .rec-train-btn:hover:not(:disabled) { background: var(--bg-3); border-color: var(--ink-mute); }
  .rec-train-btn.primary { background: #6e7d61; border-color: #556047; color: #fff; }
  .rec-train-btn.primary:hover:not(:disabled) { background: #647258; }
  .rec-train-btn:disabled { opacity: 0.42; cursor: not-allowed; }

  /* ---- Recordings: Spotify / YouTube recommendation embeds ---- */
  .rec-reco {
    border-top: 1px solid var(--rule);
    margin-top: 6px; padding: 8px 7px 6px;
  }
  .rec-reco-head { font-size: 11px; color: var(--ink-mute); margin-bottom: 7px; }
  .rec-reco-btns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
  .rec-reco-btn {
    appearance: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    border: 1px solid var(--rule); border-radius: 7px;
    background: var(--bg-2); color: var(--ink);
    min-height: 31px; padding: 5px 9px;
    font-size: 11px; font-weight: 600; cursor: pointer;
  }
  .rec-reco-btn:hover { background: var(--bg-3); border-color: var(--ink-mute); }
  .rec-reco-btn svg { width: 15px; height: 15px; flex: 0 0 auto; }
  .rec-reco-spotify svg { color: #1db954; }
  .rec-reco-spotify:hover { border-color: #1db954; }
  .rec-reco-youtube svg { color: #ff0000; }
  .rec-reco-youtube:hover { border-color: #ff0000; }
  .rec-reco-embeds { display: flex; flex-direction: column; gap: 8px; }
  .rec-reco-embeds:not(:empty) { margin-top: 8px; }
  .rec-reco-embed { width: 100%; border: 0; border-radius: 8px; display: block; }
  .rec-reco-embed.rec-reco-yt { aspect-ratio: 16 / 9; height: auto; }
  .rec-reco-msg { font-size: 11px; color: var(--ink-mute); padding: 4px 2px; }
  .rec-reco-link {
    display: block; font-size: 11px; color: #6e7d61;
    padding: 4px 2px; text-decoration: none;
  }
  .rec-reco-link:hover { text-decoration: underline; }

  /* ---- Tools popover (transposer submenu + Web Audio metronome) ---- */
  .tools-pick { position: relative; flex-shrink: 0; }
  .tools-menu {
    position: absolute; top: calc(100% + 6px); right: 0; width: 232px;
    background: var(--bg); border: 1px solid var(--rule); border-radius: 10px;
    box-shadow: 0 12px 34px rgba(0,0,0,0.18); padding: 4px; z-index: 70;
    font-size: 12.5px;
  }
  .tools-sec { padding: 8px; }
  .tools-sec + .tools-sec { border-top: 1px solid var(--rule); }
  .tools-head {
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-mute); margin-bottom: 8px;
  }
  .tools-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 8px; margin-bottom: 8px;
  }
  .tools-step {
    appearance: none; width: 34px; height: 34px; flex: 0 0 auto;
    border: 1px solid var(--rule); border-radius: 8px;
    background: var(--bg-2); color: var(--ink);
    font-size: 18px; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
  }
  .tools-step:hover:not(:disabled) { background: var(--bg-3); border-color: var(--ink-mute); }
  .tools-step:disabled { opacity: 0.4; cursor: not-allowed; }
  .tools-val {
    flex: 1; text-align: center; font-weight: 600; font-size: 13px;
    color: var(--ink); font-variant-numeric: tabular-nums;
  }
  .tools-go {
    appearance: none; width: 100%; border: 1px solid var(--rule); border-radius: 7px;
    background: var(--bg-2); color: var(--ink);
    min-height: 32px; padding: 6px 8px; font-size: 11.5px; font-weight: 600;
    cursor: pointer;
  }
  .tools-go:hover:not(:disabled) { background: var(--bg-3); border-color: var(--ink-mute); }
  .tools-go:disabled { opacity: 0.45; cursor: not-allowed; }
  .tools-go.on { background: #6e7d61; border-color: #556047; color: #fff; }
  .tools-go.on:hover { background: #647258; }
  .tools-note { font-size: 10px; color: var(--ink-mute); line-height: 1.45; margin-top: 7px; }
  /* Touch devices: bigger steppers / hit targets. */
  @media (pointer: coarse) {
    .tools-step { width: 44px; height: 44px; }
    .tools-go { min-height: 44px; }
  }

  /* ============================================================
     ANIMATIONS / RESPONSIVE
     ============================================================ */
  @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
  .skeleton {
    background: linear-gradient(90deg, var(--rule-soft) 25%, var(--rule) 50%, var(--rule-soft) 75%);
    background-size: 200% 100%; animation: shimmer 1.4s ease-in-out infinite;
    border-radius: 8px;
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation: none !important; transition: none !important; }
  }

  @media (max-width: 768px) {
    .stage { padding: 10px; }
    .topbar { min-height: 48px; padding: 0 8px; gap: 6px; }
    .top-meta { display: none; }
    .brand sub { display: none; }
    .frame { border-radius: 12px; padding: 4px; }
    .inspector { right: 8px; top: 56px; width: 180px; padding: 10px; font-size: 10px; }
    .inspector .ins-status { font-size: 9.5px; }
  }
  /* iPad layout band: 768–1180px (portrait + landscape).
     Goal: dedicate as many pixels as possible to the actual score, since the
     score reader IS the product on this device. Slim chrome, no overflow. */
  @media (min-width: 769px) and (max-width: 1180px) {
    .stage { padding: 10px clamp(10px, 1.6vw, 18px); }
    .frame { border-radius: 14px; padding: 5px; }
    .source-toggle .src-btn span { display: none; } /* keep icon, drop label */
    .source-toggle .src-btn { padding: 6px 10px; }
    .play-cta-top { padding: 8px 14px; }
    .conf-pill { font-size: 10.5px; }
  }
  /* Landscape phones / very short viewports: kill vertical chrome we don't need. */
  @media (max-height: 480px) {
    .topbar { min-height: 40px; }
    .stage { padding: 6px 10px; }
    .frame { padding: 3px; border-radius: 10px; }
    .inspector { display: none; }
  }

  /* ============================================================
     SMART-MATCH BANNER (after PDF upload)
     ============================================================ */
  .match-banner {
    position: fixed; left: 50%; bottom: 70px; transform: translateX(-50%) translateY(20px);
    width: min(640px, 92vw);
    background: var(--bg); border: 1px solid var(--rule);
    border-radius: 12px; box-shadow: var(--shadow);
    padding: 14px 16px;
    z-index: 100;
    opacity: 0; pointer-events: none;
    transition: opacity 280ms var(--easing), transform 320ms var(--easing);
  }
  .match-banner.show { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
  .match-banner .banner-head {
    display: flex; flex-direction: column; gap: 3px;
    margin-bottom: 10px;
    padding-bottom: 10px; border-bottom: 1px solid var(--rule-soft);
  }
  .match-banner .banner-head strong { font-size: 13px; color: var(--ink); font-weight: 600; }
  .match-banner .banner-head span { font-size: 11.5px; color: var(--ink-mute); }
  .match-banner .banner-body { display: flex; flex-direction: column; gap: 4px; }
  .match-banner .match-pick {
    appearance: none; cursor: pointer; text-align: left;
    padding: 10px 12px;
    background: var(--bg-2); border: 1px solid var(--rule);
    border-radius: 8px;
    display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: center;
    font-family: inherit; color: var(--ink);
    transition: background 160ms var(--easing-fast), border-color 160ms var(--easing-fast);
  }
  .match-banner .match-pick:hover { background: var(--bg); border-color: var(--accent); }
  .match-banner .match-pick .score {
    font-family: "JetBrains Mono", monospace;
    font-size: 11px; font-weight: 600; color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    padding: 3px 8px; border-radius: 99px;
  }
  .match-banner .match-pick .title {
    font-size: 13px; font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .match-banner .match-pick .why {
    font-family: "JetBrains Mono", monospace;
    font-size: 10px; color: var(--ink-mute);
  }
  .match-banner .match-skip {
    appearance: none; cursor: pointer;
    padding: 8px; background: transparent; border: none;
    color: var(--ink-mute); font-size: 11.5px; font-family: inherit;
    text-align: center;
  }
  .match-banner .match-skip:hover { color: var(--ink); }

  /* ============================================================
     LAB VIZ OVERLAY (chroma + waveform during play, devs/testers)
     ============================================================ */
  /* ============================================================
     DEMO LANDING — the marketing front door at `/`. Full-bleed and
     opaque; one job: get the visitor to hit Play. The heavy app
     (Verovio engraving) does NOT load until they do, so arriving at
     the site can never freeze on a slow engrave.
     ============================================================ */
  .demo-landing {
    position: fixed; inset: 0; z-index: 300;
    display: grid; place-items: center;
    background: var(--bg); color: var(--ink);
    padding: 32px 24px; text-align: center;
    opacity: 1; transition: opacity 480ms ease;
  }
  .demo-landing[hidden] { display: none !important; }
  .demo-landing.dismissing { opacity: 0; pointer-events: none; }
  .dl-inner {
    width: min(640px, 100%);
    animation: dl-rise 640ms cubic-bezier(0.23, 1, 0.32, 1) both;
  }
  @keyframes dl-rise { from { transform: translateY(22px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
  .dl-mark { color: var(--ink); margin-bottom: 20px; }
  .dl-mark svg {
    width: 52px; height: 52px; fill: none; stroke: currentColor;
    stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
  }
  .dl-eyebrow {
    font-family: "JetBrains Mono", ui-monospace, monospace;
    font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
    color: var(--accent, #c2410c); margin-bottom: 14px;
  }
  .dl-title {
    margin: 0 0 16px; font-weight: 600;
    font-size: clamp(32px, 5.6vw, 52px); line-height: 1.08;
    letter-spacing: -0.02em; color: var(--ink);
  }
  .dl-sub {
    margin: 0 auto 34px; max-width: 30em;
    font-size: clamp(14.5px, 1.8vw, 17px); line-height: 1.6;
    color: var(--ink-soft, var(--ink-mute));
  }
  .dl-play {
    position: relative; font: inherit; cursor: pointer;
    display: inline-flex; align-items: center; gap: 11px;
    padding: 17px 38px; border-radius: 999px; border: none;
    background: var(--ink); color: var(--bg);
    font-size: 17px; font-weight: 600; letter-spacing: 0.01em;
    transition: transform 200ms cubic-bezier(0.23,1,0.32,1), background 200ms ease;
  }
  .dl-play::after {
    content: ""; position: absolute; inset: 0; border-radius: 999px;
    border: 2px solid var(--ink); pointer-events: none;
    animation: dl-pulse 2200ms ease-out infinite;
  }
  @keyframes dl-pulse {
    0%   { transform: scale(1);    opacity: 0.5; }
    70%  { transform: scale(1.3);  opacity: 0; }
    100% { transform: scale(1.3);  opacity: 0; }
  }
  .dl-play:hover { transform: translateY(-2px); background: color-mix(in srgb, var(--ink) 86%, var(--accent)); }
  .dl-play:active { transform: translateY(0) scale(0.98); }
  .dl-play .dl-tri { font-size: 13px; line-height: 1; }
  .dl-play.loading { pointer-events: none; }
  .dl-play.loading::after { animation: none; opacity: 0; }
  .dl-play.loading .dl-tri {
    box-sizing: border-box; display: inline-block;
    width: 15px; height: 15px; border-radius: 50%;
    border: 2px solid var(--bg); border-top-color: transparent;
    animation: dl-spin 720ms linear infinite;
  }
  @keyframes dl-spin { to { transform: rotate(360deg); } }
  .dl-foot { margin: 22px 0 0; font-size: 12.5px; color: var(--ink-mute); }
  /* Web-demo notice + iPad-app waitlist, under the hero. */
  .dl-native {
    margin: 30px auto 0; max-width: 30em;
    padding-top: 22px; border-top: 1px solid var(--rule);
  }
  .dl-native-line { margin: 0 0 14px; font-size: 13px; line-height: 1.55; color: var(--ink-soft, var(--ink-mute)); }
  .dl-waitlist { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
  .dl-wl-email {
    font: inherit; font-size: 14px;
    padding: 11px 14px; border-radius: 10px;
    border: 1px solid var(--rule); background: var(--bg); color: var(--ink);
    min-width: 0; flex: 1 1 220px; max-width: 280px;
  }
  .dl-wl-email:focus { outline: 2px solid var(--accent, #c2410c); outline-offset: 1px; }
  .dl-wl-email:disabled { opacity: 0.6; }
  .dl-wl-btn {
    font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
    padding: 11px 18px; border-radius: 10px; border: none; white-space: nowrap;
    background: var(--ink); color: var(--bg);
    transition: transform 160ms ease, opacity 160ms ease;
  }
  .dl-wl-btn:hover { transform: translateY(-1px); }
  .dl-wl-btn:disabled { opacity: 0.5; cursor: default; transform: none; }
  .dl-wl-msg { margin: 10px 0 0; min-height: 1.2em; font-size: 12.5px; }
  .dl-wl-msg.ok  { color: var(--accent, #c2410c); }
  .dl-wl-msg.err { color: #c53030; }
  .dl-author {
    margin: 22px auto 0; padding-top: 18px;
    border-top: 1px solid var(--rule);
    max-width: 30em;
    font-size: 12px; line-height: 1.55;
    color: var(--ink-mute, #6b6b6b);
    text-align: center;
  }
  .dl-author strong { color: var(--ink); font-weight: 600; }
  @media (max-width: 600px) {
    .dl-play { width: 100%; max-width: 320px; justify-content: center; }
  }

  /* Onboarding (audit H3) — first-visit 3-step intro. Centered overlay with a
     subtle backdrop blur; dismissable, persists in localStorage. */
  .onboard {
    position: fixed; inset: 0; z-index: 200;
    display: grid; place-items: center;
    background: color-mix(in srgb, var(--bg) 60%, transparent);
    backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    animation: ob-fade 220ms ease-out;
  }
  .onboard[hidden] { display: none !important; }
  @keyframes ob-fade { from { opacity: 0; } to { opacity: 1; } }
  .onboard-card {
    width: min(540px, 92vw);
    background: var(--score-bg, var(--bg));
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: 14px;
    box-shadow: 0 24px 70px rgba(0,0,0,0.28), 0 2px 8px rgba(0,0,0,0.10);
    padding: 28px 30px 22px;
    animation: ob-rise 280ms cubic-bezier(0.23, 1, 0.32, 1);
  }
  @keyframes ob-rise { from { transform: translateY(14px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
  .ob-eyebrow {
    font-family: "JetBrains Mono", monospace; font-size: 10px;
    letter-spacing: 0.18em; text-transform: uppercase;
    color: var(--accent, #c2410c); margin-bottom: 8px;
  }
  .onboard-card h2 {
    margin: 0 0 14px; font-size: 22px; font-weight: 500; letter-spacing: -0.01em;
  }
  .ob-steps {
    list-style: none; margin: 0 0 20px; padding: 0;
    display: flex; flex-direction: column; gap: 14px;
  }
  .ob-steps li { display: flex; gap: 14px; align-items: flex-start; }
  .ob-num {
    flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%;
    background: var(--ink); color: var(--bg);
    display: inline-flex; align-items: center; justify-content: center;
    font-family: "JetBrains Mono", monospace; font-size: 12px; font-weight: 600;
    margin-top: 2px;
  }
  .ob-steps strong { display: block; font-size: 14.5px; font-weight: 600; margin-bottom: 3px; }
  .ob-steps p { margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink-soft, var(--ink-mute)); }
  .ob-steps kbd {
    font-family: "JetBrains Mono", monospace; font-size: 11px;
    padding: 1px 6px; border-radius: 4px;
    background: var(--rule); color: var(--ink); border: 1px solid color-mix(in srgb, var(--rule) 75%, var(--ink) 25%);
  }
  .ob-hint { color: var(--ink-mute); font-size: 12px; }
  .ob-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 6px; }
  .ob-primary, .ob-secondary {
    font: inherit; cursor: pointer; padding: 9px 18px; border-radius: 8px;
    transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
                background 160ms ease, color 160ms ease, border-color 160ms ease;
  }
  .ob-primary {
    background: var(--ink); color: var(--bg); border: 1px solid var(--ink);
    font-weight: 500;
  }
  .ob-primary:hover { background: color-mix(in srgb, var(--ink) 88%, var(--accent)); border-color: color-mix(in srgb, var(--ink) 88%, var(--accent)); }
  .ob-primary:active { transform: scale(0.97); }
  .ob-secondary {
    background: transparent; color: var(--ink-mute);
    border: 1px solid var(--rule);
  }
  .ob-secondary:hover { color: var(--ink); border-color: var(--ink); }
  .ob-secondary:active { transform: scale(0.97); }
  @media (max-width: 600px) {
    .onboard-card { padding: 22px 22px 18px; border-radius: 12px; }
    .onboard-card h2 { font-size: 19px; }
  }

  /* Lab overlay = slide-in side panel from right edge.
     Toggle with the chart icon in topbar (#open-lab acts as toggle when shift-clicked,
     OR via the dedicated #lab-toggle-tab on the right edge). */
  .lab-overlay {
    position: fixed; right: 0; top: 56px; bottom: 0;
    width: 340px; max-width: 94vw;
    background: var(--bg); border-left: 1px solid var(--rule);
    box-shadow: -8px 0 28px rgba(0,0,0,0.18);
    z-index: 60;
    transform: translateX(100%);
    transition: transform 280ms var(--easing), width 240ms var(--easing);
    display: flex; flex-direction: column; overflow: hidden;
  }
  .lab-overlay.show { transform: translateX(0); }
  .lab-overlay.resizing { transition: none; user-select: none; }
  /* Drag the left edge to resize. Thin hit strip + a faint visible rule. */
  .lab-resize {
    position: absolute; left: 0; top: 0; bottom: 0; width: 8px;
    cursor: ew-resize; z-index: 5; touch-action: none;
  }
  .lab-resize::after {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 1px;
    background: var(--rule); transition: background 160ms, width 160ms;
  }
  .lab-resize:hover::after, .lab-overlay.resizing .lab-resize::after { background: var(--accent, #c2410c); width: 2px; }
  .lab-overlay.fullscreen .lab-resize { display: none; }
  /* Full-screen lab — fills the viewport; prose panes get a comfy reading width. */
  .lab-overlay.fullscreen {
    top: 0; right: 0; bottom: 0; left: 0;
    width: 100vw; max-width: none; transform: none; z-index: 200;
    border-left: 0; box-shadow: none;
  }
  .lab-overlay.fullscreen .lab-body { padding-left: max(16px, 50vw - 460px); padding-right: max(16px, 50vw - 460px); }
  .lab-overlay.fullscreen .lab-pane:not(.lab-prose) { max-width: 760px; margin: 0 auto; }
  /* Edge tab — always visible handle to slide the panel out */
  .lab-edge-tab {
    position: fixed; right: 0; top: 50%;
    transform: translateY(-50%);
    background: var(--bg); border: 1px solid var(--rule);
    border-right: none;
    border-radius: 8px 0 0 8px;
    padding: 12px 6px; cursor: pointer;
    z-index: 59;
    color: var(--ink-mute);
    font-family: "JetBrains Mono", monospace;
    font-size: 9px; letter-spacing: 0.16em;
    writing-mode: vertical-rl; text-orientation: mixed;
    transition: color 160ms, background 160ms;
  }
  .lab-edge-tab:hover { color: var(--accent); background: var(--bg-2); }
  .lab-edge-tab.hidden-tab { display: none; }
  /* Lab header: tabs on the left, full-screen + close on the right; sticky. */
  .lab-head {
    flex: 0 0 auto;
    display: flex; align-items: flex-start; justify-content: space-between; gap: 8px;
    padding: 10px 12px 0 16px;
    border-bottom: 1px solid var(--rule);
    background: var(--bg);
  }
  .lab-tabs {
    /* min-width:0 lets this flex item shrink below its 7-tab content width so
       the tabs WRAP inside the panel instead of overflowing off-screen — the
       overflow used to carry the close button off with them (tabs unclickable). */
    display: flex; flex-wrap: wrap; gap: 2px;
    flex: 1 1 auto; min-width: 0;
  }
  .lab-tabs::-webkit-scrollbar { display: none; }
  .lab-tab {
    appearance: none; border: 0; background: transparent; cursor: pointer;
    color: var(--ink-mute); padding: 9px 10px 10px;
    font: 500 11.5px/1 inherit; letter-spacing: 0; white-space: nowrap;
    border-bottom: 2px solid transparent; margin-bottom: -1px; flex: 0 0 auto;
    transition: color 160ms var(--easing-fast), border-color 160ms var(--easing-fast);
  }
  .lab-tab:hover { color: var(--ink); }
  .lab-tab.active { color: var(--ink); border-bottom-color: var(--ink); font-weight: 600; }
  .lab-head-btns { display: flex; gap: 2px; flex-shrink: 0; }
  .lab-body { flex: 1 1 auto; overflow-y: auto; padding: 14px 16px 22px; }
  .lab-pane[hidden] { display: none; }
  .lab-overlay h3 {
    margin: 0 0 8px; font-size: 10px; color: var(--ink-mute);
    text-transform: uppercase; letter-spacing: 0.16em;
    font-family: "JetBrains Mono", monospace; font-weight: 500;
    display: flex; justify-content: space-between; align-items: center;
  }
  .lab-overlay canvas {
    display: block; width: 100%; height: 80px;
    background: var(--bg-2); border-radius: 6px;
    border: 1px solid var(--rule);
  }
  .lab-overlay .lab-stat {
    margin-top: 6px; font-family: "JetBrains Mono", monospace;
    font-size: 10.5px; color: var(--ink-soft);
    display: flex; justify-content: space-between;
  }
  .lab-overlay .lab-stat b { color: var(--ink); font-weight: 500; }
  /* Prose panes (Theory / Sources) — readable body type, real links. */
  .lab-prose { font-size: 12px; line-height: 1.6; color: var(--ink-soft); }
  .lab-prose h4 { margin: 16px 0 4px; font-size: 12.5px; font-weight: 700; color: var(--ink); letter-spacing: 0; text-transform: none; font-family: inherit; }
  .lab-prose p { margin: 4px 0 9px; }
  .lab-prose ul { margin: 5px 0 12px; padding-left: 18px; }
  .lab-prose li { margin: 5px 0; }
  .lab-prose b { color: var(--ink); font-weight: 600; }
  .lab-prose a { color: var(--accent, #c2410c); text-decoration: underline; text-underline-offset: 2px; }
  .lab-prose a:hover { color: var(--ink); }
  .lab-prose code { background: var(--bg-2); border: 1px solid var(--rule-soft); padding: 0 4px; border-radius: 3px; font-size: 11px; font-family: "JetBrains Mono", monospace; }

  /* Mobile: a 340px side panel buries the score on a phone. Dock the lab to
     the bottom as a sheet — score on top, lab below, both visible at once. */
  @media (max-width: 720px) {
    .lab-overlay:not(.fullscreen) {
      top: auto; left: 0; right: 0; bottom: 0;
      width: 100%; max-width: none; height: 46vh;
      border-left: 0; border-top: 1px solid var(--rule);
      box-shadow: 0 -10px 28px rgba(0,0,0,0.20);
      transform: translateY(100%);
    }
    .lab-overlay:not(.fullscreen).show { transform: translateY(0); }
    .lab-resize { display: none; }
    .lab-edge-tab {
      top: auto; bottom: 0; right: 12px; transform: none;
      writing-mode: horizontal-tb; text-orientation: mixed;
      border-radius: 8px 8px 0 0; border: 1px solid var(--rule); border-bottom: none;
      padding: 7px 14px; font-size: 10px;
    }
    /* Lab tabs scroll sideways inside the sheet instead of wrapping to 3 rows
       — the lab body keeps its height. The close / full-screen buttons sit in
       a separate flex sibling, so a scrolling tab strip can't carry them off. */
    .lab-tabs { flex-wrap: nowrap; overflow-x: auto; }
    .lab-tab { min-height: 44px; }

    /* --- Topbar: collapse to one compact row -------------------------------
       The score reader IS the product on a phone. The topbar keeps only the
       demo / reading essentials (brand, library, mic-demo toggle, Play). The
       power tools — annotate, trainer, studio, transpose, theme, the /test
       lab link, sign-in — overflowed the bar to 3 stacked rows (~200px) and
       buried the score; they stay on desktop + the iPad app. */
    .topbar {
      flex-wrap: nowrap; gap: 6px;
      padding: 8px 12px; min-height: 58px;
    }
    .topbar .brand { font-size: 13px; padding-right: 4px; }
    #open-lab, #open-help, #score-fs-btn, #anchor-btn,
    #annot-btn, #trainer-btn, #studio-btn, #rec-pick, .tools-pick,
    .top-divider, #conf-pill, #auth-slot, .theme-switch {
      display: none !important;
    }
    #open-library { width: 42px; height: 42px; }
    .source-toggle { margin-right: 0; }
    .source-toggle .src-btn { min-height: 42px; }
    .source-toggle .src-btn span { display: none; }   /* icon-only mic / demo */
    .play-cta-top { min-height: 42px; padding: 9px 18px; font-size: 13px; }

    /* The audio HUD is a desktop affordance — a floating bar of performer /
       time / format / bitrate / source. On a phone its long source string
       wraps it into a tall dark card that floats over the score. The collapsed
       meta strip names the piece and the progress rail shows position, so the
       HUD is dropped on mobile. */
    .audio-hud { display: none !important; }

    /* Round 1 hid the power tools off a phone; round 2 brings them back behind
       a "..." menu — calibrate / studio / full-screen / annotate / help.
       Descendant selectors (.topbar / .lab-overlay) so these out-specify the
       desktop `display:none` base rules, which sit later in the stylesheet. */
    .topbar #more-btn { display: inline-flex; }
    .more-menu.open { display: flex; }

    /* Sources leaves the lab tab strip and becomes a button bottom-left of the
       lab; tapping it opens the Sources panel over the open tab. */
    .lab-tab[data-lab-tab="sources"] { display: none; }
    .lab-overlay .lab-sources-btn { display: inline-flex; }
    .lab-body { padding-bottom: 56px; }
  }

  /* ===== Mobile overflow menu — the "..." topbar button ================= */
  #more-btn { display: none; }   /* desktop: every tool already fits the bar */
  .more-menu {
    display: none;               /* mobile only — shown via .open */
    position: absolute; top: calc(100% + 5px); left: 10px;
    z-index: 80; min-width: 234px;
    flex-direction: column; gap: 2px; padding: 6px;
    background: var(--bg); border: 1px solid var(--rule);
    border-radius: 12px; box-shadow: var(--shadow);
  }
  .more-item {
    appearance: none; border: 0; background: transparent; cursor: pointer;
    display: flex; align-items: center; gap: 12px;
    width: 100%; padding: 12px; border-radius: 8px;
    font: 500 13.5px/1.2 inherit; color: var(--ink); text-align: left;
    transition: background 140ms var(--easing-fast);
  }
  .more-item:hover { background: var(--bg-2); }
  .more-item:active { background: var(--bg-3); }
  .more-item:disabled { opacity: 0.38; cursor: not-allowed; }
  .more-item svg {
    width: 18px; height: 18px; flex: 0 0 auto;
    stroke: currentColor; fill: none; stroke-width: 1.6;
    stroke-linecap: round; stroke-linejoin: round;
  }

  /* ===== Lab "Sources" — mobile button + popover ======================= */
  .lab-sources-btn {
    display: none;               /* mobile only */
    position: absolute; left: 12px; bottom: 12px; z-index: 8;
    align-items: center; gap: 7px; padding: 9px 14px;
    background: var(--bg); color: var(--ink);
    border: 1px solid var(--rule); border-radius: 999px;
    box-shadow: 0 3px 12px rgba(0,0,0,0.16);
    font: 600 12px/1 inherit; cursor: pointer;
    transition: background 140ms var(--easing-fast), transform 80ms var(--easing-fast);
  }
  .lab-sources-btn:hover { background: var(--bg-2); }
  .lab-sources-btn:active { transform: translateY(1px); }
  .lab-sources-btn svg { flex: 0 0 auto; }
  .lab-sources-pop {
    position: absolute; inset: 0; z-index: 12;
    display: flex; flex-direction: column; background: var(--bg);
  }
  .lab-sources-pop[hidden] { display: none; }
  .lsp-head {
    flex: 0 0 auto;
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 11px 10px 11px 16px; border-bottom: 1px solid var(--rule);
    font: 600 13px/1 inherit; color: var(--ink);
  }
  .lsp-body { flex: 1 1 auto; overflow-y: auto; padding: 14px 16px 22px; }

  /* ===== Live-tracking "under construction" warning toast =============== */
  .coming-soon-toast {
    position: fixed; left: 50%; bottom: 96px;
    transform: translateX(-50%) translateY(14px);
    z-index: 250; width: min(430px, calc(100vw - 28px));
    background: var(--ink); color: var(--bg);
    border-radius: 14px; padding: 15px 18px;
    box-shadow: 0 10px 40px rgba(0,0,0,0.32);
    opacity: 0; pointer-events: none; cursor: pointer;
    transition: opacity 220ms var(--easing), transform 280ms var(--easing);
  }
  .coming-soon-toast[hidden] { display: none; }
  .coming-soon-toast.show {
    opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto;
  }
  .coming-soon-toast .cst-title {
    display: flex; align-items: center; gap: 8px;
    font-size: 14px; font-weight: 700;
  }
  .coming-soon-toast .cst-title svg {
    width: 17px; height: 17px; flex: 0 0 auto;
    stroke: currentColor; fill: none; stroke-width: 1.8;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .coming-soon-toast .cst-body {
    margin-top: 5px; font-size: 12.5px; line-height: 1.5;
    color: color-mix(in srgb, var(--bg) 70%, var(--ink));
  }
  .coming-soon-toast .cst-body b { color: var(--bg); font-weight: 700; }

  /* ---- Score full-screen: hide everything but the page; floating X to exit ---- */
  body.score-fullscreen .topbar { display: none; }
  body.score-fullscreen #piece-meta,
  body.score-fullscreen #piece-inspector { display: none; }
  /* Élie 2026-05-25: "Le mode plein écran ne fonctionne toujours pas.
     Depuis trois jours que c'est blanc." Defensive height anchors so
     the score panel survives even when the iOS WebView's flex
     resolution flips on the topbar removal — without these the
     `.frame` can collapse to ~0px on the rAF after the class change,
     producing an empty white canvas. */
  body.score-fullscreen { height: 100dvh; min-height: 100dvh; }
  body.score-fullscreen .stage { height: 100dvh; min-height: 100dvh; }
  body.score-fullscreen .frame { height: 100%; min-height: 0; }
  body.score-fullscreen #page-stage { height: 100%; min-height: 0; }
  /* in score full screen it's just the page — drop the lab-mode banner / tint too */
  body.score-fullscreen.lab-mode::before,
  body.score-fullscreen.lab-mode::after { display: none; }
  body.score-fullscreen.lab-mode { box-shadow: none; }
  body.score-fullscreen.lab-mode .topbar { margin-top: 0; }
  body.score-fullscreen .stage { padding: 0; }
  body.score-fullscreen .frame { border: 0; border-radius: 0; padding: 0; box-shadow: none; background: var(--score-bg); }
  body.score-fullscreen #page-stage { border-radius: 0; }
  #score-fs-exit {
    position: fixed; top: 12px; right: 12px; z-index: 300;
    width: 34px; height: 34px; border-radius: 8px;
    background: var(--bg); border: 1px solid var(--rule);
    box-shadow: 0 2px 10px rgba(0,0,0,0.18);
    display: none;
  }
  #score-fs-exit:hover { background: var(--bg-2); color: var(--ink); }
  body.score-fullscreen #score-fs-exit { display: inline-flex; }

  /* ====================================================================
     Score editor — drawing tools + Smart Annotations panel.
     A floating glass toolbar pinned to the bottom of the score, plus a
     small popover for the auto-colour rules. Dark glass over the cream
     page, theme-independent (the score area is always paper-coloured).
     ==================================================================== */
  .annot-bar {
    position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
    z-index: 8;
    display: flex; align-items: center; gap: 3px; flex-wrap: wrap; row-gap: 4px; justify-content: center;
    width: min(96vw, 1080px);
    padding: 6px 10px;
    background: rgba(18,20,26,0.92);
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: 12px;
    box-shadow: 0 8px 28px rgba(0,0,0,0.34), 0 1px 0 rgba(255,255,255,0.05) inset;
    backdrop-filter: blur(10px) saturate(1.1);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
    color: #e8eaf0;
    font: 12px/1.2 var(--ui, system-ui, -apple-system, sans-serif);
    transition: opacity 180ms var(--easing, ease), transform 220ms var(--easing, ease);
  }
  .annot-bar[hidden] { display: none; }
  .annot-bar.dim { opacity: 0.22; }                 /* fades while drawing so it's out of the way */
  .annot-bar.dim:hover { opacity: 1; }
  /* when the Smart panel is open it claims the bottom-right; the toolbar steps
     aside to the bottom-left so neither covers the other. */
  .annot-bar.with-panel { left: 14px; right: auto; transform: none; max-width: calc(96vw - 360px); }
  .annot-btn {
    appearance: none; -webkit-appearance: none;
    width: 30px; height: 28px; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 0; border-radius: 8px;
    color: #cdd2dc; cursor: pointer;
    transition: background 140ms ease, color 140ms ease, transform 80ms ease;
  }
  .annot-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
  .annot-menu-wrap { position: relative; display: inline-flex; align-items: center; }
  .annot-symbol {
    min-width: 28px; height: 28px; padding: 0 7px;
    font-family: Georgia, "Times New Roman", serif; font-style: italic;
    font-weight: 700; font-size: 14px; line-height: 1;
  }
  .annot-symbol[data-plain="1"] {
    font-family: var(--ui, system-ui, -apple-system, sans-serif);
    font-style: normal; font-size: 13px;
  }
  .annot-btn:hover { background: rgba(255,255,255,0.10); color: #fff; }
  .annot-btn:active { transform: scale(0.94); }
  .annot-btn.active { background: rgba(99,140,255,0.92); color: #fff; box-shadow: 0 0 0 1px rgba(255,255,255,0.10) inset; }
  .annot-btn[disabled] { opacity: 0.35; pointer-events: none; }
  .annot-btn.danger:hover { background: rgba(220,60,60,0.85); color: #fff; }
  .annot-sep { width: 1px; align-self: stretch; margin: 2px 4px; background: rgba(255,255,255,0.12); }
  /* Push the meta group (visibility / smart / close) to the right edge on wide screens.
     When the bar wraps (mobile), the auto-margin collapses and they re-flow normally. */
  .annot-right { margin-left: auto; }
  /* Keyboard focus: the bar is shortcut-driven (V/P/H/L/A/R/O/T/E, [, ], Ctrl+Z, Ctrl+Shift+Z, 1-6).
     Every button needs a visible focus ring so Tab navigation isn't invisible. */
  .annot-btn:focus-visible {
    outline: 2px solid rgba(99,140,255,0.85);
    outline-offset: 2px;
    background: rgba(255,255,255,0.08);
  }
  .annot-swatch:focus-visible {
    outline: 2px solid rgba(99,140,255,0.85);
    outline-offset: 3px;
  }
  /* Smart-panel toggle gets a persistent purple tint so it previews what's behind the click
     (the Smart Annotations panel is the only "magic" feature in the bar). When pressed it goes blue,
     matching every other .active tool. */
  #annot-smart { color: #c4b5fd; }
  #annot-smart:hover { color: #ddd6fe; background: rgba(167,139,250,0.18); }
  #annot-smart.active { color: #fff; background: rgba(99,140,255,0.92); }
  /* Discovery pulse: ride the brief moment the redo button transitions from disabled -> enabled
     for the very first time, so users notice the new affordance. JS toggles .pulse-once for 700ms.
     The ring uses a pseudo-element animating transform + opacity only (GPU-composited), so the
     pulse costs zero paint per frame. */
  .annot-btn.pulse-once { position: relative; }
  .annot-btn.pulse-once::after {
    content: ''; position: absolute; inset: -2px;
    border: 2px solid rgba(99,140,255,0.6);
    border-radius: 10px;
    pointer-events: none;
    transform: scale(0.92);
    animation: annot-pulse-ring 700ms ease-out 1;
  }
  @keyframes annot-pulse-ring {
    0%   { transform: scale(0.92); opacity: 1; }
    100% { transform: scale(1.45); opacity: 0; }
  }
  .annot-swatches { display: inline-flex; align-items: center; gap: 3px; }
  .annot-recent { padding-left: 6px; margin-left: 2px; border-left: 1px dashed rgba(255,255,255,0.22); }
  .annot-recent:empty { display: none; border-left: 0; padding-left: 0; margin-left: 0; }
  .annot-recent .annot-swatch { width: 14px; height: 14px; opacity: 0.92; border-width: 1px; }
  .annot-symbols {
    position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 42;
    display: grid; grid-template-columns: repeat(7, minmax(28px, auto)); gap: 4px;
    padding: 7px; border: 1px solid rgba(255,255,255,0.12); border-radius: 10px;
    background: rgba(24,27,34,0.96); box-shadow: 0 14px 36px rgba(0,0,0,0.34);
  }
  .annot-symbols[hidden] { display: none; }
  .annot-symbols::-webkit-scrollbar { display: none; }
  .annot-swatch {
    width: 16px; height: 16px; border-radius: 50%; cursor: pointer; padding: 0;
    border: 1.5px solid rgba(255,255,255,0.18);
    transition: transform 90ms ease, box-shadow 120ms ease;
  }
  .annot-swatch:hover { transform: scale(1.18); }
  .annot-swatch.sel { box-shadow: 0 0 0 2px rgba(18,20,26,0.92), 0 0 0 3.5px #fff; transform: scale(1.12); }
  .annot-color {
    width: 20px; height: 20px; padding: 0; border: 1.5px solid rgba(255,255,255,0.22);
    border-radius: 6px; background: none; cursor: pointer; overflow: hidden;
  }
  .annot-color::-webkit-color-swatch-wrapper { padding: 0; }
  .annot-color::-webkit-color-swatch { border: 0; border-radius: 4px; }
  .annot-color::-moz-color-swatch { border: 0; border-radius: 4px; }
  .annot-w { position: relative; }
  .annot-w i { display: block; border-radius: 50%; background: currentColor; }
  .annot-w[data-w="s"] i { width: 4px; height: 4px; }
  .annot-w[data-w="m"] i { width: 7px; height: 7px; }
  .annot-w[data-w="l"] i { width: 11px; height: 11px; }
  .annot-text-input {
    position: absolute; z-index: 12; min-width: 60px;
    padding: 2px 5px; border-radius: 5px;
    border: 1.5px solid rgba(99,140,255,0.9); outline: none;
    background: rgba(255,255,255,0.96); color: #16181d;
    font: 600 14px/1.2 var(--ui, system-ui, sans-serif);
    box-shadow: 0 4px 18px rgba(0,0,0,0.25);
  }

  /* drawing overlay sits on top of the rendered score / pdf page inside .page */
  svg.st-annot { position: absolute; overflow: visible; pointer-events: none; }
  svg.st-annot.armed { pointer-events: auto; cursor: crosshair; }
  svg.st-annot .st-cap { fill: rgba(0,0,0,0); }
  body.st-erasing svg.st-annot.armed { cursor: cell; }

  /* ---- Smart Annotations popover (slides in from the right, like Newzik's) ---- */
  .smart-panel {
    position: absolute; right: 14px; bottom: 14px;
    z-index: 9; width: min(92vw, 340px);
    max-height: calc(100% - 28px); overflow-y: auto;
    padding: 12px 14px 14px;
    background: rgba(18,20,26,0.96);
    border: 1px solid rgba(255,255,255,0.12);
    border-radius: 14px;
    box-shadow: 0 14px 40px rgba(0,0,0,0.42);
    backdrop-filter: blur(14px) saturate(1.1);
    -webkit-backdrop-filter: blur(14px) saturate(1.1);
    color: #e8eaf0;
    font: 13px/1.4 var(--ui, system-ui, -apple-system, sans-serif);
  }
  .smart-panel[hidden] { display: none; }
  .smart-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
  .smart-head h4 { margin: 0; font-size: 13.5px; font-weight: 650; letter-spacing: 0.01em; color: #f3f4f8; display: flex; align-items: center; gap: 6px; }
  .smart-head h4 svg { width: 14px; height: 14px; }
  .smart-sub { margin: 0 0 8px; font-size: 11.5px; color: #9aa0ad; }
  .smart-rows { display: flex; flex-direction: column; gap: 2px; }
  .smart-row {
    display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 8px;
    padding: 7px 6px; border-radius: 9px;
  }
  .smart-row:hover { background: rgba(255,255,255,0.05); }
  .smart-row .lbl { min-width: 0; }
  .smart-row .lbl b { display: block; font-size: 12.5px; font-weight: 600; color: #e8eaf0; }
  .smart-row .lbl span { display: block; font-size: 10.5px; line-height: 1.3; color: #8b909c; margin-top: 1px; }
  .smart-row.off .lbl b, .smart-row.off .lbl span { opacity: 0.5; }
  .smart-row input.annot-color { width: 22px; height: 22px; }
  /* toggle switch */
  .sw {
    appearance: none; -webkit-appearance: none;
    position: relative; width: 34px; height: 19px; flex: 0 0 auto;
    border-radius: 999px; border: 0; cursor: pointer; padding: 0;
    background: rgba(255,255,255,0.18);
    transition: background 160ms ease;
  }
  .sw::after {
    content: ''; position: absolute; top: 2px; left: 2px;
    width: 15px; height: 15px; border-radius: 50%;
    background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.3);
    transition: transform 180ms cubic-bezier(0.23,1,0.32,1);
  }
  .sw[aria-pressed="true"] { background: #3fae5a; }
  .sw[aria-pressed="true"]::after { transform: translateX(15px); }
  .sw:focus-visible { outline: 2px solid rgba(99,140,255,0.8); outline-offset: 2px; }
  .smart-foot { margin-top: 10px; display: flex; justify-content: flex-end; }
  .smart-foot button {
    appearance: none; -webkit-appearance: none; border: 1px solid rgba(255,255,255,0.16);
    background: rgba(255,255,255,0.06); color: #cdd2dc; cursor: pointer;
    font: 11.5px/1 var(--ui, system-ui, sans-serif); padding: 6px 10px; border-radius: 7px;
  }
  .smart-foot button:hover { background: rgba(255,255,255,0.12); color: #fff; }
  @media (prefers-reduced-motion: reduce) {
    .annot-bar, .sw::after, .annot-btn, .annot-swatch { transition: none; }
  }

  /* onboarding: lede line + the "Demo / Play / Mic" chips */
  .ob-lede { margin: 2px 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft, #555); }
  .ob-lede b { color: var(--ink, #1a1a1a); }
  .ob-chip {
    display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px;
    font-size: 0.92em; font-weight: 600; letter-spacing: 0.01em; white-space: nowrap;
    background: var(--bg-2, rgba(0,0,0,0.06)); border: 1px solid var(--rule, rgba(0,0,0,0.14)); color: var(--ink, #1a1a1a);
  }
  .ob-chip-go { background: color-mix(in srgb, var(--accent, #3b82f6) 16%, transparent); border-color: color-mix(in srgb, var(--accent, #3b82f6) 40%, transparent); }

  /* "shine hint": a soft pulsing glow used to nudge the user toward the next
     control (the Demo source toggle, then Play) right after onboarding. */
  @keyframes st-shine {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, #3b82f6) 55%, transparent); }
    70%  { box-shadow: 0 0 0 9px color-mix(in srgb, var(--accent, #3b82f6) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, #3b82f6) 0%, transparent); }
  }
  .shine-hint { animation: st-shine 1.7s cubic-bezier(0.23,1,0.32,1) infinite; position: relative; }
  .shine-hint::after {
    content: ''; position: absolute; inset: -2px; border-radius: inherit; pointer-events: none;
    box-shadow: 0 0 12px 1px color-mix(in srgb, var(--accent, #3b82f6) 45%, transparent);
  }
  /* the source toggle / play button keep their own border-radius; make the glow
     wrap them nicely */
  #src-demo.shine-hint, #play-btn.shine-hint { z-index: 3; }
  @media (prefers-reduced-motion: reduce) { .shine-hint { animation: none; } .shine-hint::after { box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent,#3b82f6) 60%, transparent); } }

  /* collapsible "?" help under each Lab diagram */
  .lab-q {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    width: 21px; height: 21px; flex: 0 0 auto; margin-left: 8px; padding: 0;
    border: 1px solid var(--rule, rgba(0,0,0,0.22)); border-radius: 50%;
    background: transparent; color: var(--ink-mute, #888);
    font: 700 12px/19px var(--ui, system-ui, sans-serif); text-align: center;
    vertical-align: middle; position: relative; touch-action: manipulation;
  }
  /* Invisible ~44px hit target around the small "?" glyph — the old 15px
     button was far below the touch minimum, so taps on iPad missed it
     entirely ("the ? doesn't work"). */
  .lab-q::before { content: ''; position: absolute; inset: -12px; }
  .lab-q:hover { background: var(--bg-2, rgba(0,0,0,0.06)); color: var(--ink, #1a1a1a); }
  .lab-q[aria-expanded="true"] { background: color-mix(in srgb, var(--accent,#3b82f6) 18%, transparent); border-color: color-mix(in srgb, var(--accent,#3b82f6) 40%, transparent); color: var(--ink, #1a1a1a); }
  .lab-help {
    margin: 4px 0 12px; padding: 8px 10px; border-radius: 7px;
    background: var(--bg-2, rgba(0,0,0,0.045)); border: 1px solid var(--rule, rgba(0,0,0,0.1));
    font-size: 11px; line-height: 1.5; color: var(--ink-mute, #777);
  }
  .lab-help[hidden] { display: none; }
  .lab-pane h3 { display: flex; align-items: center; gap: 4px; }
  /* the folded-in "Trace metrics" block (was its own tab) */
  .lab-details { border-top: 1px solid var(--rule-soft, rgba(0,0,0,0.08)); padding-top: 10px; }
  .lab-details > summary {
    cursor: pointer; list-style: none; padding: 4px 0; user-select: none;
    font-size: 12.5px; font-weight: 600; color: var(--ink, #1a1a1a);
    text-transform: uppercase; letter-spacing: 0.06em;
  }
  .lab-details > summary::-webkit-details-marker { display: none; }
  .lab-details > summary::before {
    content: '▸'; display: inline-block; margin-right: 6px; color: var(--ink-mute, #888);
    transition: transform 140ms ease; font-size: 0.9em;
  }
  .lab-details[open] > summary::before { transform: rotate(90deg); }
  .lab-details[open] > summary { margin-bottom: 6px; }
  .lab-details > summary:hover { color: var(--accent, #3b82f6); }
