/* ==========================================================
   SELF-HOSTED FONTS
   ========================================================== */

@font-face {
  font-family: "Noto Music";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/noto-music.woff2") format("woff2");
  unicode-range: U+1D100-1D1FF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/playfair-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Playfair Display";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/playfair-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ==========================================================
   CSS VARIABLES
   ========================================================== */

/* Ensure [hidden] always wins over component display rules */
[hidden] { display: none !important; }

:root {
  --font-main: "Inter", sans-serif;
  --font-serif: "Playfair Display", serif;

  /* Type scale (approved by Jappe, 10 October): one size per role, on every page and
     at every level. Playfair only for display and title; Inter for the rest. A new
     size is a decision, not a local fix: tests/type-scale.test.mjs refuses any other
     font size in new CSS, and lets existing ones only disappear. Diagram internals
     (neck, chord box, tuner dial, song chart) keep their own geometry-bound sizes. */
  --type-display: 2.6rem;    /* page title (h1), Playfair 700 */
  --type-title: 1.75rem;     /* feature or section title, Playfair 700 */
  --type-item: 1.0625rem;    /* title of a row, card, tool or exercise, Inter 600 */
  --type-body: 1rem;         /* reading text; also the section label, 700 uppercase .1em */
  --type-small: .875rem;     /* secondary text, buttons, tabs, numbers, counts */
  --type-xs: .75rem;         /* captions and small meta */

  /* Page rhythm (Jappe, 10 October): every page measures the same distances, so a
     page of a new level is right the first time. tests/page-rhythm.test.mjs holds
     the pages to them. */
  --page-title-gap: 1.4rem;  /* h1 with its gold line to the first block (tab row) */
  --page-tabs-gap: 1.9rem;   /* a tab row to its content */
  --section-gap: 3rem;       /* one section to the next */
  --reading-measure: 70ch;   /* longest line of running text */

  --bg-main: #0f0f0f;
  --bg-section: #151515;
  --bg-card: #1c1c1c;

  --text-main: #eaeaea;
  --text-muted: #b5b5b5;

  --accent: #d8a84f;
  /* Root-note accent — reserved exclusively for the root/tonic in any
     chord diagram, scale neck, piano or Circle of Fifths widget. Never
     used for anything else, so a root is always recognizable at a glance. */
  --root-accent: #e8924a;

  --radius-small: 6px;
  --radius-card: 10px;  /* every content card and widget panel (Jappe, 15 October 2026) */
  --radius-media: 12px;
  --radius-pill: 999px;

  --shadow-main: 0 18px 45px rgba(0, 0, 0, .35);
  --shadow-soft: 0 8px 26px rgba(0, 0, 0, .30);

  /* Alpha-overlay base â€” white in dark mode, black in light mode.
     Use as: rgba(var(--w), 0.08) so borders and overlays invert with the theme. */
  --w: 255,255,255;

  /* SIDEBAR WIDTH — one token, read by .platform-sidebar.

     260px is the design width and is what a large desktop still gets. The
     problem it caused sits between roughly 1000px and 1300px, where the
     sidebar is still fully expanded but the window is not wide enough to
     carry it plus a full 15-fret neck: at 1024px the sidebar and the page
     padding together took more than a third of the window.

     So the width tapers instead of stepping: 220px at 1280px and below,
     rising linearly to its full 260px at 1440px and above. 220px still
     leaves ~172px of label room inside the sidebar's 1.5rem padding,
     enough for the portal's current navigation labels on one line.

     Deliberately one declaration rather than a new breakpoint: the two
     places that DO override the width (the collapsed rail at 88px, and
     the <=768px mobile takeover at 100vw) both win on specificity or
     !important, so neither needs to know this exists. */
  --sidebar-w: clamp(220px, calc(220px + (100vw - 1280px) * 0.25), 260px);

  /* THE PORTAL CONTENT AXIS — one left edge for every page.

     --page-gutter is the distance from the content column's edge to the
     page's text margin. It is what .platform-page pads with, so the h1,
     the gold rule under it, the tab row, every section heading and every
     primary control start on the same x on every page. Anything that
     needs to sit on that axis (or deliberately step off it) reads this
     token instead of restating 4rem.

     --page-pad-y is the same padding's vertical half. It is a separate
     token only because the two stop being equal below 768px, where the
     horizontal gutter has to shrink faster than the vertical rhythm.

     --page-content-max is the other half of the contract: how wide a
     page-level block may run before it stops growing. A block that hits
     it stays PINNED TO THE AXIS — it must not centre itself in the
     leftover space, because that is exactly what pushed Theory's
     headings ~59px right of its own title on a wide screen.

     Both step with the same breakpoints the portal already used. The
     per-page exception is Fretboard's, and it sets the token rather
     than re-declaring padding — see FRETBOARD — FULL-NECK WIDTH BUDGET. */
  --page-gutter: 4rem;
  --page-pad-y: 4rem;
  /* Under the last block (Jappe, 13 October 2026): enough to end the page, never so much that a
     page which fits gets a scrollbar for empty space. It never exceeds --page-pad-y. */
  --page-pad-bottom: 2rem;
  --page-content-max: 1400px;
}
/* Phone and portrait tablet (Jappe, 13 October 2026): the page title stays clearly above the
   section titles under it. h1 32px, titles 22px; the other roles keep their size. */
@media (max-width: 768px) {
  :root { --type-display: 2rem; --type-title: 1.375rem; }
}

/* Collapsing the sidebar frees ~172px. Page-level blocks may spend it. */
:root.sidebar-collapsed {
  --page-content-max: 1580px;
}

/* Below the split's own collapse point a capped block would be narrower
   than the page, so the cap simply stops applying. */
@media (max-width: 1400px) {
  /* Both selectors, so the collapsed rail's higher-specificity 1580px
     above does not survive the breakpoint. */
  :root,
  :root.sidebar-collapsed {
    --page-content-max: none;
  }
}

@media (max-width: 900px) {
  :root {
    --page-gutter: 2rem;
    --page-pad-y: 2rem;
  }
}

@media (max-width: 768px) {
  :root {
    --page-gutter: 1.25rem;
    --page-pad-y: 1.5rem;
  }
}

/* ----------------------------------------------------------
   LIGHT THEME â€” applied via [data-theme="light"] on <html>.
   Default (no attribute) is dark.
   ---------------------------------------------------------- */
:root[data-theme="light"] {
  --bg-main:    #ffffff;
  --bg-section: #f2f2f0;
  --bg-card:    #f8f8f7;

  --text-main:  #1a1a1a;
  --text-muted: #5a5a5a;

  --accent: #b07820;

  --shadow-main: 0 18px 45px rgba(0, 0, 0, .10);
  --shadow-soft: 0 8px 26px rgba(0, 0, 0, .08);

  --w: 0,0,0;
}

/* Sidebar background stays dark in both themes, so its text must stay
   light — re-declare the shared variables locally within the sidebar. */
:root[data-theme="light"] .platform-sidebar {
  --text-main:  #eaeaea;
  --text-muted: #b5b5b5;
  --w: 255,255,255;
}


/* ==========================================================
   BASE RESET
   ========================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-main);
  background: var(--bg-main);
  color: var(--text-main);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Native controls do not inherit body typography by default. Share the
   portal's interface family, without resetting component sizes, weights or
   line heights. Explicit music glyphs and monospace editors keep their own
   component rules; never force the interface family onto every element. */
button,
input,
select,
textarea,
optgroup,
option {
  font-family: var(--font-main);
}
input::file-selector-button { font-family: var(--font-main); }

/* Native popup menus live outside the styled select box on Windows.
   Set BOTH surfaces explicitly: pale inherited text on an OS-white popup
   is otherwise unreadable even though the closed control looks correct. */
select {
  color-scheme: dark;
  color: var(--text-main);
  background-color: var(--bg-card);
}
select option, select optgroup {
  color: var(--text-main);
  background-color: var(--bg-card);
}
select option:disabled { color: var(--text-muted); }
@media (forced-colors: active) {
  select, select option, select optgroup {
    color: CanvasText;
    background-color: Canvas;
  }
  select option:disabled { color: GrayText; }
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Portal-wide: buttons and button-like interactive controls (tabs, chips,
   toggles — anything the user taps to act, not to read) should never let
   a tap/long-press start a native text selection or leave the mobile
   tap-highlight flash behind. That was previously only fixed on .fb-tab;
   apply it everywhere so it can't show up on any other control. */
button,
[role="tab"],
[role="button"] {
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}


/* ==========================================================
   PLATFORM BASE
   ========================================================== */

.platform {
  margin: 0;
  padding-top: 0;
  font-family: var(--font-main);
  background: var(--bg-main);
  color: var(--text-main);
}

.platform-layout {
  display: flex;
  min-height: 100vh;
}


/* ==========================================================
   SIDEBAR
   ========================================================== */

.platform-sidebar {
  width: var(--sidebar-w);
  background: #0f0f0f;
  border-right: 1px solid rgba(255, 255, 255, 0.06);
  padding: 2rem 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  flex-shrink: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  transition: width 0.25s ease, padding 0.25s ease;
}

.sidebar-header {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin: -0.5rem -0.5rem 2.5rem;
}

.platform-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: 0.5rem;
  border-radius: 10px;
  text-decoration: none;
  transition: background 0.25s ease;
}

.platform-logo img {
  height: auto;
}

/* Full icon+wordmark lockup (expanded sidebar) vs. icon-only crop of the
   same artwork (collapsed rail, ~88px wide â€” no room for the wordmark). */
.platform-logo-full {
  width: 185px;
  max-width: 100%;
}
.platform-logo-icon {
  display: none;
  width: 44px;
  height: auto;
}

.platform-nav {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  flex: 1;
  padding-right: 0;
}

.nav-link {
  text-decoration: none;
  color: rgba(var(--w), 0.72);
  font-size: var(--type-body);
  padding: 0.8rem 1.1rem;
  border-radius: 10px;
  transition: background 0.25s ease, color 0.25s ease;
}

.nav-link:hover {
  background: rgba(216, 168, 79, 0.08);
  color: var(--text-main);
}

.nav-link.active {
  background: rgba(216, 168, 79, 0.1);
  color: var(--accent);
}

.nav-link--coming-soon {
  color: rgba(var(--w), 0.42);
  cursor: default;
}

.nav-link--coming-soon:hover {
  background: transparent;
  color: rgba(var(--w), 0.42);
}

.platform-account {
  margin-top: 2rem;
  padding-top: 1.2rem;
  border-top: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.account-icons {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

/* Desktop, expanded sidebar only — .account-card (avatar+email) aligns to
   the exact same left content axis as .nav-link text (sidebar-edge +
   1.1rem). .account-icons stays centered (its own rule above), just fixed
   to a uniform icon size so the row's overflow (5 icons don't fit their
   normal 42px hitbox + 0.5rem gaps in ~194px) doesn't get silently
   absorbed as uneven per-icon flex-shrink. Collapsed keeps its own
   centered/stacked rule below; mobile keeps its own centered rule in the
   768px drawer media query — neither is touched by this. */
@media (min-width: 769px) {
  html:not(.sidebar-collapsed) .account-icons {
    /* Fill the row edge to edge instead of clustering as one centered
       group — first icon flush left, last flush right, evenly spaced
       between. .account-card (avatar+email) above is untouched. */
    justify-content: space-between;
    gap: 0.4rem;
  }
  /* Both direct .nav-gear buttons and the .nav-gear trigger nested inside
     .level-picker/.portal-lang-switcher get resized — targets every icon
     in the row uniformly regardless of its wrapper markup. */
  html:not(.sidebar-collapsed) .account-icons .nav-gear {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
  }
  html:not(.sidebar-collapsed) .account-icons > * {
    flex-shrink: 0;
  }
  /* The wrap is 42px (needed collapsed, to match the 42px icon rail
     there) but the icons below are now resized to 32px — a 42px wrap
     staying centered would put the avatar circle's own center ~5px off
     from ABC's (the first icon's) center below it, since the two boxes
     are different widths even when left-aligned. Shrink the wrap to the
     same 32px as the icons (still centering the 24px circle inside it)
     so both circles share one true center axis, not just a left edge —
     matters because the icon's gold hover ring is centered on the icon,
     not flush to its edge. */
  html:not(.sidebar-collapsed) .account-avatar-wrap {
    width: 32px;
  }
}

.account-card {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.35rem;
  /* Left inset matches .nav-link's own horizontal padding (1.1rem) so the
     avatar sits on the same content axis as the nav links above, both
     measured from .platform-sidebar's shared 1.5rem edge padding. Desktop
     gets the full symmetric .nav-link padding box below (>768px); mobile's
     own centered treatment lives in the 768px drawer media query. */
  padding: 0.4rem 0 0.4rem 1.1rem;
  min-width: 0;
}

/* Desktop only — matches .nav-link's padding box (0.8rem 1.1rem) exactly,
   so the avatar+email row fills out identically to Learn/Theory/etc.
   instead of just sharing a left inset. Mobile (<=768px) keeps its own
   centered treatment below, untouched by this. */
@media (min-width: 769px) {
  .account-card {
    /* No left padding — the avatar now starts flush at the row's own
       left edge, same as ABC (the first icon) below it now that
       .account-icons fills edge to edge with space-between. */
    padding: 0.8rem 1.1rem 0.8rem 0;
  }
}

.account-avatar-wrap {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.account-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg-main);
  font-size: var(--type-xs);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  text-transform: uppercase;
}

.account-email {
  /* Was flex:1 (stretch to fill the row) — that leaves no free space for
     .account-card's justify-content:center to center into. Capped so
     avatar+email center as one compact block instead of the email
     spanning the sidebar's full width; ellipsis truncation (below)
     still applies for long addresses. */
  flex: 0 1 auto;
  max-width: 190px;
  font-family: var(--font-main);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Collapse lives in the account menu. Only the rail has a direct expand
   control at the top, using a panel icon rather than a navigation arrow. */
.sidebar-toggle {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border-radius: 8px;
  display: none;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
  z-index: 10;
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}
.sidebar-toggle svg {
  width: 20px;
  height: 20px;
}
.sidebar-toggle:hover {
  background: rgba(216, 168, 79, 0.12);
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.3);
}
.sidebar-collapsed .sidebar-header {
  flex-direction: column;
  gap: 0.5rem;
  margin: -0.5rem 0 1rem;
}
.sidebar-collapsed .sidebar-toggle {
  display: flex;
}
.sidebar-collapsed .platform-sidebar {
  width: 88px;
  padding-left: 1rem;
  padding-right: 1rem;
  /* Collapsed content (logo + icon rail) always fits well under 100vh —
     no internal scrollbar here, unlike the expanded sidebar which keeps
     overflow-y:auto as a safety net for the full nav list. */
  overflow-y: visible;
}
.sidebar-collapsed .platform-logo {
  justify-content: center;
  margin-left: 0;
}
.sidebar-collapsed .platform-logo-full,
.sidebar-collapsed .platform-nav,
.sidebar-collapsed .account-email {
  display: none;
}
.sidebar-collapsed .platform-logo-icon {
  display: block;
}
.sidebar-collapsed .account-card {
  justify-content: center;
  /* Desktop's expanded-only rule (0.8rem 1.1rem 0.8rem 0) exists to align
     the avatar with nav-link text and pad the row like a nav item when
     expanded. Collapsed, that horizontal padding skews the centering
     off-axis from .account-icons (which has none), and the vertical
     padding stacks on top of .platform-account's own 0.6rem gap — so the
     avatar ends up ~22px from ABC below it, nearly 3x .account-icons'
     own 0.5rem inter-icon gap. Zero all of it so avatar and icons share
     one true center line AND the same vertical rhythm as the icon rail. */
  padding: 0;
}
.sidebar-collapsed .account-icons {
  /* 4 icons no longer fit one row at 88px wide, and a 2x2 grid still ran
     wider than the collapsed rail — stack them instead. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

/* Language toggle */
.lang-toggle {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  align-items: flex-start;
}

.lang-btn {
  background: none;
  border: none;
  font-family: var(--font-main);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0.45rem 1rem;
  border-radius: 8px;
  text-align: left;
  transition: background 0.25s ease, color 0.25s ease;
}

.lang-btn:hover {
  background: rgba(216, 168, 79, 0.08);
  color: var(--text-main);
}

.lang-btn.active {
  background: rgba(216, 168, 79, 0.12);
  color: var(--accent);
}

/* Settings gear icon + logout icon */
.nav-gear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 10px;
  color: var(--text-muted);
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}

/* Every icon in the account-icons row shares this hitbox — pin the SVG
   render size here so artwork drawn at any inline viewBox/width still
   comes out visually equal, instead of trusting each icon's own markup. */
.nav-gear svg {
  width: 20px;
  height: 20px;
  stroke: currentColor;
}

.nav-gear:hover {
  background: rgba(216, 168, 79, 0.08);
  color: var(--accent);
}

/* Theme toggle — sun (light) / moon (dark) swap via [data-theme] on <html> */
.theme-toggle .icon-sun {
  display: none;
}
:root[data-theme="light"] .theme-toggle .icon-sun {
  display: block;
}
:root[data-theme="light"] .theme-toggle .icon-moon {
  display: none;
}

/* Notation toggle — cycles ABC / Do / 123, shown as text.
   Shares typography with .portal-lang-switcher .lang-trigger below so
   ABC and EN carry the same optical weight as the 20px SVG icons;
   font-size is a touch smaller than EN's since 3 characters read
   larger than 2 at the same size. */
.notation-toggle {
  font-family: var(--font-main);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.nav-gear.active {
  background: rgba(216, 168, 79, 0.15);
  color: var(--accent);
}

/* Footer language switch — compact "EN ▾" trigger in the account
   utility row, same visual pattern as the main site's nav trigger
   (.lang-trigger in styles.css), scoped here since the portal only
   supports EN/NL (settings.html's .lang-toggle-row below), not the
   site's 5 languages. The button carries both .nav-gear (42×42 box,
   centering — matches ABC/settings/logout) and .lang-trigger (text
   styling only) so it sits at the same size as its neighbors. */
.portal-lang-switcher {
  position: relative;
  display: inline-flex;
}

.portal-lang-switcher .lang-trigger {
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  gap: 0.2em;
}

/* Hover is inherited from .nav-gear:hover — no language-specific
   treatment. Open state still needs to read as active. */
.portal-lang-switcher .lang-trigger[aria-expanded="true"] {
  color: var(--accent);
}

/* Dropdown arrow removed — active language shows as a bare 2-letter
   code (e.g. "EN"); the trigger's own hover/focus state is what
   signals it's interactive. Element kept in the DOM (JS/markup
   untouched) but visually hidden rather than deleted. */
.portal-lang-switcher .lang-trigger-caret {
  display: none;
}

/* Opens upward — this trigger sits in the last footer row, close to
   the bottom of the sidebar/drawer, so a downward menu would usually
   run off screen. */
.portal-lang-switcher .lang-menu {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 1200;
  min-width: 70px;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: #0a0a0a;
  border: 1px solid rgba(216, 168, 79, 0.25);
  border-radius: 6px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
}

.portal-lang-switcher .lang-option {
  padding: 6px 10px;
  font-family: var(--font-main);
  font-size: 0.8125rem;
  color: var(--text-muted);
  text-align: center;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.portal-lang-switcher .lang-option:hover,
.portal-lang-switcher .lang-option:focus-visible {
  background: rgba(216, 168, 79, 0.12);
  color: var(--accent);
  outline: none;
}

.portal-lang-switcher .lang-option.active {
  color: var(--accent);
  font-weight: 600;
}

/* Language toggle (settings page) */
.lang-toggle-row {
  display: flex;
  gap: 0.6rem;
}

/* Level toggle (settings page) */
.level-toggle {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.level-btn {
  background: var(--bg-card);
  border: 1px solid rgba(255, 255, 255, 0.08);
  font-family: var(--font-main);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0.9rem 1.6rem;
  border-radius: var(--radius-small);
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}

.level-btn:hover {
  color: var(--text-main);
  border-color: rgba(216, 168, 79, 0.3);
}

.level-btn.active {
  background: rgba(216, 168, 79, 0.15);
  border-color: var(--accent);
  color: var(--accent);
}

.level-btn.level-locked {
  opacity: 0.45;
  cursor: default;
  pointer-events: none;
  position: relative;
}

/* Sidebar quick-access level picker — same .level-btn control as
   Settings > Level, just reachable without leaving the page. level.js
   wires these up automatically (it binds every .level-btn on load). */
.level-picker {
  position: relative;
}

.level-picker-popover {
  position: absolute;
  bottom: calc(100% + 0.5rem);
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 150px;
  padding: 0.5rem;
  background: var(--bg-card);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-small);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  z-index: 210;
}

.level-picker-popover[hidden] {
  display: none;
}

.level-picker-popover .level-btn {
  padding: 0.55rem 0.8rem;
  font-size: 0.85rem;
  text-align: left;
}

/* Label comes from data-soon, set by level.js from the "coming-soon" i18n
   key — a hardcoded content: string here would stay English for NL users
   (the rest of the portal uses data-i18n="coming-soon" → "binnenkort"). */
.level-btn.level-locked::after {
  content: attr(data-soon);
  display: block;
  font-size: 0.65rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin-top: 3px;
  text-transform: uppercase;
}

.theory-mobile-contents {
  display: none;
}

/* ==========================================================
   CONTENT AREA
   ========================================================== */

.platform-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: var(--bg-main);
  min-width: 0;
}


/* ==========================================================
   TOPBAR
   ========================================================== */

.platform-topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.5rem 3rem;
  background: #141414;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  gap: 1rem;
}

.topbar-left,
.topbar-right {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.platform-search {
  padding: 0.7rem 1rem;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: #111;
  color: var(--text-main);
  width: 280px;
  font-family: var(--font-main);
  font-size: 0.9rem;
}

.platform-search:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.1);
}

.platform-action {
  background: var(--accent);
  color: #111;
  border: none;
  padding: 0.7rem 1.3rem;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-weight: 600;
  font-family: var(--font-main);
  font-size: 0.9rem;
  transition: opacity 0.2s ease;
}

.platform-action:hover {
  opacity: 0.85;
}


/* ==========================================================
   PAGE CONTENT
   ========================================================== */

.platform-page {
  /* The one place the portal's left content edge is set. Everything on a
     page starts here; see --page-gutter in :root for the contract and for
     the breakpoints, which now live on the token rather than being
     re-declared as four competing .platform-page rules. */
  padding: var(--page-pad-y) var(--page-gutter) min(var(--page-pad-y), var(--page-pad-bottom));
}

.platform-page h1 {
  font-family: var(--font-serif);
  font-size: var(--type-display);
  margin: 0 0 var(--page-title-gap);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.platform-page h1::after {
  content: "";
  display: block;
  width: 72px;
  height: 2px;
  margin: 0.9rem 0 0;
  background: linear-gradient(to right, var(--accent), transparent);
}

.platform-page h3.theory-lesson-title,
.platform-page h2 {
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  margin: 2.5rem 0 1rem;
  padding: 0;
  text-align: left;
}

.platform-page h2::after {
  display: none;
}

.page-intro {
  max-width: 600px;
  margin: 0 0 2.5rem;
  color: var(--text-muted);
  line-height: 1.7;
  font-size: var(--type-body);
}


/* ==========================================================
   TABS
   ========================================================== */

.platform-tabs {
  display: flex;
  gap: 2rem;
  margin-bottom: 2rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  padding-bottom: 0;
}

.platform-tabs button {
  background: none;
  border: none;
  border-bottom: 1.5px solid transparent;
  font-size: var(--type-small);
  cursor: pointer;
  padding: 0 0 0.6rem;
  color: var(--text-muted);
  font-family: var(--font-main);
  transition: color 0.2s ease, border-color 0.2s ease;
  margin-bottom: -1px;
}

.platform-tabs button:hover {
  color: var(--text-main);
}

/* LEVEL 1 (main tabs) â€” same recipe as .fb-tab.active: gold text + thin
   full-width gold underline. */
.platform-tabs button.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}


.lesson-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}


/* ==========================================================
   ADMIN: LIBRARY -> SONG TOOL
   ----------------------------------------------------------
   The Edit action sits beside the card rather than inside it: a link
   nested in a button is invalid markup and costs the card its keyboard
   behaviour. The row is the positioning context; the card keeps every
   style it had.
   ========================================================== */

.song-row { position: relative; }

.song-edit {
  position: absolute;
  top: .85rem;
  right: 1rem;
  z-index: 1;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .02em;
  padding: .32rem .7rem;
  border-radius: var(--radius-pill, 999px);
  border: 1px solid rgba(255, 255, 255, .18);
  color: var(--text-muted);
  text-decoration: none;
  background: rgba(0, 0, 0, .25);
  white-space: nowrap;
}

.song-edit:hover,
.song-edit:focus-visible {
  color: var(--bg-main);
  background: var(--accent);
  border-color: var(--accent);
}

/* An unfinished song says so, in the library and in its own detail. */
.lesson-tag--draft {
  border-color: var(--accent);
  color: var(--accent);
}

/* The same badge, three review states. Draft is the quiet one: a chart
   that is not finished yet is the normal condition of a chart being
   written, not a warning. Ready stops looking like a caution the moment
   it stops being one. */
.lesson-tag--draft.lesson-tag--draft {
  border-color: rgba(128, 128, 128, 0.28);
  color: var(--text-muted);
}
.lesson-tag--needs_review.lesson-tag--needs_review {
  border-color: var(--accent);
  color: var(--accent);
}
.lesson-tag--ready.lesson-tag--ready {
  border-color: var(--ok, #4c9a6a);
  color: var(--ok, #4c9a6a);
}

.scanned-actions { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }

.scanned-edit {
  display: inline-flex;
  align-items: center;
  gap: .35em;
  text-decoration: none;
}

@media (max-width: 640px) {
  .song-edit { position: static; display: inline-block; margin: .5rem 0 0 1.8rem; }
}

/* ==========================================================
   LESSON ITEMS
   ========================================================== */

.platform-list,
.lesson-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.lesson-item {
  display: flex;
  gap: 1.5rem;
  align-items: center;
  padding: 1.4rem 1.8rem;
  background: linear-gradient(145deg,
    rgba(255, 255, 255, 0.03),
    rgba(255, 255, 255, 0.01));
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.06);
  text-decoration: none;
  color: var(--text-main);
  transition: transform 0.25s ease, border-color 0.25s ease;
}

.lesson-item:hover {
  transform: translateY(-3px);
  border-color: rgba(216, 168, 79, 0.4);
}

.lesson-thumb {
  width: 120px;
  height: 68px;
  background: #1d1d1d;
  border-radius: 8px;
  flex-shrink: 0;
}

.lesson-thumb--pdf {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
  background: rgba(216, 168, 79, 0.08);
  border: 1px solid rgba(216, 168, 79, 0.2);
}

.lesson-info {
  flex: 1;
}

.lesson-info h3 {
  margin: 0 0 0.4rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-main);
}

.lesson-info p {
  margin: 0;
  font-size: 0.875rem;
  color: var(--text-muted);
  line-height: 1.5;
}


/* ==========================================================
   LEARN â€” FILTERS
   ========================================================== */

.learn-filters {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-bottom: 2.2rem;
}

.filter-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.filter-label {
  width: 64px;
  flex-shrink: 0;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.filter-chip {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: var(--radius-small);
  /* padding/font-size: canonical LEVEL 3 values, see the shared
     .tab-filter group. */
  font-family: var(--font-main);
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.filter-chip:hover {
  color: var(--text-main);
  border-color: rgba(216, 168, 79, 0.4);
}

/* LEVEL 3 (filter chips) canonical active state â€” same dark gold tint as
   .scale-btn.active, not a filled pill. */
.filter-chip.active {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.15);
  border-color: rgba(216, 168, 79, 0.4);
  font-weight: 600;
}

.learn-empty {
  color: var(--text-muted);
  font-size: 0.95rem;
  padding: 1.5rem 0;
}

/* Learn is intentionally in its coming-soon phase. Offer one useful route
   back into the available curriculum so the page is never a dead end. */
.learn-available-card {
  display: block;
  max-width: 680px;
  margin-top: 2.25rem;
  padding: 1.5rem 1.7rem;
  border: 1px solid rgba(216, 168, 79, 0.25);
  border-radius: 14px;
  background: linear-gradient(145deg, rgba(216, 168, 79, 0.09), rgba(216, 168, 79, 0.02));
  color: inherit;
  text-decoration: none;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.learn-available-card:hover,
.learn-available-card:focus-visible {
  transform: translateY(-2px);
  border-color: rgba(216, 168, 79, 0.55);
  background: linear-gradient(145deg, rgba(216, 168, 79, 0.14), rgba(216, 168, 79, 0.035));
  outline: none;
}
.learn-available-card h2 {
  margin: 0.45rem 0 0.55rem;
  font-size: 1.2rem;
  color: var(--text-main);
}
.learn-available-card p {
  max-width: 560px;
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.55;
}
.learn-available-go {
  display: inline-block;
  margin-top: 1rem;
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 600;
}


/* ==========================================================
   LEARN â€” LESSON CARDS (extends .lesson-item)
   ========================================================== */

.lesson-item {
  width: 100%;
  text-align: left;
  font-family: var(--font-main);
  cursor: pointer;
}

.lesson-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.7rem;
}

.lesson-tag {
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: var(--radius-pill);
  padding: 0.2rem 0.6rem;
}


/* ==========================================================
   LEARN â€” LESSON DETAIL
   ========================================================== */

.lesson-back {
  background: none;
  border: none;
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: 0.9rem;
  cursor: pointer;
  padding: 0;
  margin-bottom: 1.5rem;
  transition: color 0.2s ease;
}

.lesson-back:hover {
  color: var(--accent);
}

.lesson-detail-head {
  margin-bottom: 1.5rem;
}

.lesson-detail-head h1 {
  margin-bottom: 0.8rem;
}

.lesson-video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-media);
  overflow: hidden;
  margin-bottom: 1.8rem;
  background: #1d1d1d;
  border: 1px solid rgba(255, 255, 255, 0.06);
}

.lesson-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.lesson-video--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.lesson-explanation {
  max-width: 680px;
  color: var(--text-main);
  line-height: 1.7;
  font-size: 0.95rem;
  margin-bottom: 1.8rem;
}

.lesson-explanation p {
  margin: 0 0 1rem;
}

.lesson-resources {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
  margin-bottom: 2.2rem;
}

.lesson-resource {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.1rem;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-small);
  color: var(--text-main);
  text-decoration: none;
  font-size: 0.9rem;
  transition: border-color 0.2s ease;
}

.lesson-resource:hover {
  border-color: rgba(216, 168, 79, 0.4);
}

.lesson-resource-icon {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.lesson-detail-foot {
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  padding-top: 1.5rem;
  display: flex;
  justify-content: flex-end;
}

.lesson-next {
  background: var(--accent);
  color: #0f0f0f;
  border: none;
  border-radius: var(--radius-pill);
  padding: 0.7rem 1.4rem;
  font-family: var(--font-main);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.lesson-next:hover {
  opacity: 0.88;
}

.lesson-next--end {
  background: none;
  color: var(--text-muted);
  font-weight: 400;
}

.lesson-resource--empty {
  color: var(--text-muted);
  cursor: default;
}

.lesson-resource--empty:hover {
  border-color: rgba(255, 255, 255, 0.08);
}


/* ==========================================================
   SONGS â€” DETAIL
   ========================================================== */

.song-artist {
  margin: -0.4rem 0 0.8rem;
  color: var(--text-muted);
  font-size: 0.95rem;
}

.lesson-detail h2 {
  font-size: 1.1rem;
  margin: 0 0 1rem;
}

.song-versions {
  margin-bottom: 2.2rem;
}

.song-version-toggle {
  display: inline-flex;
  gap: 0.3rem;
  padding: 0.25rem;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-pill);
  margin-bottom: 1.2rem;
}

.song-version-toggle button {
  background: none;
  border: none;
  border-radius: var(--radius-pill);
  padding: 0.45rem 1.1rem;
  font-family: var(--font-main);
  font-size: 0.85rem;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}

.song-version-toggle button.active {
  color: #0f0f0f;
  background: var(--accent);
  font-weight: 600;
}

.song-playalong h2 {
  margin-top: 0.5rem;
}

.song-track {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.9rem 1.1rem;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-small);
  margin-bottom: 0.8rem;
  font-size: 0.9rem;
}

.song-track audio {
  height: 36px;
}

.song-track--empty em {
  color: var(--text-muted);
  font-size: 0.8rem;
}


/* ==========================================================
   PRACTICE â€” SKILL DRILLS
   ========================================================== */

.drill-sub {
  margin: 0 0 1.2rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.drill-skill-tag {
  display: inline-block;
  vertical-align: middle;
  margin-left: 0.5rem;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(216, 168, 79, 0.1);
  border: 1px solid rgba(216, 168, 79, 0.25);
  border-radius: var(--radius-pill);
  padding: 0.15rem 0.55rem;
}

.drill-placeholder {
  opacity: 0.72;
}

.drill-soon {
  margin: 0;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Ear training */
.ear-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 1.3rem;
}

@media (max-width: 480px) {
  .practice-page #practice-panel-ear .ear-controls {
    flex-wrap: nowrap;
    justify-content: center;
    gap: 0.3rem;
  }
  /* Rhythm Recognition's transport row is a 3-column grid on desktop (see
     above), which spreads icon-only buttons across full-width columns
     with large gaps between them. On phones, switch it to the same
     compact centered flex row Ear Training uses so the icons sit close
     together instead of scattered. */
  .practice-page #tool-rhythm-trainer .ear-controls {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    gap: 0.3rem;
  }
  /* Icon+label buttons (.btn-icon-text, used across Practice's widgets —
     Play / Replay / Slow / Start / Stop / …) collapse to icon-only
     squares on phones; the label stays visible on tablet/desktop where
     the row has room. data-i18n-title on the button still supplies the
     tooltip/aria-label so the accessible name survives losing the
     visible text. Page-wide so every widget gets the same treatment
     automatically just by using the .btn-icon-text/.btn-label markup. */
  .practice-page .btn-icon-text {
    width: 44px;
    height: 44px;
    padding: 0;
  }
  .practice-page .btn-label {
    display: none;
  }
  .practice-page #crOptions.ear-options {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  /* Rhythm Recognition/Reading answer buttons each hold a full 8-count
     row ("1 & 2 & 3 & 4 &") — forced to 3-per-row like every other
     .ear-options grid left too little width per button on mobile for
     that many glyphs to read cleanly. Drop to 2-per-row here too. */
  .practice-page #rhythmtOptions.ear-options,
  .practice-page #rhythmReadOptions.ear-options {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.ear-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
  gap: 0.55rem;
  margin-bottom: 1.3rem;
}

/* TAB Trainer note-name options: always 4 per row (the auto-fit
   default varies with card width, landing on 3 in some layouts). */
#tabTrainerOptions.ear-options {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

#tabTrainerOptions .ear-option {
  flex: 0 0 calc((100% - 1.65rem) / 4);
}

/* Sized to match the Drone's root-note keypad (.drone-note) — same
   padding/font-size/weight, so the note-name chips read as the same
   "pick a note" control wherever they appear. Padding keeps the
   top-heavy nudge (more top than bottom) that centers the label inside
   the button, just scaled up proportionally from the old 1/0.4 split. */
#tabTrainerOptions .ear-option {
  padding: 1.1rem 0 0.7rem;
  font-size: 1.05rem;
  font-weight: 700;
}

/* Rhythm Reading Trainer count-pattern options: always 3 per row. */
#rhythmReadOptions.ear-options {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

#rhythmReadOptions .ear-option {
  min-width: 0;
  min-height: 2.65rem;
  padding-block: 0.65rem;
  padding-inline: 0.2rem;
  font-size: 0.76rem;
  white-space: nowrap;
}

/* Chord Trainer answers are primary exercise controls, not filter chips.
   Give roots the same balanced 4+3 keypad as the TAB note answers and
   make the three chord qualities equally prominent and touch-friendly. */
#diagramAnswerRootBtns {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 0.5rem;
  width: 100%;
}
#diagramAnswerRootBtns .metro-opt-btn {
  grid-column: span 2;
  min-height: 2.3rem;
  padding-block: 0.4rem;
  font-size: 0.95rem;
  font-weight: 700;
}
#diagramAnswerRootBtns .metro-opt-btn:nth-child(5) { grid-column: 2 / span 2; }
#diagramAnswerRootBtns .metro-opt-btn:nth-child(6) { grid-column: 4 / span 2; }
#diagramAnswerRootBtns .metro-opt-btn:nth-child(7) { grid-column: 6 / span 2; }

#diagramAnswerQualityBtns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  width: 100%;
}
#diagramAnswerQualityBtns .metro-opt-btn {
  min-width: 0;
  min-height: 2.3rem;
  padding-block: 0.4rem;
  padding-inline: 0.35rem;
  font-size: 0.76rem;
  white-space: nowrap;
}

@media (min-width: 1201px) {
  #practice-panel-reading .drill-sub {
    min-height: 3em;
  }

  /* Equal-height cards should spend their spare room on the exercise,
     not leave an arbitrary gap above the score. Give TAB, chord diagram
     and rhythm notation one shared presentation zone; answers then start
     on the same visual baseline and feedback remains anchored below. */
  #tabTrainerTab.reading-tab-example,
  #tool-diagram-trainer .cswitch-display,
  #rhythmReadGlyphs.rhythmp-glyphs {
    min-height: 150px;
    margin-block: 0.45rem 0.85rem;
    align-items: center;
    justify-content: center;
  }

  #tabTrainerTab.reading-tab-example {
    display: flex;
  }
}

/* Reading cards contain dense answer grids. Use the compact spacing tier
   here so the full three-card row remains visible without shrinking the
   actual notation or answer text. */
#practice-panel-reading .tool-card {
  padding-block: 1.1rem;
}
#practice-panel-reading .tool-title {
  margin-bottom: 0.9rem;
}
#practice-panel-reading .drill-sub {
  margin-bottom: 0.85rem;
}
#practice-panel-reading .ear-controls {
  margin-bottom: 0.85rem;
}
#practice-panel-reading .ear-options {
  margin-bottom: 0.85rem;
}
#practice-panel-reading .ear-status {
  padding-top: 0.55rem;
}

/* Clapping/Tapping: empty spacer between the hint and the BPM bar so the
   BPM bar sits at the same height as Strumming Patterns' BPM bar (which
   has a pattern picker + strum-arrows row above it) — height picked to
   roughly match that combined block. */
.clap-bpm-spacer {
  min-height: 7.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}
.clap-count-in {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.45rem;
  width: min(210px, 100%);
}
.clap-count-in span {
  display: grid;
  place-items: center;
  min-height: 2.1rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius-small);
  color: var(--text-muted);
  font-size: 0.78rem;
  font-weight: 600;
  transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease;
}
/* Beat indicator: JS toggles .active on the current beat's box in time
   with the count-in click and the record-phase beats — these four boxes
   now do the job the separate .metro-dot row used to (removed), so the
   only beat readout on screen is the one that actually updates. */
.clap-count-in span.active {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.5);
  background: rgba(216, 168, 79, 0.1);
}

/* On the multi-card desktop/tablet layout, give Clapping's count-in visual
   enough room to align its tempo controls with Strumming's BPM zone. */
@media (min-width: 761px) {
  #tool-clap-tap .metro-bpm-row,
  #tool-strum-patterns .metro-bpm-row {
    margin-top: auto;
  }
  /* The score belongs beneath Clapping's controls, not as a second flexible
     gap. Both tempo blocks can therefore anchor to the card's lower zone. */
  #tool-clap-tap .ear-status {
    margin-top: 0;
    padding-top: 0.55rem;
    min-height: 2.6rem;
  }
  /* Reserve the same compact score space under Strumming as Clapping has. */
  #tool-strum-patterns .metro-row {
    /* Timing has a result meter and score below Start. Reserving the same
       lower zone here keeps both BPM sliders and Start buttons on one
       shared baseline while the timing meter occupies that zone next door. */
    margin-bottom: 4.225rem;
  }
}
.cswitch-count-in {
  align-self: center;
  margin: 0.25rem 0 0.75rem;
}
.scale-count-in {
  align-self: center;
  margin: 0.35rem 0 0.75rem;
}
.arpeggio-count-in {
  align-self: center;
  margin: 0.35rem 0 0.75rem;
}

/* ---- Practice Timing: early/late meter ----
   Same visual language as the Tuner's cents meter (.tuner-track/.tuner-
   zone/.tuner-needle) — a center "on beat" zone with a needle that moves
   left (early) or right (late) per tap, so timing is something you SEE,
   not just a millisecond number after the round ends. */
.clap-timing-meter {
  width: 100%;
  margin: 0;
}
/* THE CENTRE TICK IS PART OF THE BOX, NOT SOMETHING HANGING OUT OF IT.
   The tick deliberately overhangs the 4px rail by 5px above and below.
   It used to do that by escaping its own element, which left the track
   reporting content outside its bounds; the drawing is unchanged, but
   the 5px is now real padding, cancelled again by a negative margin so
   the meter occupies exactly the height it always did. */
.clap-timing-track {
  position: relative;
  height: 14px;
  padding-block: 5px;
  background: rgba(255, 255, 255, 0.07);
  background-clip: content-box;
  border-radius: 99px;
  margin: -5px 0 calc(0.4rem - 5px);
}
.clap-timing-track::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 0;
  width: 1px;
  height: 100%;
  background: rgba(255, 255, 255, 0.18);
  transform: translateX(-50%);
}
.clap-timing-zone {
  position: absolute;
  left: calc(50% - 6%);
  top: 5px;
  bottom: 5px;
  width: 12%;
  background: rgba(72, 199, 116, 0.22);
  border-radius: 99px;
}
.clap-timing-needle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 12px;
  height: 12px;
  background: var(--accent);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  transition: left 0.08s ease, background 0.08s ease;
  box-shadow: 0 0 8px rgba(216, 168, 79, 0.5);
  opacity: 0;
}
.clap-timing-needle.is-visible { opacity: 1; }
.clap-timing-needle.is-early   { background: #5aa9e6; box-shadow: 0 0 8px rgba(90, 169, 230, 0.5); }
.clap-timing-needle.is-late    { background: #e0704f; box-shadow: 0 0 8px rgba(224, 112, 79, 0.5); }
.clap-timing-needle.is-onbeat  { background: #48c774; box-shadow: 0 0 8px rgba(72, 199, 116, 0.5); }

.clap-timing-labels {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.45rem;
  font-size: 0.68rem;
  color: var(--text-muted);
  opacity: 0.5;
  letter-spacing: 0.02em;
}
.clap-timing-labels span:nth-child(2) {
  opacity: 0.7;
}

/* Per-tap history strip — one dot per tap this round, left-to-right in
   tap order, colored/positioned the same early=blue/late=orange/on-
   beat=green as the needle, so a whole round's pattern (e.g. "I keep
   rushing") is visible at a glance, not just the single latest tap. */
.clap-timing-history {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  min-height: 0.6rem;
  margin-top: 0.5rem;
}
.clap-timing-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #48c774;
  flex-shrink: 0;
}
.clap-timing-dot.is-early  { background: #5aa9e6; }
.clap-timing-dot.is-late   { background: #e0704f; }

/* A bit more breathing room between the rhythm visualization and the
   answer options below it, and slightly larger glyphs. */
#rhythmReadGlyphs.rhythmp-glyphs {
  margin-bottom: 2rem;
  font-size: 1.65rem;
}
#rhythmReadGlyphs .rhythmp-glyph {
  width: 35px;
  height: 47px;
}

/* When all three Reading trainers sit side by side, give the compact
   rhythm notation the same visual zone as the TAB and chord diagram.
   This aligns the answer rows without adding extra rows or mobile height. */
@media (min-width: 1400px) {
  #rhythmReadGlyphs.rhythmp-glyphs {
    min-height: 150px;
    align-items: center;
  }
}

/* Technique: the four count-in beats sit between the pattern and tempo,
   consistent with the other tempo-based beginner exercises. */
.practice-page #practice-panel-technique .technique-count-in {
  margin: 0.75rem auto 0.9rem;
}

/* The cards have equal height on desktop. Let the tempo zone absorb any
   difference in instruction/picker height so all three BPM controls share
   one baseline. */
.practice-page #practice-panel-technique .metro-bpm-row {
  margin-top: auto;
}

@media (max-width: 760px) {
  .practice-page #practice-panel-technique .practice-3col {
    align-items: start;
  }

  .practice-page #practice-panel-technique .practice-3col > .tool-card {
    min-height: 0;
    height: auto;
  }
}

/* Rhythm Trainer count-pattern options: always 3 per row. */
#rhythmtOptions.ear-options {
  grid-template-columns: repeat(3, 1fr);
}

/* Rhythm Trainer uses the same compact listen controls as Ear Training.
   A three-column row keeps Play / Replay / Slow available without making
   the card taller at tablet widths. Buttons use the shared icon+label
   .btn-icon-text markup (see SHARED TOOL BUTTONS) so they size themselves
   like every other Practice widget — the old fixed tiny padding/font-size
   override is gone now that the label is a real element, not a stretched
   button. */
#tool-rhythm-trainer .ear-controls {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.35rem;
}
#tool-rhythm-trainer .ear-controls > button {
  min-width: 0;
}

/* Interval Recognition answer options: always 4 per row. */
#earOptions.ear-options {
  grid-template-columns: repeat(4, 1fr);
}

/* Scale families stay grouped by function: Major, Minor, Dominant on
   the first row and their pentatonic forms directly underneath. */
#scaleRecogOptions.ear-options {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

#scaleRecogOptions .ear-option {
  min-width: 0;
  padding-inline: 0.25rem;
  white-space: normal;
}

.ear-option {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius-small);
  padding: 0.7rem 0;
  font-family: var(--font-main);
  font-size: 0.95rem;
  color: var(--text-main);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.ear-option:hover:not(:disabled) {
  border-color: rgba(216, 168, 79, 0.5);
}

.ear-option:disabled {
  cursor: default;
}

.ear-option.correct {
  background: rgba(80, 200, 120, 0.18);
  border-color: rgba(80, 200, 120, 0.6);
  color: #8fe0a8;
}

.ear-option.wrong {
  background: rgba(220, 80, 80, 0.16);
  border-color: rgba(220, 80, 80, 0.55);
  color: #f0a0a0;
}

.ear-status {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  /* Pinned to the bottom of its (now flex-column) .tool-card, so the
     score row lines up at the same height across cards in a stretched
     grid row instead of trailing right after each card's own content. */
  margin-top: auto;
  padding-top: 0.9rem;
}

.ear-feedback {
  font-size: 0.9rem;
  font-weight: 600;
}

.ear-feedback.is-correct { color: #8fe0a8; }
.ear-feedback.is-wrong   { color: #f0a0a0; }

.ear-score {
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Rhythm tap-along drill */
.rhythm-controls {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 1.1rem;
}

.rhythm-bpm {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.rhythm-pad {
  width: 100%;
  min-height: 120px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius-media);
  color: var(--text-main);
  font-family: var(--font-main);
  cursor: pointer;
  transition: background 0.06s ease, border-color 0.06s ease, transform 0.06s ease;
}

.rhythm-pad:disabled {
  opacity: 0.5;
  cursor: default;
}

.rhythm-pad-label {
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.rhythm-pad-hint {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.rhythm-pad.beat {
  background: rgba(216, 168, 79, 0.18);
  border-color: rgba(216, 168, 79, 0.5);
  transform: scale(1.01);
}

.rhythm-feedback {
  min-height: 1.4em;
  margin: 1rem 0 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  text-align: center;
}

.rhythm-feedback.is-tight { color: #8fe0a8; }
.rhythm-feedback.is-late  { color: #f0c060; }
.rhythm-feedback.is-early { color: #f0a0a0; }

.rhythm-stats {
  display: flex;
  justify-content: space-between;
  gap: 0.8rem;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* Fretboard recall drill â€” SVG guitar neck */
.fbr-prompt {
  font-size: 1.1rem;
  margin-bottom: 1rem;
  color: var(--text-main);
}

.fbr-prompt strong {
  color: var(--accent);
  font-weight: 700;
}

.fbr-board {
  margin-bottom: 1.1rem;
  padding: 0.6rem 0.4rem;
  background: linear-gradient(180deg, rgba(40, 28, 18, 0.5), rgba(26, 20, 14, 0.5));
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: var(--radius-small);
}

.fbr-svg {
  display: block;
  width: 100%;
  height: auto;
}

.fbr-hit {
  fill: transparent;
  cursor: pointer;
}

.fbr-hit:hover {
  fill: rgba(216, 168, 79, 0.16);
}

.fbr-mark {
  pointer-events: none;
}

.fbr-mark.correct { fill: #5cc878; }
.fbr-mark.wrong   { fill: #e05555; }
.fbr-mark.reveal  { fill: var(--accent); }

.fbr-mark-text {
  pointer-events: none;
  fill: #141414;
}

.fbr-status {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.fbr-feedback {
  font-size: 0.9rem;
  font-weight: 600;
  flex: 1;
}

.fbr-feedback.is-correct { color: #8fe0a8; }
.fbr-feedback.is-wrong   { color: #f0a0a0; }

.fbr-score {
  font-size: 0.85rem;
  color: var(--text-muted);
}


/* ==========================================================
   PRACTICE â€” METRONOME TRAINER
   ========================================================== */

.metro-modes {
  display: inline-flex;
  gap: 0.3rem;
  padding: 0.25rem;
  margin-bottom: 1rem;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-pill);
}

.metro-mode-btn {
  background: none;
  border: none;
  border-radius: var(--radius-pill);
  padding: 0.4rem 0.95rem;
  font-family: var(--font-main);
  font-size: 0.82rem;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.metro-mode-btn:hover {
  color: var(--text-main);
}

.metro-mode-btn.active {
  background: var(--accent);
  color: #111;
  font-weight: 600;
}

.metro-advanced {
  margin: 0.4rem 0 0.8rem;
  font-family: var(--font-main);
}

.metro-advanced > summary {
  cursor: pointer;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: 0.4rem 0;
  list-style: none;
}

.metro-advanced > summary::-webkit-details-marker { display: none; }

.metro-advanced > summary::before {
  content: 'â–¸';
  display: inline-block;
  margin-right: 0.4rem;
  transition: transform 0.2s ease;
}

.metro-advanced[open] > summary::before {
  transform: rotate(90deg);
}

.metro-trainer-fields,
.metro-tap-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.2rem;
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}

.metro-tap-fields {
  flex-direction: column;
  align-items: stretch;
}

.metro-trainer-fields[hidden],
.metro-tap-fields[hidden] {
  display: none;
}

.metro-trainer-fields label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.metro-num {
  width: 58px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--radius-small);
  padding: 0.35rem 0.5rem;
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.85rem;
}

.metro-trainer-now {
  width: 100%;
  margin: 0.6rem 0 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent);
}


/* ==========================================================
   PRACTICE â€” PROGRESS
   ========================================================== */

.tracker-progress {
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  margin-top: 1.4rem;
  padding-top: 1.3rem;
}

.tracker-subtitle {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  font-weight: 600;
  margin: 0 0 1.1rem;
}

.progress-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin-bottom: 1.8rem;
}

.progress-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 90px;
}

.progress-num {
  font-size: 1.9rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1.1;
}

.progress-label {
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.progress-subtitle {
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 1rem;
}

.progress-bar-row {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin-bottom: 0.6rem;
  font-size: 0.85rem;
}

.progress-bar-name {
  width: 120px;
  flex-shrink: 0;
  color: var(--text-main);
}

.progress-bar-track {
  flex: 1;
  height: 8px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.progress-bar-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-pill);
}

.progress-bar-val {
  width: 44px;
  text-align: right;
  color: var(--text-muted);
  flex-shrink: 0;
}


/* ==========================================================
   DASHBOARD â€” control center
   ========================================================== */

.dash-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.3rem;
}

.dash-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.45rem;
  padding: 1.3rem 1.5rem;
  background: linear-gradient(145deg,
    rgba(255, 255, 255, 0.03),
    rgba(255, 255, 255, 0.01));
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 16px;
  text-decoration: none;
  color: var(--text-main);
  transition: transform 0.25s ease, border-color 0.25s ease;
}

a.dash-card:hover {
  transform: translateY(-3px);
  border-color: rgba(216, 168, 79, 0.4);
}

.dash-card--wide {
  grid-column: 1 / -1;
}

.dash-card--accent {
  background: linear-gradient(145deg,
    rgba(216, 168, 79, 0.12),
    rgba(216, 168, 79, 0.03));
  border-color: rgba(216, 168, 79, 0.28);
}

.dash-eyebrow {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.dash-card-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--text-main);
  letter-spacing: -0.01em;
}

.dash-card-title::after {
  display: none;
}

.dash-card-sub {
  margin: 0;
  font-size: 0.9rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.dash-go {
  margin-top: 0.4rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent);
}

@media (max-width: 768px) {
  .dash-grid {
    grid-template-columns: 1fr;
  }
}


/* ==========================================================
   COMING SOON
   ========================================================== */

.coming-soon {
  margin: 2rem 0 0;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.4;
}

.tool-card--soon {
  opacity: 0.5;
  align-items: flex-start;
  justify-content: center;
}

/* Full-width "coming soon" shell shaped like the eventual interactive
   fretboard widget (Notes/Scales/Chords), for drills that need hals-
   interaction rather than a multiple-choice picker. Stacked, not in a
   3-col grid — same footprint each will have once built. */
.fbx-soon-widget {
  width: 100%;
  margin-bottom: 1.5rem;
}

.fbx-widget-placeholder {
  margin-top: 1rem;
  min-height: 180px;
  border: 1px dashed rgba(255, 255, 255, 0.12);
  border-radius: 12px;
  background: repeating-linear-gradient(90deg,
    rgba(255, 255, 255, 0.04) 0,
    rgba(255, 255, 255, 0.04) 1px,
    transparent 1px,
    transparent 64px);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
}

.section-coming-soon {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #a0a0a0;
  margin-left: 0.6rem;
  vertical-align: middle;
  line-height: 1;
  font-family: var(--font-main);
}

/* A section that exists but has no content yet. It leads with what the
   section will BE and carries the status as a small label above it —
   the reverse of a centred "nothing here" panel, which states the
   absence and says nothing about the promise. Left-aligned so it reads
   as one block with the h1 above it rather than as an island in a void,
   and without an icon: a stock glyph is the part that makes these pages
   look generic. */
.section-pending {
  max-width: 640px;
  padding: 0.5rem 0 4rem;
}

/* No rule under this label. The h1 above already draws the portal's gold
   gradient bar, and a second one 60px below it repeats the same motif
   rather than adding a level. */
.section-pending-status {
  margin: 0 0 0.9rem;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.85;
}

/* Kept in the serif so it reads as a statement rather than body copy,
   but clearly under the h1: smaller, and off full white. Matching the
   h1's weight and colour here gave the page two competing headings. */
.section-pending-lead {
  margin: 0;
  font-family: var(--font-serif);
  font-size: 1.45rem;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: rgba(var(--w), 0.84);
}

.section-pending-note {
  margin: 0.75rem 0 0;
  max-width: 46ch;
  color: var(--text-muted);
  font-size: 0.92rem;
  line-height: 1.75;
}

@media (max-width: 480px) {
  .section-pending-lead {
    font-size: 1.2rem;
  }
}


/* ==========================================================
   DASHBOARD â€” HOME v2
   ========================================================== */

/* Continue card â€” full-width lesson-item with accent treatment */
.lesson-item--continue {
  border-color: rgba(216, 168, 79, 0.25);
  background: linear-gradient(145deg,
    rgba(216, 168, 79, 0.07),
    rgba(216, 168, 79, 0.02));
}

.lesson-item--continue:hover {
  border-color: rgba(216, 168, 79, 0.5);
}

.home-item-cta {
  flex-shrink: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent);
  margin-left: auto;
  white-space: nowrap;
}

/* Muted placeholder cards */
.home-item--muted {
  opacity: 0.45;
  pointer-events: none;
}

/* Light theme — card backgrounds/borders below are white-tint overlays
   tuned for a dark page; on a white background they're nearly invisible,
   and the already-faint muted opacity becomes unreadable. */
:root[data-theme="light"] .lesson-item,
:root[data-theme="light"] .home-tool {
  background: linear-gradient(145deg, rgba(0, 0, 0, 0.035), rgba(0, 0, 0, 0.012));
  border-color: rgba(0, 0, 0, 0.16);
}

:root[data-theme="light"] .lesson-item--continue {
  border-color: rgba(216, 168, 79, 0.45);
  background: linear-gradient(145deg, rgba(216, 168, 79, 0.12), rgba(216, 168, 79, 0.04));
}

:root[data-theme="light"] .home-item--muted {
  opacity: 0.65;
}

/* 2-col grid for recent section */
.home-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

/* 3-col grid for tools */
.home-3col {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

/* Tool tiles */
.home-tool {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1.4rem 1.6rem;
  background: linear-gradient(145deg,
    rgba(255, 255, 255, 0.03),
    rgba(255, 255, 255, 0.01));
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 14px;
  text-decoration: none;
  color: var(--text-main);
  transition: transform 0.25s ease, border-color 0.25s ease;
}

.home-tool:hover {
  transform: translateY(-3px);
  border-color: rgba(216, 168, 79, 0.35);
}

.home-tool-name {
  font-size: var(--type-item);
  font-weight: 600;
  color: var(--text-main);
}

.home-tool-desc {
  font-size: var(--type-small);
  color: var(--text-muted);
  line-height: 1.4;
}

@media (max-width: 900px) {
  .home-2col {
    grid-template-columns: 1fr;
  }

  .home-3col {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 600px) {
  .home-3col {
    grid-template-columns: 1fr;
  }
}


/* ==========================================================
   HOME — hero / how-it-works / beginner path / tool cards
   ========================================================== */

/* Extra specificity needed: .platform-page h2 already styles every heading
   with a fixed size + gold underline bar — this override has to out-rank
   that (element + class) selector, not just add to it.

   h1 is NOT in this list any more. The Dashboard's hero is the page's own
   h1, and it now carries the same gold gradient every other portal title
   carries — one h1 treatment, no page-specific exception. Only h2 stays
   suppressed here: the accent is a page-title identity mark and section
   headings must never inherit it. */
.home-page.platform-page h2::after {
  display: none;
}

.home-page section + section {
  margin-top: 3.1rem;
}

.home-page .home-section-title {
  margin: 0 0 1.6rem;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-main);
  text-transform: none;
  letter-spacing: normal;
}

/* ── Hero ──
   640px cut the supporting sentence in half: it wants 715px on one line
   in both EN and NL, so every wide desktop drew it as two. The cap is
   still a READING MEASURE, not the content column — the sentence sets
   it, and 760px is that plus a little slack for a future rewording. The
   title is unaffected either way: it breaks on its own <br>s. ── */
.home-hero {
  max-width: 760px;
  padding-top: 0.5rem;
}

.home-page .home-hero-title {
  margin: 0 0 0.9rem;
  font-family: var(--font-serif);
  font-size: 2.1rem;
  line-height: 1.25;
  font-weight: 700;
  color: var(--text-main);
  letter-spacing: -0.01em;
}

.home-hero-sub {
  margin: 0 0 1.25rem;
  font-size: 1.02rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.home-hero-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.home-hero-cta {
  display: inline-block;
  padding: 0.85rem 1.7rem;
  border-radius: 10px;
  background: var(--accent);
  color: #1a1408;
  font-weight: 700;
  font-size: 0.95rem;
  text-decoration: none;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

.home-hero-cta:hover {
  transform: translateY(-2px);
  opacity: 0.92;
}

.home-hero-link {
  color: var(--text-main);
  font-size: 0.92rem;
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid rgba(216, 168, 79, 0.55);
  padding: 0.35rem 0;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.home-hero-link:hover,
.home-hero-link:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  outline: none;
}

/* ── How it works ── */
.home-how-row {
  display: flex;
  align-items: flex-start;
  gap: 1.2rem;
}

.home-how-step {
  flex: 1;
  min-width: 0;
}

.home-how-num {
  display: block;
  font-family: var(--font-serif);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--accent);
  opacity: 0.8;
  margin-bottom: 0.4rem;
}

.home-how-title-sm {
  margin: 0 0 0.3rem;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-main);
}

.home-how-name {
  margin: 0 0 0.35rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-main);
  opacity: 0.85;
}

.home-how-desc {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--text-muted);
}

.home-how-arrow {
  flex-shrink: 0;
  align-self: center;
  font-size: 1.3rem;
  color: var(--accent);
  opacity: 0.5;
  /* Steps lost their middle line, so the arrow sits higher to stay level
     with them. */
  margin-top: 1.1rem;
}

/* Four steps need more room than three did. Measured: each step needs
   about 105px before its longest word ("Understand") starts overflowing
   its own column, which four of them stop getting at roughly 870px of
   viewport — so the row stacks from 960px down instead of 768px, with a
   margin. Same stacked treatment mobile already had; only the width at
   which it starts moved. */
@media (max-width: 960px) {
  .home-how-row {
    flex-direction: column;
    gap: 1.6rem;
  }

  .home-how-arrow {
    display: none;
  }
}

/* ── The active level's card ──
   Named .home-level-* rather than .home-beginner-*: one card, filled
   from whichever level the portal is currently showing (dashboard.js).
   The styling is unchanged — only the name stopped claiming it belongs
   to one level. ── */
.home-level-card {
  display: block;
  /* Was 2rem 2.2rem. The card is still the page's primary object — it
     just stopped reserving a third of its height for nothing. The
     horizontal figure then matched the vertical one, which is both
     tidier and the last ~10px the twelve-topic row needed to stay on
     one line at 1600 in Dutch as well as English. */
  padding: 1.6rem;
  border-radius: 18px;
  background: linear-gradient(145deg,
    rgba(216, 168, 79, 0.1),
    rgba(216, 168, 79, 0.02));
  border: 1px solid rgba(216, 168, 79, 0.25);
  color: inherit;
  text-decoration: none;
  transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.home-level-card:hover,
.home-level-card:focus-visible {
  transform: translateY(-2px);
  border-color: rgba(216, 168, 79, 0.55);
  background: linear-gradient(145deg,
    rgba(216, 168, 79, 0.14),
    rgba(216, 168, 79, 0.035));
  outline: none;
}

.home-page .home-level-title {
  margin: 0.3rem 0 0.5rem;
  font-size: 1.4rem;
  font-weight: 700;
  color: var(--text-main);
  text-transform: none;
  letter-spacing: normal;
}

.home-level-desc {
  margin: 0 0 1.05rem;
  max-width: 640px;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--text-muted);
}

/* Twelve topics is the longest list any level has (Beginner and
   Intermediate both), and at 1.2rem between items it needed 1429px —
   more than a 1600px window gives the card, so the last topic dropped
   to a line of its own. The bullets already separate the items, so the
   gap was doing work it did not need to do. Still wraps: this buys
   roughly 140px, it does not switch wrapping off. */
.home-level-topics {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem 0.7rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.home-level-topics li {
  display: inline-flex;
  align-items: center;
  gap: 0.38rem;
  padding: 0;
  /* 13.12px → 12.48px. Half a pixel, deliberately: the list has to read
     as the same quiet caption it always did, not as small print. */
  font-size: 0.78rem;
  color: var(--text-muted);
}

.strum-count-in {
  margin: 0.15rem auto 1rem;
}

.home-level-topics li::before {
  content: "";
  width: 0.32rem;
  height: 0.32rem;
  border-radius: 50%;
  background: rgba(216, 168, 79, 0.72);
  flex: 0 0 auto;
}

/* ── Level ladder: Beginner live, Intermediate/Advanced/Expert soon ── */
.home-levels-title {
  margin: 1.8rem 0 0.8rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted);
}

/* FOUR, because four is how many there are. The row lists the public
   ladder minus the level the card above already is — which has been
   four ever since Starter shipped, so a three-column grid left the last
   one stranded on a row of its own. */
.home-levels-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* Every rung the same height, including when the row folds to 2x2 and
     the live levels (no badge) would otherwise sit in a shorter grid row
     than the two that carry "Coming soon". */
  grid-auto-rows: 1fr;
  gap: 0.7rem;
}

/* Four cards stop being four cards and start being four slivers well
   before the phone breakpoint, so they pair up first. */
@media (max-width: 1200px) {
  .home-levels-row {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* THE LADDER — four cards with one structure, differing only in colour.
   Availability is a STATE, not a component: an available level has no
   badge and a level in progress has one, but both centre the same way,
   at the same size, so the row reads as one ladder rather than as two
   kinds of card that happen to sit together. Centring is on the base
   rule for exactly that reason — a modifier that centred only the live
   ones would put the difference back. */
.home-level-item {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  gap: 0.25rem;
  min-height: 3.4rem;
  padding: 0.7rem 0.9rem;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.07);
  text-decoration: none;
}

.home-level-name {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-main);
}

.home-level-badge {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.7;
}

/* Understated until touched. The gold WASH this replaces read as a
   filled status panel — the same visual weight as the feature card
   above it, for something that is only a way to change level. What is
   left is the row's own dark base and a restrained gold edge; the
   warmth arrives on hover instead of sitting there permanently.
   No transform: the small cards deliberately do not borrow the feature
   card's lift, which is what keeps them subordinate to it. */
.home-level-item--active {
  border-color: rgba(216, 168, 79, 0.32);
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.home-level-item--active:hover,
.home-level-item--active:focus-visible {
  background: rgba(216, 168, 79, 0.08);
  border-color: rgba(216, 168, 79, 0.6);
}

.home-level-item--active:hover .home-level-name,
.home-level-item--active:focus-visible .home-level-name {
  color: var(--text-main);
}

/* Hover is a hint; keyboard focus has to be unmistakable, so it keeps a
   real ring rather than relying on the background shift alone. */
.home-level-item--active:focus-visible {
  outline: 2px solid rgba(216, 168, 79, 0.7);
  outline-offset: 2px;
}

/* Intermediate/Advanced/Expert: visually secondary, not clickable
   (plain <div>, not <a> — no route to navigate into unfinished content). */
.home-level-item--soon {
  opacity: 0.62;
  cursor: default;
}

.home-level-item--soon .home-level-name {
  color: var(--text-muted);
}

/* The badge carries its own 0.7 opacity, which compounded with the
   card's into something close to unreadable. Inside a card that is
   already dimmed, it does not need to dim itself again. */
.home-level-item--soon .home-level-badge {
  opacity: 1;
}

:root[data-theme="light"] .home-level-item {
  background: rgba(0, 0, 0, 0.025);
  border-color: rgba(0, 0, 0, 0.1);
}

@media (max-width: 700px) and (orientation: portrait) {
  .home-levels-row {
    /* Three future levels read as a quiet roadmap, not a 2+1 orphan grid. */
    grid-template-columns: 1fr;
  }
}

/* ── Theory / Fretboard / Practice / Songs reuse .dash-grid / .dash-card.
   No override any more: three cards needed a 3-column rule, four fit the
   base grid's own two columns (and its one-column rule below 768px), so
   the section stopped having a layout of its own. ── */

/* ── More coming soon: Learn / Songs — deliberately smaller & muted so
   they never compete with the Theory/Fretboard/Practice cards above. ── */
/* ONE ITEM, ONE ROW. A two-column grid holding a single card drew a
   conspicuous empty half — the page looked like it had lost a card
   rather than like the roadmap is genuinely one item long. The card
   turns horizontal instead of stretching: same language, one line of
   height, and it wraps on its own when the row gets narrow. */
.home-soon-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

/* Plain <div>, not <a> — status-only, no route to navigate into
   unfinished content (same rationale as .home-level-item--soon). */
.home-soon-card {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.9rem;
  padding: 0.9rem 1.3rem;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.08);
  text-decoration: none;
  cursor: default;
}

.home-soon-badge {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.7;
}

.home-soon-card-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-main);
}

.home-soon-card-desc {
  margin: 0;
  /* Takes the rest of the row, and drops to its own line when what is
     left would be too narrow to read. */
  flex: 1 1 22rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-muted);
}

:root[data-theme="light"] .home-soon-card {
  background: rgba(0, 0, 0, 0.015);
  border-color: rgba(0, 0, 0, 0.14);
}

@media (max-width: 700px) {
  .home-soon-row {
    grid-template-columns: 1fr;
  }
}


/* ==========================================================
   PORTAL OVERVIEW (future use)
   ========================================================== */

.portal-intro {
  max-width: 700px;
  margin: 1rem auto 3rem;
  text-align: center;
  color: var(--text-muted);
  line-height: 1.8;
  font-size: 1rem;
  opacity: 0.75;
}

.portal-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 2rem;
  margin-top: 3rem;
}

.portal-card {
  padding: 2.2rem 1.8rem;
  border-radius: 18px;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.02),
    rgba(255, 255, 255, 0.01)),
    var(--bg-card);
  border: 1px solid rgba(255, 255, 255, 0.06);
  text-align: center;
  text-decoration: none;
  color: var(--text-main);
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

.portal-card:hover {
  transform: translateY(-6px);
  border-color: rgba(216, 168, 79, 0.6);
  box-shadow: 0 0 0 1px var(--accent), 0 10px 30px rgba(216, 168, 79, 0.15);
}

.portal-icon {
  font-size: 2rem;
  margin-bottom: 1rem;
  color: var(--accent);
}

.portal-card h3 {
  font-size: 0.95rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 0.6rem;
}

.portal-card p {
  font-size: 0.9rem;
  color: var(--text-muted);
  line-height: 1.6;
}


/* ==========================================================
   LOGIN PAGE
   ========================================================== */

.login {
  margin: 0;
  padding-top: 0;
  min-height: 100vh;
  /* A faint gold glow behind the card gives the page depth. */
  background:
    radial-gradient(ellipse 620px 520px at 50% 46%, rgba(216, 168, 79, 0.07), transparent 70%),
    var(--bg-main);
  font-family: var(--font-main);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Login — design A "verfijnde kaart" (2026-09-24). One quiet card:
   wordmark, title, the promise in one line, labelled fields. The
   language switch sits above the card because a first-time visitor
   has to be able to change it before reading anything else. */
.login-wrapper {
  width: 100%;
  max-width: 460px;
  padding: 1.5rem 1.25rem 2rem;
  text-align: center;
}

.login-topbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.9rem;
}

/* From tablet up the switch sits in the top-right corner of the screen,
   where visitors look for it, so the card itself stays centred. */
@media (min-width: 600px) {
  .login-topbar {
    position: fixed;
    top: 1.5rem;
    right: 2rem;
    margin: 0;
  }
}

.login-lang {
  display: inline-flex;
  border: 1px solid rgba(var(--w), 0.12);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.login-lang .lang-btn {
  min-height: 44px;
  padding: 0.35rem 0.95rem;
  border-radius: 0;
  font-size: 0.75rem;
  text-align: center;
}

.login-card {
  background: var(--bg-card);
  border: 1px solid rgba(var(--w), 0.08);
  border-radius: 22px;
  padding: 2rem 2rem 1.75rem;
  text-align: left;
  box-shadow:
    0 25px 60px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(var(--w), 0.05),
    inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}

.login-logo {
  margin-bottom: 1.1rem;
  display: flex;
  justify-content: center;
}

.login-logo img {
  width: 240px;
  height: auto;
  opacity: 0.95;
  display: block;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.4));
}

.login-title {
  margin: 0 0 0.4rem;
  font-family: var(--font-serif);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--text-main);
  text-align: center;
}

.login-intro {
  margin: 0 0 1.4rem;
  color: var(--text-muted);
  font-size: var(--type-body);
  line-height: 1.55;
  text-align: center;
  text-wrap: balance;
}

.login-label {
  margin-bottom: 0.4rem;
  font-size: var(--type-small);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-muted);
}

/* The shared .portal-form field is tuned for a darker page; on the card
   its border all but disappears, so the login card draws it clearer. */
.login-card .portal-form {
  margin-top: 0;
}

.login-card .portal-form input {
  padding: 0.8rem 0.95rem;
  border-radius: 10px;
  background: #141414;
  border-color: rgba(var(--w), 0.14);
}

.login-card .portal-form input:-webkit-autofill,
.login-card .portal-form input:-webkit-autofill:hover,
.login-card .portal-form input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px #141414 inset;
}

.login-links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  margin-top: 0.6rem;
}

.login-first {
  margin: 0;
  font-size: var(--type-small);
  color: var(--text-muted);
}

.login-invite {
  margin: 1.4rem 0 0;
  font-size: var(--type-small);
  line-height: 1.55;
  text-wrap: balance;
  color: var(--text-muted);
}

.login-invite a {
  color: var(--accent);
  text-decoration: none;
}

.login-invite a:hover {
  text-decoration: underline;
}

.portal-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 1.6rem;
}

.form-field {
  display: flex;
  flex-direction: column;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.portal-form input,
.portal-form textarea {
  padding: 1rem 1.2rem;
  font-size: 1rem;
  border-radius: 14px;
  background: #181818;
  border: 1px solid rgba(255, 255, 255, 0.04);
  color: var(--text-main);
  font-family: var(--font-main);
  resize: vertical;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.portal-form input:focus,
.portal-form textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.15);
}

.password-field {
  position: relative;
  display: flex;
}

.password-field input {
  width: 100%;
  padding-right: 3rem;
}

.password-toggle {
  position: absolute;
  top: 50%;
  right: 0.25rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-main);
  opacity: 0.6;
  padding: 0.4rem;
  transition: opacity 0.2s ease;
}

.password-toggle:hover {
  opacity: 1;
}

.password-toggle--emoji {
  font-size: 1rem;
  opacity: 0.9;
}

.settings-link {
  color: var(--accent);
  font-size: 0.9rem;
  text-decoration: none;
  border-bottom: 1px solid rgba(216, 168, 79, 0.3);
  transition: border-color 0.2s ease;
}

.settings-link:hover {
  border-color: var(--accent);
}

.portal-form input:-webkit-autofill,
.portal-form input:-webkit-autofill:hover,
.portal-form input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px #181818 inset;
  -webkit-text-fill-color: var(--text-main);
  caret-color: var(--text-main);
}

.login-btn {
  margin-top: 0.5rem;
  width: 100%;
  padding: 0.75rem 2rem;

  /* The portal's primary button (13 October 2026): accent gold, a pill. */
  background: var(--accent);
  color: #111;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);

  font-family: var(--font-main);
  font-size: var(--type-small);
  font-weight: 600;
  letter-spacing: 0.02em;

  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.login-btn:hover {
  background: var(--accent);
  opacity: 0.85;
  border-color: transparent;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}

.login-btn:active {
  transform: translateY(1px);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.login-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.login-link {
  background: none;
  border: none;
  padding: 0.5rem 0;
  color: var(--accent);
  font-family: var(--font-main);
  font-size: var(--type-small);
  cursor: pointer;
  text-align: left;
}

.login-link:hover {
  text-decoration: underline;
}

.login-link:focus-visible,
.login-lang .lang-btn:focus-visible,
.login-invite a:focus-visible,
.login-back:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.portal-error {
  margin-top: 1rem;
  font-size: 0.9rem;
  color: #e05a5a;
  min-height: 1.2em;
}

/* On the login card the empty line takes no room; it only opens when
   there is something to say. */
.login-card .portal-error:empty {
  margin-top: 0;
  min-height: 0;
}

/* The same message line, reporting a success (e.g. "link sent"). */
.portal-error.is-success {
  color: #4caf7d;
}

.portal-success {
  margin-top: 1rem;
  font-size: 0.9rem;
  color: #4caf7d;
  min-height: 1.2em;
}

.login-back {
  display: inline-block;
  margin-top: 0.4rem;
  padding: 0.5rem 0.25rem;
  font-size: var(--type-small);
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.2s ease;
}

.login-back:hover {
  color: var(--accent);
}


/* ==========================================================
   RESPONSIVE
   ========================================================== */

@media (max-width: 900px) {
  .platform-topbar {
    padding: 1rem 1.5rem;
  }
}

@media (max-width: 768px) {
  .platform-topbar {
    padding: 1rem;
    flex-direction: column;
    align-items: stretch;
  }

  .platform-search {
    width: 100%;
  }

  .platform-action {
    width: 100%;
    text-align: center;
  }


  .lesson-item {
    flex-direction: column;
    gap: 1rem;
    padding: 1.2rem;
  }

  .lesson-thumb {
    width: 100%;
    height: 160px;
  }

  .portal-grid {
    gap: 1.2rem;
    margin-top: 2rem;
  }

  .portal-card {
    padding: 1.6rem 1.2rem;
  }

  .login-card {
    padding: 1.75rem 1.5rem 1.5rem;
  }
}


@media (max-width: 480px) {

  .login-wrapper {
    padding: 1rem 1rem 1.5rem;
  }

  .login-card {
    padding: 1.5rem 1.25rem 1.25rem;
  }

  .login-logo img {
    width: 200px;
  }

  .login-title {
    font-size: 1.55rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .login-btn {
    transition: none;
  }

  .login-btn:active {
    transform: none;
  }
}


/* ==========================================================
   PRACTICE PAGE â€” LAYOUT
   ========================================================== */

/* Tool navigation bar */
.tool-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  padding: 0.65rem 0;
  margin-bottom: 1.5rem;
  background: var(--bg-main);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.tool-nav[hidden] { display: none; }

.tool-nav-link {
  font-size: 0.72rem;
  font-weight: 500;
  padding: 0.28rem 0.75rem;
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  text-decoration: none;
  border: 1px solid rgba(255, 255, 255, 0.07);
  background: rgba(255, 255, 255, 0.03);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  white-space: nowrap;
}

.tool-nav-link:hover {
  background: rgba(255, 255, 255, 0.07);
  color: var(--text-main);
  border-color: rgba(255, 255, 255, 0.12);
}

.prog-roots-mobile { display: none; }

.tool-nav-link--adv {
  margin-left: auto;
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.25);
  background: rgba(216, 168, 79, 0.06);
}

.tool-nav-link--adv:hover {
  background: rgba(216, 168, 79, 0.14);
  border-color: rgba(216, 168, 79, 0.45);
  color: var(--accent);
}

/* Section dividers */
.practice-section {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin: 1.8rem 0 1rem;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-muted);
  opacity: 0.35;
}

.practice-section::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.06);
}

/* Practice Tracker full-width bar */
.tool-card--bar {
  margin-bottom: 1.5rem;
  padding: 1.3rem 1.8rem;
}

.tool-card--bar .tool-title {
  margin-bottom: 0.9rem;
}

.tool-card--bar .tracker-top {
  margin-bottom: 0.8rem;
}

/* 2-column grid */
.practice-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
  /* stretch (not start): Finding the Note / Note Naming otherwise sit at
     mismatched heights since one's neck diagram + controls are taller
     than the other's — stretch matches .practice-3col's behavior and,
     combined with .tool-card's flex-column + .ear-status's
     margin-top:auto, keeps both cards' full height equal and their
     score rows aligned at the bottom. */
  align-items: stretch;
}

/* Every Practice panel shows its widgets two-across on desktop instead
   of the old 3-across grid — cards here (metronomes, ear trainers with
   feedback panels, etc.) read better with more room per card than a
   third of the row. Collapses to one column on narrow viewports. */
.practice-3col {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  max-width: 1400px;
  margin-bottom: 1.5rem;
  gap: 1.5rem;
  align-items: stretch;
}

@media (max-width: 760px) {
  .practice-3col {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================
   PRACTICE PAGE — per-tab desktop grid refinement
   Base .practice-3col above already gives every panel 2 columns
   down to 760px, then 1 column below that. What remains here is
   COMPOSITION — which card spans, which one centres — for the
   panels whose third card needs more room than a third column
   gives it. The wide-desktop COLUMN COUNT is not decided here any
   more: practice-layout.css states it once, for every panel and
   both levels, so a panel cannot end up with a third column and
   nothing to put in it. All scoped under .practice-page
   (practice.html <body>) so nothing here touches the shared
   .practice-3col/.tool-card rules used elsewhere in the portal.
   ========================================================== */

/* Tools: Tuner + Metronome share the 2-col tier (900-1399px and below,
   inherited from the base rule); Drone drops to a full-width row under
   them until the 3-col tier kicks in at 1400px (practice-layout.css). */
.practice-page #practice-panel-tools .practice-3col > section:nth-child(3) {
  grid-column: 1 / -1;
}
@media (min-width: 1400px) {
  .practice-page #practice-panel-tools .practice-3col > section:nth-child(3) {
    grid-column: auto;
  }
}

/* Chords: Chord Practice (now covers Open + Barre modes) + Chord Changes
   need width for chip selectors, diagrams and BPM controls — kept 2-up
   at every tier (never 3-up), no full-width 3rd row since Barre Chords
   was merged into Chord Practice. */
.practice-page #practice-panel-chords .practice-3col {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Scales: Scale Practice + Scale Position Practice need width for
   selectors, note sequences and BPM slider — 2-up at every tier.
   Improvisation (coming soon) spans full width below. */
.practice-page #practice-panel-scales .practice-3col {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.practice-page #practice-panel-scales .practice-3col > section:nth-child(3) {
  grid-column: 1 / -1;
}

/* Chords, Scales and Fretboard are the three visual-practice families.
   Give their desktop cards one shared, compact canvas so switching tabs
   never makes the whole practice area jump in height. On a phone the
   cards become content-led again; blank vertical space is less useful
   once they are stacked. */
@media (min-width: 761px) {
  .practice-page #practice-panel-chords .practice-3col > .tool-card,
  .practice-page #practice-panel-scales .practice-3col > .tool-card,
  .practice-page #practice-panel-fretboard .practice-fretboard-row > .tool-card {
    min-height: 34rem;
  }
}

/* A next-round button belongs after the result, not before the exercise. */
#tool-find-note .ear-controls,
#tool-note-naming .ear-controls {
  margin: 0.8rem 0 0;
}

/* At the two-column tier, center Reading's third card instead of
   leaving it as an orphan on the left. It returns to the normal grid at
   the single- and three-column breakpoints.
   Rhythm and Ear Training used to be listed here too. Neither has a
   third card any more: Practice Timing is Starter's alone, so Rhythm
   shows two from Beginner up, and Ear Training's four cards are two-up
   at every tier. A nth-child(3) rule would now grab the SECOND visible
   card and drop it, full width, under the first. */
@media (min-width: 761px) and (max-width: 1399px) {
  .practice-page #practice-panel-reading .practice-3col > section:nth-child(3) {
    grid-column: 1 / -1;
    width: calc((100% - 1.5rem) / 2);
    justify-self: center;
  }
}

/* Keep the tempo controls on the same baseline in the two Scale cards.
   Both cards are flex columns, so the shorter Position card uses its spare
   height above the BPM row while the controls themselves remain contiguous. */
.practice-page #tool-scale-practice .metro-bpm-row,
.practice-page #tool-position-practice .metro-bpm-row {
  margin-top: auto;
}

/* Scales have one additional practice control, so tighten only this pair of
   cards to retain the compact proportion used by the Chords pair. */
.practice-page #practice-panel-scales .metro-option-group {
  margin-bottom: 0.6rem;
}
.practice-page #practice-panel-scales .cswitch-display {
  margin-bottom: 0.55rem;
}
.practice-page #practice-panel-scales .metro-slider {
  margin-bottom: 0.85rem;
}
.practice-page #practice-panel-scales .metro-row {
  margin-bottom: 0.75rem;
}
.practice-page #tool-scale-practice .scalep-degrees {
  margin: 0.45rem 0;
}

@media (max-width: 760px) {
  #tool-scale-practice .scalep-degrees {
    flex-wrap: nowrap;
    gap: 0.2rem;
    width: 100%;
  }
  #tool-scale-practice .scalep-degree {
    min-width: 0;
    flex: 1 1 0;
    padding: 0.28rem 0.1rem;
    font-size: 0.75rem;
  }

  /* Mobile preference: category choices stay a single, swipeable row rather
     than turning a compact practice card into a tall stack of pills. */
  .practice-page .metro-option-group .metro-opt-row {
    width: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    padding: 0 1.25rem 0.3rem 0;
    scroll-padding-right: 1.25rem;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .practice-page .metro-option-group .metro-opt-row::-webkit-scrollbar {
    display: none;
  }
  .practice-page .metro-option-group .metro-opt-row .metro-opt-btn {
    flex: 0 0 auto;
  }

  /* Scale names are meaningful labels; keep them intact and swipe rather
     than breaking the picker into a tall stack on narrow phones. */
  .practice-page #practice-panel-scales #scalepPicker,
  .practice-page #practice-panel-scales #posPicker,
  .practice-page #practice-panel-scales #scalepPatternBtns {
    width: 100%;
  }
}

/* Match Chords to the compact vertical rhythm of Scales while preserving the
   aligned timing controls inside the two chord widgets. */
.practice-page #practice-panel-chords .metro-option-group {
  margin-bottom: 0.6rem;
}
.practice-page #practice-panel-chords .metro-slider {
  margin-bottom: 0.85rem;
}
.practice-page #practice-panel-chords .metro-row {
  margin-bottom: 0.75rem;
}

/* Fretboard: Finding the Note, Note Naming, and String Note Challenge
   share a wide 2-col row (NOT 3-col — their full-neck diagrams must not
   get compressed). Three cards in a 2-col grid wraps the 3rd (String
   Note Challenge) under the 1st (Finding the Note), same card size as
   the other two — see .practice-2col's align-items:stretch. */
.practice-fretboard-row {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 900px) {
  .practice-fretboard-row {
    grid-template-columns: 1fr;
  }
}

/* Chord-diagram SVG (Chord Diagram Trainer, Chord Practice, Chord
   Changes, Barre Chords all share .cswitch-diagram) — enlarged ~30%
   for Practice only; the Fretboard page's own diagram uses different
   markup and is unaffected. */
.practice-page .cswitch-diagram {
  width: 118px;
}

/* Chord Diagram Trainer: only card using .cswitch-display with just the
   diagram inside (no adjacent "current chord" text like Chord Switch/
   Barre Drill), so it can be centered and enlarged to fill the empty
   space below the answer buttons. */
.practice-page #tool-diagram-trainer .cswitch-display {
  justify-content: center;
  margin-bottom: 0.6rem;
}

/* Four complete beginner ear drills read more clearly as two balanced
   comparisons per row than as a 3+1 orphan layout. */
.practice-page #practice-panel-ear .practice-3col {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 900px) {
  .practice-page #practice-panel-ear .practice-3col {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================
   Fretboard-practice neck orientation (Finding the Note / Note Naming)
   ----------------------------------------------------------
   Same system as Fretboard's Notes tab (see the "FRETBOARD ORIENTATION
   SYSTEM" block further down this file, and neck-orientation.js, which
   drives both): a narrow phone gets a vertical neck instead of a
   horizontally-scrolling one. Kept at this widget's own pre-existing
   760px breakpoint rather than Fretboard's 700px — no reason to force
   the two to match.
   ========================================================== */
@media (max-width: 760px) {
  .practice-page #practice-panel-fretboard .practice-neck-wrap.fb-vertical {
    overflow: hidden;
  }

  .practice-page #practice-panel-fretboard .practice-neck-wrap.fb-vertical svg {
    width: auto;
    height: auto;
    max-width: none;
    transform-origin: top left;
    transform: scale(var(--fb-vert-scale, 1)) rotate(90deg) translateY(-100%);
  }

  .practice-page #practice-panel-fretboard .practice-neck-wrap.fb-vertical svg text {
    transform-box: fill-box;
    transform-origin: center;
    transform: rotate(-90deg);
  }

  /* Left-handed vertical: same rotation, mirrored so high-e ends up on
     the left and low-E on the right (the vertical equivalent of the
     horizontal scaleX(-1) mirror elsewhere in this file). */
  [data-handed="left"] .practice-page #practice-panel-fretboard .practice-neck-wrap.fb-vertical svg {
    transform: scale(var(--fb-vert-scale, 1)) rotate(90deg) scaleY(-1);
  }
  [data-handed="left"] .practice-page #practice-panel-fretboard .practice-neck-wrap.fb-vertical svg text {
    transform: rotate(90deg) scaleY(-1);
  }
}
.practice-page #tool-diagram-trainer .cswitch-diagram {
  width: 125px;
}

#tool-diagram-trainer .metro-option-group {
  margin-bottom: 0.55rem;
  gap: 0.45rem;
}

@media (max-width: 900px) {
  .practice-page #practice-panel-chords .practice-3col,
  .practice-page #practice-panel-scales .practice-3col {
    grid-template-columns: 1fr;
  }
}

/* Fretboard-practice neck SVGs (Finding the Note / Note Naming / String
   Note Challenge) — the fret-number row and string-letter column were
   rendered at very low opacity (0.35-0.4) and small size, hard to read
   on the dark card background. Boost both without touching the JS that
   generates the SVG (attribute selectors target the exact presentation
   attributes practiceNeckBase() emits, overriding them at the CSS
   layer — the note markers/dots elsewhere in the same SVG use
   different font-size/fill values and are untouched). */
.practice-page .practice-neck-wrap svg text[font-size="9"] {
  fill: rgba(255, 255, 255, 0.62);
  font-size: 10px;
}
.practice-page .practice-neck-wrap svg text[font-size="8"] {
  fill: rgba(255, 255, 255, 0.55);
  font-size: 9px;
}

/* ==========================================================
   DRONE
   ========================================================== */

.drone-hint {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.55;
}

#tool-drone.tool-card--drone {
  overflow: visible;
  padding: 0;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.1);
  border-radius: 14px;
}

#tool-drone .drone-card-header {
  padding: 1.1rem 1.45rem 0.9rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
}

#tool-drone .tool-title {
  margin: 0;
}

.drone-stage {
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: flex-start;
  gap: 1rem;
  padding: 1.15rem 1.45rem 1rem;
}

.drone-readout {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  min-height: 9.35rem;
  text-align: center;
}

.drone-readout-kicker,
.drone-section-label,
.drone-option-label {
  color: var(--text-muted);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.drone-selected-note {
  display: flex;
  align-items: center;
  min-height: var(--practice-readout-height);
  margin: 0;
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: var(--practice-readout-size);
  font-weight: 700;
  letter-spacing: -0.045em;
  line-height: 1;
  transition: color 0.2s ease, text-shadow 0.2s ease;
}

.drone-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 1.3rem;
  margin: 0.35rem 0 0;
  color: var(--text-muted);
  font-size: 0.78rem;
}

#tool-drone[data-drone-playing="true"] .drone-selected-note,
#tool-drone[data-drone-playing="true"] #droneStatusState {
  color: var(--accent);
}

#tool-drone[data-drone-playing="true"] .drone-selected-note {
  text-shadow: 0 0 28px rgba(216, 168, 79, 0.18);
}

.drone-root-picker {
  display: block;
}

/* A single restrained note strip keeps the main choice immediately
   available. On narrow phones it becomes the same balanced 4 + 3 keypad,
   so every target remains comfortably tappable without horizontal scroll. */
.drone-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.45rem;
  width: 100%;
  margin: 0;
}

.drone-note {
  min-width: 0;
  min-height: 62px;
  padding: 0.55rem 0.2rem;
  background: rgba(var(--w), 0.03);
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: 7px;
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease,
              color 0.15s ease, transform 0.1s ease;
}

.drone-note:hover {
  background: rgba(var(--w), 0.055);
  border-color: rgba(216, 168, 79, 0.38);
}

.drone-note.active {
  background: rgba(216, 168, 79, 0.08);
  border-color: rgba(216, 168, 79, 0.62);
  color: var(--accent);
}

.drone-note:active {
  transform: translateY(1px);
}

.drone-note:focus-visible,
.drone-type-row .metro-opt-btn:focus-visible,
.drone-settings-trigger:focus-visible,
#droneToggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.drone-controls {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.25fr);
  gap: 0.65rem;
  width: 100%;
  margin-top: auto;
  padding: 0.85rem 1.45rem 0.95rem;
  border-top: 1px solid rgba(var(--w), 0.08);
}

.drone-controls > * {
  min-width: 0;
}

.drone-settings-menu {
  position: relative;
}

.drone-settings-menu > summary {
  list-style: none;
}

.drone-settings-menu > summary::-webkit-details-marker {
  display: none;
}

.drone-settings-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  width: 100%;
  min-height: 46px;
  padding: 0.55rem 0.7rem;
  color: var(--text-main);
  background: rgba(var(--w), 0.035);
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: 8px;
  font-size: 0.875rem;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.drone-settings-trigger:hover {
  background: rgba(var(--w), 0.065);
}

.drone-settings-menu[open] .drone-settings-trigger {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.58);
}

.drone-settings-chevron {
  width: 14px;
  height: 14px;
  margin-left: 0.1rem;
  flex: 0 0 auto;
  transition: transform 0.15s ease;
}

.drone-settings-menu[open] .drone-settings-chevron {
  transform: rotate(180deg);
}

#tool-drone:has(.drone-settings-menu[open]) {
  z-index: 30;
}

.drone-settings {
  position: absolute;
  z-index: 30;
  bottom: calc(100% + 0.65rem);
  left: 0;
  display: grid;
  gap: 0.65rem;
  width: min(330px, calc(100vw - 2rem));
  padding: 1rem 1.15rem;
  background: var(--bg-card);
  border: 1px solid rgba(var(--w), 0.12);
  border-radius: 10px;
  box-shadow: var(--shadow-soft);
}

.drone-settings > .metro-select {
  width: 100%;
  min-width: 0;
}

/* Five voicings fit as a quiet 2 + 2 + 1 grid inside Settings. The
   selected voicing is repeated in the central readout, so this menu can
   remain subordinate to the root note and transport. */
.drone-type-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.45rem;
  margin: 0;
}

.metro-hint {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.drone-type-row .metro-opt-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 0.4rem 0.55rem;
  color: var(--text-main);
  background: var(--bg-section);
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: 7px;
  font-family: var(--font-main);
  text-align: center;
  font-size: 0.78rem;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease,
              color 0.15s ease;
}

.drone-type-row .metro-opt-btn:nth-child(5) {
  grid-column: 1 / -1;
}

.drone-type-row .metro-opt-btn.active {
  background: rgba(216, 168, 79, 0.08);
  border-color: rgba(216, 168, 79, 0.62);
  color: var(--accent);
}

.drone-type-row .metro-opt-btn:hover {
  background: rgba(var(--w), 0.055);
  border-color: rgba(var(--w), 0.16);
}

#droneToggle {
  width: 100%;
  min-height: 46px;
  border-radius: 8px;
}

#tool-drone[data-drone-playing="true"] #droneToggle {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.08);
  border: 1px solid rgba(216, 168, 79, 0.46);
}

@media (max-width: 520px) {
  #tool-drone .drone-card-header,
  #tool-drone .drone-stage,
  #tool-drone .drone-controls {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  .drone-grid {
    grid-template-columns: repeat(8, minmax(0, 1fr));
  }

  .drone-note {
    grid-column: span 2;
  }

  .drone-note:nth-child(5) { grid-column: 2 / span 2; }
  .drone-note:nth-child(6) { grid-column: 4 / span 2; }
  .drone-note:nth-child(7) { grid-column: 6 / span 2; }
}

/* 3-column reference row: Chord Viz wider, COF + Key Sig equal */
.practice-ref-row {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

/* Sharps & Flats black key cards */
.sf-keys-row {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.sf-black-key {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  background: #141414;
  border: 1px solid rgba(var(--w),0.1);
  border-radius: 8px;
  padding: 0.7rem 0.65rem 0.6rem;
  min-width: 58px;
  flex: 1;
}

.sf-sharp {
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1.1;
}

.sf-eq {
  font-size: 0.65rem;
  color: var(--text-muted);
  opacity: 0.35;
  line-height: 1;
}

.sf-flat {
  font-size: 1rem;
  font-weight: 700;
  color: rgba(130, 180, 255, 0.9);
  line-height: 1.1;
}

.sf-sol {
  margin-top: 0.35rem;
  font-size: 0.65rem;
  color: var(--text-muted);
  opacity: 0.55;
  line-height: 1.2;
  text-align: center;
}

.sf-deg {
  font-size: 0.62rem;
  color: var(--text-muted);
  opacity: 0.35;
  line-height: 1.2;
  text-align: center;
}

/* Key Signature Reference */
.ks-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
  padding-right: 2.2rem;
}

/* C major origin block */
.ks-origin {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.75rem;
  border: 1px solid rgba(216, 168, 79, 0.18);
  border-radius: var(--radius-small);
  background: rgba(216, 168, 79, 0.04);
}

.ks-origin .ks-notes-list {
  font-style: italic;
}

/* Two-column layout */
.ks-two-col {
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  gap: 0 0.5rem;
}

.ks-divider {
  background: rgba(var(--w),0.06);
  align-self: stretch;
}

.ks-col-header {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  opacity: 0.35;
  font-weight: 700;
  padding: 0 0.3rem 0.4rem;
}

.ks-entry {
  display: flex;
  align-items: flex-start;
  gap: 0.3rem;
  padding: 0.22rem 0.3rem;
  border-radius: 5px;
  transition: background 0.1s;
  cursor: default;
}

.ks-entry:hover { background: rgba(var(--w),0.03); }
.ks-entry--active {
  background: rgba(216,168,79,0.08);
  box-shadow: inset 2px 0 0 rgba(216,168,79,0.6);
  padding-left: 0.5rem;
}
.ks-entry--active .ks-key-name { color: var(--accent); }

.ks-count {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--accent);
  min-width: 2.1rem;
  text-align: center;
  padding-top: 0.05rem;
  flex-shrink: 0;
}

.ks-count--none {
  color: var(--text-muted);
  opacity: 0.25;
  font-weight: 400;
}

.ks-count--flat {
  color: rgba(130, 180, 255, 0.9);
}

.ks-entry-right {
  display: flex;
  flex-direction: column;
  gap: 0.08rem;
  min-width: 0;
}

.ks-key-name {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-main);
  line-height: 1.2;
}
.ks-rel-minor {
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--text-muted);
}

.ks-enh {
  font-size: 0.58rem;
  color: var(--text-muted);
  opacity: 0.4;
  font-weight: 400;
  margin-left: 0.12rem;
}

.ks-notes-list {
  font-size: 0.75rem;
  color: var(--text-muted);
  opacity: 0.45;
  letter-spacing: 0.02em;
  line-height: 1.2;
}

.ks-notes-list--none { opacity: 0.15; }

/* Deprecated â€” kept for safety */
.practice-top-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

/* Fretboard tabs */
.fb-tabs {
  display: flex;
  /* Primary tabs must remain one continuous navigation row. Wrapping makes
     a lone tab look accidental; overflow preserves readable labels and
     lets every portal page use the same swipe/scroll behaviour. */
  flex-wrap: nowrap;
  gap: 0.15rem 0;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.5rem;
  margin-top: 1rem;
  overflow-x: auto;
  overflow-y: hidden;
  padding-right: 1.25rem;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Mobile browsers can leave a blue text caret at the clipped edge of the
     next tab after tapping or swiping this row. The tabs are controls, not
     editable text; keyboard focus remains visible through :focus-visible. */
  caret-color: transparent;
}
.fb-tabs::-webkit-scrollbar {
  display: none;
}

/* Glossary is a page-level reference action rather than a curriculum
   step. It sits at the far edge of the topic rail, not among the lessons. */
.theory-page-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: var(--page-title-gap);
}
.theory-page-heading h1 { margin: 0; }

/* PUBLIC STARTER PAGE ONLY (theory/starter/index.html, generated by
   tools/build-public-starter.mjs). Outline/ghost, never a filled
   primary — the accent colour alone is enough emphasis for a button
   that sits in a page title row on every breakpoint without opening
   the sidebar. See docs/ACCOUNTS-PLAN.md §2.13. */
.public-auth-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 2.5rem;
  padding: 0.45rem 1.1rem;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: var(--radius-small);
  background: transparent;
  color: var(--accent);
  font: 700 0.85rem var(--font-main);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
.public-auth-btn:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.public-auth-btn:focus { outline: none; }
.public-auth-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
@media (max-width: 700px) {
  .public-auth-btn { min-height: 2.35rem; padding: 0.4rem 0.85rem; font-size: 0.78rem; }
}
.theory-topic-navigation {
  display: flex;
  align-items: stretch;
  gap: 0.8rem;
  min-width: 0;
  margin-bottom: var(--page-tabs-gap);
  border-bottom: 1px solid var(--border);
}
.theory-topic-navigation .fb-tabs {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
  border-bottom: 0;
}
.theory-topic-navigation .theory-glossary-action {
  align-self: stretch;
  flex: 0 0 auto;
  min-height: 0;
  padding: 0.55rem 0.7rem 0.35rem;
  border-radius: 0;
}
.theory-glossary-action {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 2.5rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid transparent;
  border-radius: var(--radius-small);
  background: transparent;
  color: var(--text-muted);
  font: 700 var(--type-small) var(--font-main);
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
.theory-glossary-icon {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.65;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.theory-glossary-action:hover {
  color: var(--text-main);
  border-color: rgba(var(--w), 0.12);
  background: rgba(var(--w), 0.025);
}
.theory-glossary-action.active {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.theory-glossary-action:focus { outline: none; }
.theory-glossary-action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
@media (max-width: 700px) {
  .theory-page-heading { gap: 0.75rem; }
  .theory-topic-navigation { gap: 0.35rem; }
  .theory-glossary-action { min-height: 2.35rem; padding-inline: 0.55rem; font-size: var(--type-xs); }
  .theory-glossary-action span { display: none; }
}

.fb-tab {
  flex: 0 0 auto;
  scroll-snap-align: center;
  padding: 0.55rem 1.4rem 0.35rem;
  font-size: var(--type-small);
  font-weight: 700;
  color: var(--text-muted);
  background: transparent;
  border: none;
  border-bottom: 1.5px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  white-space: nowrap;
  /* user-select/tap-highlight now handled portal-wide by the base `button`
     reset above — no need to repeat it here. */
}

/* Practice has eight Level 1 tabs. The same one-row scroll behaviour is
   already defined on .fb-tabs above; this keeps the page-specific selector
   available for any future Practice-only tuning. */
@media (max-width: 1200px) {
  .practice-page #practiceTabs {
    scroll-padding-inline: 1.25rem;
  }
}
@media (max-width: 700px) {
  .fb-tab {
    padding: 0.5rem 0.9rem 0.35rem;
    font-size: 0.875rem;
  }
}
.fb-tab:hover { color: var(--text-main); }
/* LEVEL 1 (main tabs) canonical active state, shared portal-wide by every
   .fb-tab instance (fretboard.html, theory.html, practice.html): gold text
   + a thin full-width gold underline, ~2-3px below the text. */
.fb-tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Without this, tapping/clicking a tab leaves the browser's own default
   blue focus ring sitting on it — visually random-looking (its position
   depends on scroll/crop) and off-brand. Suppress the plain-click ring but
   keep an on-brand one for real keyboard (Tab-key) navigation, so
   accessibility isn't lost, just the stray blue box. */
.fb-tab:focus {
  outline: none;
}
.fb-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: 3px;
}

/* ==========================================================
   TAB HIERARCHY - portal-wide 3-level system
   ========================================================== */
/* .tab-primary / .tab-secondary / .tab-filter are the canonical names
   for new markup. Existing page-specific classes (.fb-tab, .sv-cat-btn,
   .mode-family-btn, .fbx-step-btn, .tuner-mode-btn, .filter-chip,
   .scale-btn, .mode-chip, .iv-chip, .metro-opt-btn) are grouped into
   these same rules instead of being deleted/renamed everywhere they're
   already wired into JS - so every tab/category/filter row in the
   portal now reads off one shared size scale:
     Level 1 .fb-tab / .platform-tabs button   0.85-0.9375rem, underline
     Level 2 .tab-secondary + siblings          0.8125rem, quiet pill
     Level 3 .tab-filter + siblings             0.8125rem, compact chip
   Level 1 is the clearly strongest tier (larger + bold + more
   horizontal room). Level 2 and Level 3 share the same font-size on
   purpose — what keeps Level 3 reading as subordinate is its smaller
   padding/pill footprint, not a separate font-size step - no tier can
   visually outweigh the one above it. */
.tab-secondary,
.sv-cat-btn,
.mode-family-btn,
.fbx-step-btn,
.tuner-mode-btn {
  font-size: 0.8125rem;
  font-weight: 600;
  padding: 0.32rem 0.75rem;
}

.tab-filter,
.filter-chip,
.scale-btn,
.mode-chip,
.iv-chip,
.metro-opt-btn {
  font-size: 0.8125rem;
  padding: 0.22rem 0.5rem;
}

/* Standalone .tab-secondary / .tab-filter (used directly, not via one
   of the page-specific classes above) get the same visual treatment
   as their reference implementation (.sv-cat-btn / .scale-btn). */
.tab-secondary {
  background: rgba(var(--w), 0.04);
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.tab-secondary:hover { background: rgba(var(--w), 0.07); color: var(--text-main); }
.tab-secondary.active {
  background: rgba(216, 168, 79, 0.08);
  border-color: rgba(216, 168, 79, 0.28);
  color: var(--accent);
}

.tab-filter {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
  white-space: nowrap;
}
.tab-filter:hover { background: rgba(255, 255, 255, 0.08); color: var(--text-main); }
.tab-filter.active {
  background: rgba(216, 168, 79, 0.15);
  border-color: rgba(216, 168, 79, 0.4);
  color: var(--accent);
  font-weight: 600;
}

/* Full-width tools â€” fretboard */
.fb-root-bar {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.fb-root-row,
.fb-type-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.fb-root-label {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  white-space: nowrap;
}

.fb-bar-deg {
  margin-left: auto;
  white-space: nowrap;
}

.practice-tools {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.tool-card {
  position: relative;
  background: linear-gradient(145deg,
    rgba(255, 255, 255, 0.03),
    rgba(255, 255, 255, 0.01));
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 16px;
  padding: 1.4rem 1.5rem;
  /* .practice-3col stretches every card in a row to the tallest one's
     height (align-items:stretch) — this flex column + the margin-top:auto
     on .ear-status below is what turns that spare height into the score
     row landing at the same y-position across cards, instead of just
     leaving blank space at the bottom of the shorter cards. */
  display: flex;
  flex-direction: column;
}

/* Ear Training workspace entry card — a plain link into ear-training.html,
   deliberately NOT a .tool-card (see the comment at its markup in
   practice.html): it must not count toward the tool-inventory tests. Sits
   above the four legacy ear-training cards while both exist side by side. */
.ear-entry-card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  background: linear-gradient(145deg, rgba(216, 168, 79, 0.10), rgba(216, 168, 79, 0.03));
  border: 1px solid rgba(216, 168, 79, 0.28);
  border-radius: 16px;
  padding: 1.1rem 1.4rem;
  margin-bottom: 1.5rem;
  text-decoration: none;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.ear-entry-card:hover,
.ear-entry-card:focus-visible {
  border-color: rgba(216, 168, 79, 0.5);
  background: linear-gradient(145deg, rgba(216, 168, 79, 0.16), rgba(216, 168, 79, 0.05));
}
.ear-entry-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.ear-entry-title {
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent);
}
.ear-entry-sub {
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Generic flex header (injected by JS for cards with no existing header) */
.tool-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.5rem;
}

.tool-card-header .tool-title {
  margin: 0;
}

/* Groups existing controls + expand btn on the right side of a flex header */
.tool-topbar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.tool-title {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-muted);
  font-weight: 600;
  margin: 0 0 1.25rem;
}

.tool-title::after {
  display: none;
}

/* Compact contextual help shared by the performance tools. The native
   details element keeps the interaction keyboard-accessible without
   introducing another component or dependency. */
.tool-info-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.tool-info-header .tool-title {
  margin: 0;
}

.tool-info-menu {
  position: relative;
  flex: 0 0 auto;
}

.tool-info-menu > summary {
  list-style: none;
}

.tool-info-menu > summary::-webkit-details-marker {
  display: none;
}

.tool-info-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  color: var(--text-muted);
  background: rgba(var(--w), 0.025);
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: 50%;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.tool-info-trigger svg {
  width: 17px;
  height: 17px;
}

.tool-info-trigger:hover,
.tool-info-menu[open] .tool-info-trigger {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.07);
  border-color: rgba(216, 168, 79, 0.55);
}

.tool-info-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.tool-info-popover {
  position: absolute;
  z-index: 40;
  top: calc(100% + 0.55rem);
  right: 0;
  width: min(320px, calc(100vw - 2rem));
  padding: 0.9rem 1rem;
  color: var(--text-muted);
  background: var(--bg-card);
  border: 1px solid rgba(var(--w), 0.12);
  border-radius: 9px;
  box-shadow: var(--shadow-soft);
}

.tool-info-popover :is(.tuner-hint, .metro-hint) {
  max-width: none;
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.55;
}

.tool-card:has(.tool-info-menu[open]) {
  z-index: 40;
}

.tuner-card-header {
  margin-bottom: 0;
}


/* ==========================================================
   SHARED TOOL BUTTONS
   ========================================================== */

.tool-btn-primary {
  background: var(--accent);
  color: #111;
  border: none;
  padding: 0.55rem 1.2rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.tool-btn-primary:hover { opacity: 0.85; }

.tool-btn-secondary {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-main);
  border: 1px solid rgba(255, 255, 255, 0.08);
  padding: 0.55rem 1.2rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  font-size: 0.875rem;
  cursor: pointer;
  transition: background 0.2s ease;
}

.tool-btn-secondary:hover { background: rgba(255, 255, 255, 0.1); }

/* Icon+label buttons (Ear Training's transport row uses this first) — the
   icon (unicode glyph or inline SVG, both get .btn-icon) and the label sit
   in separate elements so .btn-label can be hidden independently for an
   icon-only layout without fighting the data-i18n textContent swap. */
.btn-icon-text {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
}

/* Fixed rem sizes (not em) so the ▶ / ↻ glyphs and the speedometer SVG all
   read as the same size regardless of each glyph's own intrinsic
   proportions or the button's font-size. */
.btn-icon-text .btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  line-height: 1;
  width: 1.3rem;
  height: 1.3rem;
  font-size: 1.3rem;
}

.btn-icon-text svg.btn-icon {
  width: 1.3rem;
  height: 1.3rem;
}

/* Clap/Tap's "Tap" button flash — brief highlight confirming a tap was
   actually registered (toggled for 100ms per tap in practice.js). Was
   dead weight before: this class had no CSS rule at all, so tapping (by
   click or Space) gave zero visible feedback even though it was working
   internally — very likely what read as "the tap button doesn't work". */
.tool-btn-secondary.mth-toggle--active {
  background: var(--accent);
  color: #141414;
  border-color: var(--accent);
}

.tool-btn-accent {
  background: rgba(216, 168, 79, 0.12);
  color: var(--accent);
  border: 1px solid rgba(216, 168, 79, 0.25);
  padding: 0.55rem 1.2rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease;
}

.tool-btn-accent:hover { background: rgba(216, 168, 79, 0.2); }
.tool-btn-accent:disabled { opacity: 0.35; cursor: not-allowed; }


/* ==========================================================
   METRONOME
   ========================================================== */

.metro-bpm-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}

.metro-tempo-name {
  margin-left: auto;
  font-size: 0.85rem;
  color: var(--text-muted);
  font-style: italic;
  letter-spacing: 0.04em;
  align-self: center;
}

.metro-bpm-display {
  font-size: 2.2rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.metro-bpm-unit {
  font-size: 1rem;
  color: var(--text-muted);
}

.metro-slider {
  width: 100%;
  accent-color: var(--accent);
  margin-bottom: 1.2rem;
  cursor: pointer;
}

.metro-row {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  margin-bottom: 1.5rem;
}

.metro-select {
  background: #1a1a1a;
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--text-main);
  padding: 0.55rem 0.8rem;
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  font-size: 0.875rem;
  cursor: pointer;
}

/* Flash bar */
.metro-flash {
  height: 34px;
  border-radius: 8px;
  background: rgba(var(--w),0.02);
  border: 1px solid rgba(var(--w),0.05);
  margin-bottom: 0.9rem;
  position: relative;
  overflow: hidden;
}

.metro-flash::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 10px;
  opacity: 0;
}

@keyframes metro-flash-fade {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

.metro-flash--beat::after {
  background: rgba(220,220,220,0.18);
  animation: metro-flash-fade 0.22s ease-out forwards;
}

.metro-flash--accent::after {
  background: rgba(216,168,79,0.55);
  animation: metro-flash-fade 0.22s ease-out forwards;
}

.metro-flash--sub::after {
  background: rgba(var(--w),0.07);
  animation: metro-flash-fade 0.12s ease-out forwards;
}

/* Beat dots */
.metro-beat-dots {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  justify-content: center;
  margin-bottom: 1rem;
}

.metro-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.12);
  transition: background 0.05s ease, transform 0.05s ease, box-shadow 0.05s ease;
  cursor: pointer;
  flex-shrink: 0;
}

.metro-dot.accent {
  background: rgba(216, 168, 79, 0.18);
  border-color: rgba(216, 168, 79, 0.38);
}

.metro-dot.active {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.35);
  box-shadow: 0 0 8px rgba(216,168,79,0.5);
}

.metro-dot.muted {
  background: rgba(255, 255, 255, 0.02);
  border: 1px dashed rgba(255, 255, 255, 0.12);
  opacity: 0.4;
}

.metro-dot.muted.active {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.2);
  border-style: dashed;
  transform: scale(1.1);
  box-shadow: none;
  opacity: 0.5;
}

/* Subdivision segment display */
.metro-sub-display {
  display: flex;
  gap: 3px;
  margin-bottom: 1.2rem;
  padding: 0 2px;
}

.metro-sub-display:empty { display: none; }

.metro-sub-seg {
  flex: 1;
  height: 10px;
  border-radius: 3px;
  background: rgba(var(--w),0.05);
  border: 1px solid rgba(var(--w),0.08);
  transition: background 0.08s ease, border-color 0.08s ease;
}

.metro-sub-seg--gap { margin-left: 7px; }

.metro-sub-seg.active {
  background: rgba(216,168,79,0.55);
  border-color: rgba(216,168,79,0.8);
  transition: none;
}

.metro-sub-seg.sub-active {
  background: rgba(220,220,220,0.28);
  border-color: rgba(220,220,220,0.45);
  transition: none;
}

/* Metronome — a compact performance surface rather than a stack of
   unrelated form rows. All selectors stay on the one card so the shared
   controls used by the other Practice tools keep their existing layout. */
.practice-page {
  --practice-readout-size: 3.5rem;
  --practice-readout-height: 4.8rem;
}

#tool-metronome {
  overflow: visible;
  padding: 0;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.1);
  border-radius: 14px;
}

#tool-metronome .metro-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem 1.45rem 0.9rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
}

#tool-metronome .tool-title {
  margin: 0;
}

#tool-metronome .metro-hint {
  max-width: none;
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.55;
}

#tool-metronome .metro-stage {
  padding: 1.15rem 1.45rem 0.95rem;
}

#tool-metronome .metro-bpm-control,
.practice-page .rhythm-tempo-block .metro-bpm-control {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) 44px;
  align-items: center;
  gap: 0.9rem;
  min-height: var(--practice-readout-height);
}

#tool-metronome .metro-bpm-row,
.practice-page .rhythm-tempo-block .metro-bpm-row {
  justify-content: center;
  margin: 0;
}

#tool-metronome .metro-bpm-display,
.practice-page .rhythm-tempo-block .metro-bpm-display {
  font-family: var(--font-main);
  font-size: var(--practice-readout-size);
  font-weight: 700;
  color: var(--text-main);
  letter-spacing: -0.04em;
}

#tool-metronome .metro-bpm-unit,
.practice-page .rhythm-tempo-block .metro-bpm-unit {
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

#tool-metronome .metro-step-btn,
.practice-page .rhythm-tempo-block .metro-step-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  color: var(--text-main);
  background: rgba(var(--w), 0.035);
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: 8px;
  font-family: var(--font-main);
  font-size: 1.2rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

#tool-metronome .metro-step-btn:hover,
.practice-page .rhythm-tempo-block .metro-step-btn:hover:not(:disabled) {
  background: rgba(var(--w), 0.065);
  border-color: rgba(216, 168, 79, 0.55);
}

#tool-metronome .metro-step-btn:focus-visible,
.practice-page .rhythm-tempo-block :is(.metro-step-btn, .metro-slider):focus-visible,
#tool-metronome .metro-slider:focus-visible,
#tool-metronome .metro-dot:focus-visible,
#tool-metronome .metro-select:focus-visible,
#tool-metronome .metro-opt-btn:focus-visible,
#tool-metronome .metro-settings-trigger:focus-visible,
#tool-metronome .metro-row--bottom button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

#tool-metronome .metro-tempo-name,
.practice-page .rhythm-tempo-block .metro-tempo-name {
  margin: 0.05rem 0 0.8rem;
  color: var(--accent);
  font-size: 0.8rem;
  font-style: normal;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: center;
}

#tool-metronome .metro-slider,
.practice-page .rhythm-tempo-block .metro-slider {
  --metro-progress: 21.43%;
  display: block;
  width: 100%;
  height: 18px;
  margin: 0;
  appearance: none;
  background: transparent;
}

#tool-metronome .metro-slider::-webkit-slider-runnable-track,
.practice-page .rhythm-tempo-block .metro-slider::-webkit-slider-runnable-track {
  height: 4px;
  background: linear-gradient(to right,
    var(--accent) 0 var(--metro-progress),
    rgba(var(--w), 0.14) var(--metro-progress) 100%);
  border-radius: 2px;
}

#tool-metronome .metro-slider::-webkit-slider-thumb,
.practice-page .rhythm-tempo-block .metro-slider::-webkit-slider-thumb {
  width: 16px;
  height: 16px;
  margin-top: -6px;
  appearance: none;
  background: var(--accent);
  border: 0;
  border-radius: 50%;
}

#tool-metronome .metro-slider::-moz-range-track,
.practice-page .rhythm-tempo-block .metro-slider::-moz-range-track {
  height: 4px;
  background: rgba(var(--w), 0.14);
  border: 0;
  border-radius: 2px;
}

#tool-metronome .metro-slider::-moz-range-progress,
.practice-page .rhythm-tempo-block .metro-slider::-moz-range-progress {
  height: 4px;
  background: var(--accent);
  border-radius: 2px;
}

#tool-metronome .metro-slider::-moz-range-thumb,
.practice-page .rhythm-tempo-block .metro-slider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  background: var(--accent);
  border: 0;
  border-radius: 50%;
}

#tool-metronome .metro-slider-scale,
.practice-page .rhythm-tempo-block .rhythm-slider-scale {
  display: flex;
  justify-content: space-between;
  margin-top: 0.15rem;
  color: var(--text-muted);
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  opacity: 0.72;
}

#tool-metronome .metro-beat-dots {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.45rem;
  margin: 0.9rem 0 0.55rem;
}

#tool-metronome .metro-beat-dots[data-beats="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
#tool-metronome .metro-beat-dots[data-beats="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
#tool-metronome .metro-beat-dots[data-beats="5"] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
#tool-metronome .metro-beat-dots[data-beats="6"] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
#tool-metronome .metro-beat-dots[data-beats="7"] { grid-template-columns: repeat(7, minmax(0, 1fr)); }
#tool-metronome .metro-beat-dots[data-beats="9"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
#tool-metronome .metro-beat-dots[data-beats="11"],
#tool-metronome .metro-beat-dots[data-beats="12"] { grid-template-columns: repeat(6, minmax(0, 1fr)); }

#tool-metronome .metro-dot {
  width: auto;
  height: 44px;
  min-width: 0;
  padding: 0;
  color: var(--text-muted);
  background: rgba(var(--w), 0.03);
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: 7px;
  font-family: var(--font-main);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  box-shadow: none;
  transition: background 0.06s ease, border-color 0.06s ease, color 0.06s ease, opacity 0.15s ease;
}

/* The primary selectors in Tuner, Metronome and Drone share one 62px
   control rail. Very large meters keep the compact cells below so their
   second/third row does not make the whole Tools surface unnecessarily tall. */
#tool-metronome .metro-beat-dots:is(
  [data-beats="2"],
  [data-beats="3"],
  [data-beats="4"],
  [data-beats="5"],
  [data-beats="6"],
  [data-beats="7"]
) .metro-dot {
  height: 62px;
}

#tool-metronome .metro-dot.accent {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.06);
  border-color: rgba(216, 168, 79, 0.55);
}

#tool-metronome .metro-dot.active {
  color: #15120c;
  background: var(--accent);
  border-color: var(--accent);
  transform: none;
  box-shadow: none;
}

#tool-metronome .metro-dot.muted,
#tool-metronome .metro-dot.muted.active {
  color: var(--text-muted);
  background: rgba(var(--w), 0.018);
  border: 1px solid rgba(var(--w), 0.07);
  text-decoration: line-through;
  transform: none;
  box-shadow: none;
  opacity: 0.38;
}

#tool-metronome .metro-dot:disabled {
  cursor: default;
  opacity: 1;
}

#tool-metronome .metro-sub-display {
  gap: 3px;
  margin: 0;
  padding: 0;
}

#tool-metronome .metro-sub-seg {
  height: 5px;
  border: 0;
  border-radius: 2px;
  background: rgba(var(--w), 0.07);
}

#tool-metronome .metro-sub-seg.active {
  background: var(--accent);
  border: 0;
}

#tool-metronome .metro-sub-seg.sub-active {
  background: rgba(var(--w), 0.34);
  border: 0;
}

#tool-metronome .metro-settings {
  position: absolute;
  z-index: 30;
  bottom: calc(100% + 0.65rem);
  left: 0;
  display: grid;
  gap: 0.65rem;
  width: min(380px, calc(100vw - 2rem));
  padding: 1rem 1.15rem;
  background: var(--bg-card);
  border: 1px solid rgba(var(--w), 0.12);
  border-radius: 10px;
  box-shadow: var(--shadow-soft);
  max-height: min(70vh, 520px);
  overflow-y: auto;
}

#tool-metronome .metro-settings-menu {
  position: relative;
  min-width: 0;
}

#tool-metronome .metro-settings-menu > summary {
  list-style: none;
}

#tool-metronome .metro-settings-menu > summary::-webkit-details-marker {
  display: none;
}

#tool-metronome .metro-settings-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  width: 100%;
  min-height: 46px;
  padding: 0.55rem 0.7rem;
  color: var(--text-main);
  background: rgba(var(--w), 0.035);
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: 8px;
  font-size: 0.875rem;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

#tool-metronome .metro-settings-trigger:hover {
  background: rgba(var(--w), 0.065);
}

#tool-metronome .metro-settings-menu[open] .metro-settings-trigger {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.58);
}

#tool-metronome .metro-settings-chevron {
  width: 14px;
  height: 14px;
  margin-left: 0.1rem;
  flex: 0 0 auto;
  transition: transform 0.15s ease;
}

#tool-metronome .metro-settings-menu[open] .metro-settings-chevron {
  transform: rotate(180deg);
}

#tool-metronome:has(.metro-settings-menu[open]) {
  z-index: 30;
}

#tool-metronome .metro-settings .metro-option-group {
  display: grid;
  grid-template-columns: 108px minmax(0, 1fr);
  align-items: center;
  gap: 0.7rem;
  margin: 0;
}

#tool-metronome .metro-option-label {
  min-width: 0;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
}

#tool-metronome .metro-select {
  width: 100%;
  min-height: 40px;
  padding: 0 0.7rem;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.11);
  border-radius: 7px;
}

#tool-metronome .metro-opt-row {
  gap: 0.38rem;
}

#tool-metronome .metro-opt-btn {
  min-height: 38px;
  padding: 0.35rem 0.7rem;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.1);
  border-radius: 7px;
}

#tool-metronome .metro-opt-btn:hover {
  background: rgba(var(--w), 0.055);
  border-color: rgba(var(--w), 0.16);
}

#tool-metronome .metro-opt-btn.active {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.08);
  border-color: rgba(216, 168, 79, 0.62);
}

#tool-metronome .metro-opt-btn:disabled {
  cursor: not-allowed;
  opacity: 0.35;
}

#tool-metronome .metro-opt-btn--note {
  min-width: 40px;
  justify-content: center;
}

#tool-metronome .metro-row--bottom {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 0.8fr) minmax(0, 1.25fr);
  gap: 0.65rem;
  width: 100%;
  margin: auto 0 0;
  padding: 0.85rem 1.45rem 0.95rem;
  border-top: 1px solid rgba(var(--w), 0.08);
}

#tool-metronome[data-metro-has-tap="false"] .metro-row--bottom {
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.25fr);
}

#tool-metronome .metro-row--bottom button,
#tool-metronome .metro-settings-trigger {
  width: 100%;
  min-height: 46px;
  border-radius: 8px;
}

@media (max-width: 520px) {
  #tool-metronome .metro-card-header,
  #tool-metronome .metro-stage,
  #tool-metronome .metro-row--bottom {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  #tool-metronome .metro-bpm-control,
  .practice-page .rhythm-tempo-block .metro-bpm-control {
    grid-template-columns: 42px minmax(0, 1fr) 42px;
    gap: 0.55rem;
  }

  .practice-page {
    --practice-readout-size: 3rem;
  }

  #tool-metronome .metro-step-btn,
  .practice-page .rhythm-tempo-block .metro-step-btn {
    width: 42px;
    height: 42px;
  }

  #tool-metronome .metro-settings .metro-option-group {
    grid-template-columns: 1fr;
    gap: 0.45rem;
  }

  #tool-metronome .metro-settings {
    width: min(360px, calc(100vw - 2rem));
    padding: 1rem;
  }

  #tool-metronome .metro-select {
    width: 100%;
  }
}

/* Chord Switch Trainer (Practice > Chord switching) */
.cswitch-display {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  margin-bottom: 1rem;
}

.cswitch-diagram {
  width: 90px;
  flex-shrink: 0;
}
.cswitch-diagram svg { width: 100%; height: auto; }

.cswitch-current {
  font-size: 1.8rem;
  font-weight: 700;
  color: var(--text-main);
  line-height: 1.1;
}

.cswitch-next {
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-top: 0.25rem;
}
.cswitch-next span:last-child {
  color: var(--accent);
  font-weight: 600;
}

/* Scale Degree Trainer: the degree number is the actual prompt the
   student must act on, so it needs to visually dominate — bigger than
   the shared .cswitch-current baseline. The note name underneath
   (.degreet-note reuses .cswitch-next's already-muted/smaller styling)
   stays secondary, just confirming the mapping, never the instruction. */
.degreet-degree {
  font-size: 3rem;
  line-height: 1;
}
.degreet-note {
  margin-top: 0.4rem;
}

/* Active-recall fretboard quizzes. The shape stays visible as context, but
   the learner must choose a dot before its note/function is revealed. */
.practice-quiz-prompt {
  margin: 0.9rem 0 0;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.9rem;
}
.practice-quiz-prompt strong {
  display: block;
  margin-top: 0.3rem;
  color: var(--text-main);
  font-size: 2.7rem;
  line-height: 1;
}
.practice-quiz-answer {
  min-height: 1.35em;
  margin: 0.4rem 0 0;
  text-align: center;
  color: var(--accent);
  font-weight: 700;
}
.practice-quiz-status { min-height: 2.6rem; }
.practice-quiz-controls { margin-top: auto; }
.practice-neck-choice { cursor: pointer; outline: none; }
.practice-neck-choice .practice-quiz-dot {
  transition: opacity 0.16s ease, stroke 0.16s ease, fill 0.16s ease, transform 0.16s ease;
  transform-box: fill-box;
  transform-origin: center;
}
.practice-neck-choice:hover .practice-quiz-dot,
.practice-neck-choice:focus .practice-quiz-dot {
  opacity: 0.78;
  stroke: rgba(var(--w),0.72);
  stroke-width: 1.6;
  transform: scale(1.12);
}
.practice-quiz-dot.hint {
  opacity: 0.78;
  stroke: var(--accent);
  stroke-width: 2;
}
.practice-quiz-dot.wrong {
  fill: #d86155;
  opacity: 1;
  stroke: #fff;
  stroke-width: 1.6;
}
.practice-quiz-dot.selected { opacity: 1; }
.scalep-degree.done {
  color: var(--accent);
  border-color: rgba(216,168,79,0.38);
  background: rgba(216,168,79,0.1);
}

/* Scale Position Practice: the complete shape is quiet context; the gold
   dot is the only instruction the player needs to act on right now. */
.pos-mini-neck { width: min(100%, 310px); margin-inline: auto; }
.pos-mini-neck svg { display: block; width: 100%; height: auto; }
.pos-neck-string { stroke: rgba(var(--w),0.22); }
.pos-neck-fret { stroke: rgba(var(--w),0.1); stroke-width: 1.2; }
.pos-neck-nut { fill: rgba(235,235,235,0.65); }
.pos-neck-string-label,
.pos-neck-fret-label { fill: rgba(var(--w),0.45); font-family: var(--font-main); font-size: 8px; font-weight: 600; }
.pos-neck-dot { fill: var(--accent); stroke: none; }
.pos-neck-dot.root { fill: var(--root-accent); }
.pos-neck-dot.active { stroke: #fff; stroke-width: 1.75; }
.pos-neck-note { fill: #141414; font-family: var(--font-main); font-size: 8px; font-weight: 800; text-anchor: middle; }
.pos-neck-note.active { fill: #141414; }
.pos-location { margin-top: 0.25rem; }
@media (min-width: 761px) {
  #tool-position-practice .pos-mini-neck { margin: 1.25rem auto 0; }
}
@media (max-width: 760px) {
  #tool-position-practice .pos-mini-neck { margin: 1rem auto 0; }
}

/* ==========================================================
   PRACTICE — ARPEGGIOS TAB
   Purely additive: the two cards reuse the Scales tab's existing
   components wholesale (.tool-card, .metro-option-group,
   .metro-opt-btn, .cswitch-display, .scalep-degree*, .metro-slider,
   .pos-mini-neck/.pos-neck-*), so all that is needed here is the same
   panel-scoped grid + spacing the Scales panel already declares, plus
   the one genuinely new visual: the Chord Tone Trainer's mini neck
   shows the WHOLE arpeggio shape as quiet context and lights up only
   the tone currently being asked for, so the non-asked dots are
   dimmed. (Scale Degree Trainer steps through its shape in order, so
   it never needed that distinction.) Roots keep --root-accent either
   way — dimming changes emphasis, never the root convention.
   ========================================================== */
.pos-neck-dot--dim { opacity: 0.34; }
.pos-neck-note--dim { opacity: 0.45; }

/* Same 2-up-at-every-tier composition as the Chords and Scales panels:
   both cards need width for chip selectors, a step row / diagram and
   the BPM controls. */
.practice-page #practice-panel-arpeggios .practice-3col {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 900px) {
  .practice-page #practice-panel-arpeggios .practice-3col {
    grid-template-columns: 1fr;
  }
}

/* Shared compact canvas with the other visual-practice families, so
   switching between Chords / Scales / Arpeggios / Fretboard never makes
   the practice area jump in height. */
@media (min-width: 761px) {
  .practice-page #practice-panel-arpeggios .practice-3col > .tool-card {
    min-height: 34rem;
  }
}

/* Identical control spacing to the Scales pair. */
.practice-page #practice-panel-arpeggios .metro-option-group { margin-bottom: 0.6rem; }
.practice-page #practice-panel-arpeggios .cswitch-display { margin-bottom: 0.55rem; }
.practice-page #tool-arpeggio-practice .metro-bpm-row,
.practice-page #tool-chordtone-practice .metro-bpm-row { margin-top: auto; }
.practice-page #practice-panel-arpeggios .metro-slider { margin-bottom: 0.85rem; }
.practice-page #practice-panel-arpeggios .metro-row { margin-bottom: 0.75rem; }
.practice-page #tool-arpeggio-practice .scalep-degrees { margin: 0.45rem 0; }

@media (min-width: 761px) {
  #tool-chordtone-practice .pos-mini-neck { margin: 1.25rem auto 0; }
}
@media (max-width: 760px) {
  #tool-chordtone-practice .pos-mini-neck { margin: 1rem auto 0; }

  /* Same no-overflow treatment the Scale Practice step row gets: the
     chord-tone row stays one line and shares the width instead of
     wrapping or pushing the card sideways. */
  #tool-arpeggio-practice .scalep-degrees {
    flex-wrap: nowrap;
    gap: 0.2rem;
    width: 100%;
  }
  #tool-arpeggio-practice .scalep-degree {
    min-width: 0;
    flex: 1 1 0;
    padding: 0.28rem 0.1rem;
    font-size: 0.75rem;
  }
}

.cswitch-need-hint {
  font-size: 0.78rem;
  color: var(--text-muted);
}

/* Option groups (Sound + Subdivision) */
.metro-options {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1.2rem;
}

/* margin-bottom (not a "+ .metro-option-group" adjacent-sibling rule)
   so every option row gets breathing room below it whether the next
   element is another option group, the action button row, or the
   exercise display — the widget-hierarchy spacing goal (title → desc
   → filters → action → exercise → score) needs a gap after the LAST
   group too, not just between consecutive groups. */
.metro-option-group {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.9rem;
}

.metro-option-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  font-weight: 600;
  min-width: 72px;
  flex-shrink: 0;
}

.metro-opt-row {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* font-size/padding: see the shared .tab-filter group (~line 3162). */
.metro-opt-btn {
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.07);
  color: var(--text-muted);
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.metro-opt-btn:hover {
  background: rgba(var(--w),0.08);
  color: var(--text-main);
}

.metro-opt-btn.active {
  background: rgba(216,168,79,0.15);
  border-color: rgba(216,168,79,0.4);
  color: var(--accent);
}

/* Reused when a .metro-opt-btn row doubles as an answer picker (e.g.
   Chord Diagram Trainer) — same correct/wrong language as .ear-option. */
.metro-opt-btn.correct {
  background: rgba(80, 200, 120, 0.18);
  border-color: rgba(80, 200, 120, 0.6);
  color: #8fe0a8;
}

.metro-opt-btn.wrong {
  background: rgba(220, 80, 80, 0.16);
  border-color: rgba(220, 80, 80, 0.55);
  color: #f0a0a0;
}

/* Subdivision buttons show only the rhythmic-notation glyph (no text
   label) so Whole/Half/Quarter/Eighth/Sixteenth all fit on one row —
   the name is still available via title/aria-label (data-i18n-title). */
.metro-opt-btn--note {
  display: inline-flex;
  align-items: center;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}

.metro-note-icon {
  flex-shrink: 0;
  display: block;
}

/* .metro-opt-btn--note's own `display: inline-flex` has the same
   specificity as the UA [hidden] rule and comes later in the cascade,
   so it would otherwise win and keep a hidden note button visible —
   this reasserts display:none for any subdivision button toggled off
   by updateSubAvailability() in practice.js. */
.metro-opt-btn[hidden] {
  display: none;
}

/* Chord Practice — Barre mode's compact shape explanation, shown beside
   the diagram inside .cswitch-names. Kept short by design (see JS) so it
   never grows the card past the Open-mode height. */
.chordp-tip {
  font-size: 0.78rem;
  line-height: 1.35;
}
.chordp-tip[hidden] {
  display: none;
}

/* Chord Changes — compact progression-slot editor. Each slot is a
   selectable chip (same visual language as .metro-opt-btn) showing the
   chord name; clicking one opens a small root/quality popover anchored
   to it. Wraps on narrow widths instead of overflowing horizontally. */
.cswitch-slots {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* The remove badge is a corner badge: it sits ON the chip's top-right
   corner, half outside it. The padding/negative-margin pair reserves
   that corner inside the slot's own box, so the badge is drawn exactly
   where it always was while the slot no longer reports a 6px overhang —
   and the chip row keeps its original spacing and alignment. */
.cswitch-slot {
  position: relative;
  padding: 6px 6px 0 0;
  margin: -6px -6px 0 0;
}

.cswitch-slot-btn {
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.07);
  color: var(--text-main);
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.cswitch-slot-btn:hover {
  background: rgba(var(--w),0.08);
}
.cswitch-slot-btn.is-open {
  background: rgba(216,168,79,0.15);
  border-color: rgba(216,168,79,0.4);
  color: var(--accent);
}
.cswitch-slot-btn.is-playing {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.cswitch-slot-remove {
  position: absolute;
  top: 0;
  right: 0;
  width: 16px;
  height: 16px;
  line-height: 14px;
  border-radius: 50%;
  background: var(--bg-card);
  border: 1px solid rgba(var(--w),0.2);
  color: var(--text-muted);
  font-size: 0.65rem;
  cursor: pointer;
  padding: 0;
}
.cswitch-slot-remove:hover {
  color: #f0a0a0;
  border-color: rgba(220, 80, 80, 0.55);
}

.cswitch-add-btn {
  background: transparent;
  border: 1px dashed rgba(var(--w),0.2);
  color: var(--text-muted);
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  font-size: 0.85rem;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
}
.cswitch-add-btn:hover {
  color: var(--text-main);
  border-color: rgba(var(--w),0.35);
}

/* Root/quality selector popover — same floating-panel language as
   .level-picker-popover, positioned below its slot instead of above. */
.cswitch-slot-popover {
  position: absolute;
  top: calc(100% + 0.4rem);
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 220px;
  padding: 0.6rem;
  background: var(--bg-card);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-small);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  z-index: 210;
}
.cswitch-slot-popover[hidden] {
  display: none;
}
.cswitch-slot-popover .metro-opt-row {
  gap: 0.3rem;
}
.cswitch-slot-popover .cswitch-root-row {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
}
.cswitch-slot-popover .cswitch-root-row .metro-opt-btn {
  min-width: 0;
  padding-inline: 0;
}
.cswitch-slot-popover .cswitch-quality-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.cswitch-slot-popover .cswitch-quality-row .metro-opt-btn {
  min-width: 0;
  padding-inline: 0.45rem;
  white-space: nowrap;
}

/* On phones the timing choice is one compact control, matching the
   "Beats per chord" row beside it rather than sending its values below. */
@media (max-width: 760px) {
  #tool-chord-practice .chordp-seconds-group {
    flex-wrap: nowrap;
    gap: 0.7rem;
  }
  #tool-chord-practice .chordp-seconds-group .metro-option-label {
    min-width: 0;
    letter-spacing: 0.08em;
  }
  #tool-chord-practice .chordp-seconds-label-full { display: none; }
  #tool-chord-practice .chordp-seconds-label-short { display: inline; }
  #tool-chord-switch .cswitch-beats-label-full { display: none; }
  #tool-chord-switch .cswitch-beats-label-short { display: inline; }
  #tool-chord-practice .chordp-seconds-group .metro-opt-row {
    flex: 1 1 auto;
    flex-wrap: nowrap;
    gap: 0.3rem;
  }
  #tool-chord-practice .chordp-seconds-group .metro-opt-btn {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: 0.45rem;
  }
}

@media (min-width: 761px) {
  #tool-chord-practice .chordp-seconds-label-short { display: none; }
  #tool-chord-switch .cswitch-beats-label-short { display: none; }
}

/* Chord Practice shares the same tall desktop row as Chord Changes. Let its
   diagram occupy the available center space, then anchor its timing controls
   to the same lower zone as Beats per chord + Start next door. */
@media (min-width: 761px) {
  #tool-chord-practice .cswitch-display {
    flex: 1 1 auto;
    min-height: 145px;
    align-items: center;
    justify-content: center;
  }
  #tool-chord-practice .cswitch-diagram {
    width: 155px;
  }
}

.metro-volume-slider {
  flex: 1;
  max-width: 180px;
  accent-color: var(--accent);
  cursor: pointer;
}


/* ==========================================================
   CHORD VISUALIZER
   ========================================================== */

/* The chord card spans the full width of the practice grid */
.chord-visualizer-card {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.chord-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-right: 2.2rem;
}

.chord-body {
  display: flex;
  gap: 2.5rem;
  align-items: flex-start;
}

/* Left: selector panel */
.chord-selector {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  flex: 1;
  min-width: 0;
}

/* Root row */
.chord-roots {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
}

/* Quality groups stacked */
.chord-qualities {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.chord-quality-group {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.chord-quality-title {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  opacity: 0.55;
  font-weight: 600;
  padding-left: 0.1rem;
}

.chord-quality-title--toggle {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
  user-select: none;
  opacity: 0.75;
  transition: opacity 0.15s;
}

.chord-quality-title--toggle:hover {
  opacity: 1;
}

.chord-quality-icon {
  font-size: 0.65rem;
  transition: transform 0.2s ease;
  display: inline-block;
}

.chord-quality-group--collapsed .chord-quality-row {
  display: none;
}

.chord-quality-group--collapsed .chord-quality-icon {
  transform: rotate(-90deg);
}

.chord-quality-row {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
}

/* Shared button style */
.chord-btn {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--text-muted);
  padding: 0.35rem 0.7rem;
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  font-size: 0.82rem;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  white-space: nowrap;
}

.chord-btn:hover {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-main);
}

.chord-btn.active {
  background: rgba(216, 168, 79, 0.15);
  border-color: rgba(216, 168, 79, 0.4);
  color: var(--accent);
}

/* Right: diagram panel */
.chord-display {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex-shrink: 0;
}

/* Flex row: primary voicing + variants block */
.chord-diagram-wrap {
  margin-top: 0.5rem;
}

.chord-diagram-wrap svg {
  display: block;
  width: 100%;
  max-width: 210px;
  height: auto;
  margin: 0 auto;
}

.chord-symbol-label {
  text-align: center;
  font-family: var(--font-main);
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.15rem;
}

#harmonyIntroWidget svg {
  display: block;
  width: 100%;
  height: auto;
}

#harmonyIntroWidget .iv-chips {
  margin-top: 12px;
}

/* Theory's chord widgets have nothing above the diagram (unlike Fretboard's
   info-bar-then-diagram stack, which needs that top margin) — center it
   truly on both axes instead of leaving an offset from that spacing. */
.theory-lesson-widget .chord-diagram-wrap {
  margin-top: 0;
}

.chord-diagram-inner {
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}

/* Primary voicing â€” name + notes centred above SVG */
.chord-voicing--primary {
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 1 !important;
  flex-shrink: 0;
  width: 320px;
}

.chord-name {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
  letter-spacing: 0.05em;
  text-align: center;
}

.chord-notes {
  font-size: 0.82rem;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  text-align: center;
  margin-bottom: 0.5rem;
  opacity: 0.85;
}

/* Variants block */
.chord-variants {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding-left: 1rem;
  border-left: 1px solid rgba(var(--w),0.07);
}

.chord-variants-label {
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  opacity: 0.35;
  font-weight: 600;
}

.chord-variants-row {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
}

/* Individual alternate voicing */
.chord-voicing {
  flex-shrink: 0;
  width: 170px;
  opacity: 0.4;
  transition: opacity 0.15s;
}

.chord-voicing:hover {
  opacity: 0.7;
}

.chord-voicing svg,
.chord-voicing--primary svg {
  width: 100%;
  height: auto;
}

.chord-unavailable {
  color: var(--text-muted);
  font-size: 0.875rem;
  text-align: center;
  padding: 2rem 0;
  margin: 0;
}

@media (max-width: 700px) {
  .chord-body {
    flex-direction: column;
  }

  .chord-variants {
    display: none;
  }
}


/* ==========================================================
   CHORD PROGRESSIONS
   ========================================================== */

.prog-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
  padding-right: 2.2rem;
}

.prog-mode-btn {
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.28rem 0.85rem;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(var(--w),0.1);
  background: rgba(var(--w),0.04);
  color: var(--text-muted);
  cursor: pointer;
  font-family: var(--font-main);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.prog-mode-btn:hover,
.prog-mode-btn.minor,
.prog-mode-btn.dom {
  background: rgba(216,168,79,0.12);
  border-color: rgba(216,168,79,0.35);
  color: var(--accent);
}

#progRoots {
  display: flex;
  flex-wrap: wrap;
  gap: 0.22rem;
  margin-bottom: 1.2rem;
}

.prog-container {
  border: 1px solid rgba(var(--w),0.07);
  border-radius: 12px;
  padding: 1rem;
  background: rgba(var(--w),0.015);
}

.prog-tabs {
  margin-bottom: 0.85rem;
  flex-wrap: wrap;
}

.prog-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0.65rem;
  max-height: 380px;
  overflow-y: auto;
  padding-right: 0.3rem;
}

/* ==========================================================
   THEORY â€” CAGED CHORD SHAPES
   ========================================================== */

#tool-caged {
  padding: 1.4rem 1.5rem;
}

#tool-caged .caged-topbar {
  margin-bottom: 0.75rem;
}

#tool-caged .caged-hint {
  margin: 0 0 0.8rem;
  font-size: 0.72rem;
}

#tool-caged .caged-shape-box {
  cursor: pointer;
  border-radius: 8px;
  padding: 4px;
  transition: background 0.15s;
}

#tool-caged .caged-shape-box:hover {
  background: rgba(var(--w),0.04);
}

#tool-caged .caged-shape-box--active {
  outline: 1px solid rgba(216,168,79,0.5);
  background: rgba(216,168,79,0.05);
}

#tool-caged .caged-shapes {
  gap: 0.5rem;
}

#tool-caged .caged-shape-box svg {
  width: 100%;
  max-width: 100%;
  height: auto;
}


.caged-topbar {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.6rem;
  margin-bottom: 0.75rem;
  padding-right: 2.2rem;
}
.caged-topbar .sv-cats {
  margin-bottom: 0;
  margin-top: -0.35rem;
}

.caged-controls {
  margin-top: 0;
}

.caged-type-row {
  display: flex;
  justify-content: center;
  margin-bottom: 0.75rem;
}

.caged-hint {
  margin: 0 0 0.8rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.caged-roots {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-bottom: 0.9rem;
}

.caged-root-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.1);
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.caged-root-btn:hover {
  border-color: rgba(216, 168, 79, 0.5);
}

.caged-root-btn.active {
  background: var(--accent);
  color: #111;
  border-color: var(--accent);
}

.caged-shapes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.caged-shape-box {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
}

.caged-shape-letter {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.05em;
  line-height: 1;
}

.caged-shape-sublabel {
  font-size: 0.65rem;
  font-weight: 500;
  color: var(--text-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: -0.4rem;
}

.caged-shape-box svg {
  width: 100%;
  height: auto;
}

.caged-shape-box--open {
  outline: 1px solid rgba(216,168,79,0.45);
  border-radius: 6px;
  padding: 3px;
}

.caged-shape-open-label {
  font-size: 0.62rem;
  font-weight: 500;
  color: rgba(216,168,79,0.6);
  letter-spacing: 0.05em;
  margin-top: 0;
}

#ksDisplay {
  padding-right: 0.3rem;
}

.prog-card {
  background: rgba(var(--w),0.025);
  border: 1px solid rgba(var(--w),0.055);
  border-radius: 10px;
  padding: 0.85rem 1rem;
  cursor: default;
  transition: border-color 0.15s, background 0.15s;
}

.prog-card:hover {
  background: rgba(var(--w),0.04);
  border-color: rgba(var(--w),0.09);
}

.prog-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
}

.prog-label {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text-main);
  white-space: nowrap;
}

.prog-roman {
  font-size: 0.62rem;
  color: var(--text-muted);
  opacity: 0.4;
  font-family: 'Courier New', monospace;
  letter-spacing: 0.03em;
}

.prog-chords {
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 0.3rem;
  letter-spacing: 0.01em;
}

.prog-desc {
  font-size: 0.75rem;
  color: var(--text-muted);
  opacity: 0.45;
  font-style: italic;
  line-height: 1.6;
}

/* ==========================================================
   CIRCLE OF FIFTHS
   ========================================================== */

.vis-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
  flex-wrap: wrap;
  gap: 0.75rem;
}


/* ==========================================================
   FRETBOARD VISUALIZER
   ========================================================== */

/* ---- Chord type selector ---- */
.fb-type-selector {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
}

.fb-type-main {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
}

.fb-chord-btn {
  background: rgba(var(--w),0.06);
  border: 1px solid rgba(var(--w),0.12);
  color: var(--text-muted);
  font-size: 0.82rem;
  padding: 0.3rem 0.75rem;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  font-family: var(--font-main);
}

.fb-chord-btn:hover {
  background: rgba(var(--w),0.1);
  color: var(--text-main);
}

.fb-chord-btn.active {
  background: rgba(216,168,79,0.18);
  border-color: rgba(216,168,79,0.45);
  color: var(--accent);
}

.fb-chord-btn--main {
  font-size: 0.88rem;
  padding: 0.35rem 1rem;
  font-weight: 600;
}

.fb-more-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.8rem;
  cursor: pointer;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  transition: color 0.15s;
}

.fb-more-btn:hover { color: var(--text-main); }

.fb-type-extra {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.6rem 0 0.2rem;
  border-top: 1px solid rgba(var(--w),0.06);
}

.fb-extra-group { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; }

.fb-extra-label {
  font-size: 0.72rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-right: 0.25rem;
  white-space: nowrap;
}

/* ---- Layer toggles ---- */
.fb-layers {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.65rem;
}

.fb-layers-label {
  font-size: 0.75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  margin-right: 0.25rem;
}

.fb-layer-btn {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: rgba(var(--w),0.05);
  border: 1px solid rgba(var(--w),0.1);
  color: var(--text-muted);
  font-size: 0.8rem;
  padding: 0.28rem 0.75rem;
  border-radius: var(--radius-pill);
  cursor: pointer;
  font-family: var(--font-main);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.fb-layer-btn::before {
  content: '';
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.4;
}

.fb-layer-btn[data-layer="roots"]     { --layer-c: #d8a84f; }
.fb-layer-btn[data-layer="arpeggio"]  { --layer-c: rgba(var(--w),0.85); }
.fb-layer-btn[data-layer="pentatonic"]{ --layer-c: #5b9bd5; }
.fb-layer-btn[data-layer="scale"]     { --layer-c: #84c47a; }

.fb-layer-btn.active {
  border-color: var(--layer-c, rgba(var(--w),0.3));
  color: var(--layer-c, var(--text-main));
}

.fb-layer-btn.active::before { opacity: 1; background: var(--layer-c, currentColor); }

/* ---- Sub-toggles (penta / scale) ---- */
.fb-sub-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.fb-sub-label {
  font-size: 0.75rem;
  color: var(--text-muted);
  min-width: 2.8rem;
}

/* ---- Chord Visualizer layout ---- */
.cv-header {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 0.75rem;
}

.cv-type-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.cv-type-extra {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.5rem 0 0.2rem;
  border-top: 1px solid rgba(var(--w),0.06);
}

.cv-caged-shapes,
.fb-caged-shapes {
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
  justify-content: center;
  margin: 1rem 0 0.5rem;
  padding: 1.25rem 1rem;
  background: rgba(var(--w),0.03);
  border-radius: var(--radius-small);
}

/* Large CAGED shapes inside chord visualizer â€” full width grid */
#cvCagedShapes {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0.75rem;
}

.cv-intro {
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.55;
  margin: 0 0 1rem;
}

/* â”€â”€ Category buttons (Maj / min / dom7) â”€â”€ */
.cv-cats {
  display: flex;
  gap: 0.5rem;
  margin: 0.75rem 0 0.5rem;
}
.cv-cat-btn {
  flex: 1;
  padding: 0.55rem 0;
  border: 2px solid rgba(var(--w),0.12);
  border-radius: var(--radius-small);
  background: transparent;
  color: var(--text-muted);
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.cv-cat-btn.active { border-color: var(--accent); color: var(--accent); background: rgba(216,168,79,0.08); }
.cv-cat-btn:not(.active):hover { border-color: rgba(var(--w),0.25); color: var(--text-main); }

/* â”€â”€ Subtype buttons â”€â”€ */
.cv-subtypes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.75rem;
}
.cv-sub-btn {
  padding: 0.3rem 0.65rem;
  border: 1px solid rgba(var(--w),0.12);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font-size: 0.82rem;
  cursor: pointer;
  transition: border-color 0.12s, color 0.12s;
}
.cv-sub-btn--base {
  font-weight: 700;
  font-size: 0.88rem;
}
.cv-sub-btn.active {
  border-color: rgba(var(--w),0.4);
  color: var(--text-main);
  background: rgba(var(--w),0.06);
}
.cv-sub-btn:hover:not(.active) { border-color: rgba(var(--w),0.25); color: var(--text-main); }
.cv-sub-more-btn { opacity: 0.6; }
.cv-sub-more-btn:hover { opacity: 1; }
.cv-sub-more {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  width: 100%;
  padding-top: 0.35rem;
}

.cv-chord-info {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid rgba(var(--w),0.06);
  margin-bottom: 0.25rem;
}
.cv-chord-name {
  font-size: 2rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: -0.02em;
}
.cv-chord-notes {
  font-size: 0.82rem;
  color: var(--text-muted);
  letter-spacing: 0.02em;
}

.fb-caged-shape {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
}

.fb-caged-shape svg {
  width: 100%;
  height: auto;
}

.fb-caged-shape-label {
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.05em;
}

.fb-caged-shape-pos {
  font-size: 0.75rem;
  color: var(--text-muted);
}

.cv-play-btn {
  margin-top: 0.15rem;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 0.7rem;
  line-height: 1;
  color: var(--accent);
  background: rgba(216, 168, 79, 0.1);
  border: 1px solid rgba(216, 168, 79, 0.35);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background 0.15s ease, transform 0.1s ease;
}
.cv-play-btn:hover   { background: rgba(216, 168, 79, 0.22); }
.cv-play-btn:active  { transform: scale(0.9); }

/* â•â•â• UNIFIED FRETBOARD VISUALIZER â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
/* Top zone: controls (left) + explanation (right) */
.fbx-top {
  display: flex;
  gap: 1.5rem;
  align-items: stretch;
  margin-top: 1.2rem;
  flex-wrap: wrap;
}
.fbx-about {
  flex: 1 1 320px;
  min-width: 0;
  background: rgba(var(--w),0.03);
  border: 1px solid rgba(var(--w),0.06);
  border-radius: var(--radius-media);
  padding: 1rem 1.15rem;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.fbx-about-title {
  margin: 0 0 0.55rem;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--accent);
}
.fbx-about p { margin: 0 0 0.65rem; }
.fbx-about ul {
  margin: 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.fbx-about strong { color: var(--text-main); font-weight: 600; }

/* Aligned control stack: [label | controls] rows */
.fbx-controls {
  flex: 1.25 1 360px;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}
.fbx-row {
  display: grid;
  grid-template-columns: 64px 1fr;
  align-items: start;
  column-gap: 1rem;
}
.fbx-row-label {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding-top: 0.5rem;
  white-space: nowrap;
}
/* root selector: dim accidentals so the 7 naturals stand out */
.fbx-root-btn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1.05;
}
.fbx-root-btn.fbx-acc:not(.active) {
  color: rgba(var(--w),0.4);
  background: rgba(var(--w),0.015);
  border-color: rgba(var(--w),0.06);
}
/* stacked enharmonics: common spelling on top, alternative below */
.fbx-root-btn .rt-bot {
  font-size: 0.62rem;
  opacity: 0.6;
}

/* control cell: wrapping buttons share one left edge */
.fbx-cell {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  min-width: 0;
}

/* Legend row aligns with the control labels; toggle sits under the swatches */
.fbx-legend-row {
  grid-template-columns: 64px 1fr;
  align-items: start;
}
.fbx-legend-wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.65rem;
}
.fbx-legend-toggle {
  font-size: 0.78rem;
  padding: 0.3rem 0.75rem;
  white-space: nowrap;
}
.fbx-legend-row .fbx-row-label { padding-top: 0; }

/* Density stepper (legacy pill, kept for other uses) */
.fbx-density {
  display: inline-flex;
  flex-wrap: wrap;
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.08);
  border-radius: var(--radius-pill);
  padding: 3px;
  gap: 2px;
}

/* The CAGED density stepper had its own .dens-stepper pill group here.
   Both it and the five-island view it belonged to are gone; the layer
   stepper that replaced it carries .sv-cats.sv-cats--inline, the
   same class the Intermediate CAGED layer stepper and every position
   stepper on the page carry, so Advanced/Expert read the shared active
   state instead of a solid gold fill of their own. Content unchanged:
   the same density levels, in the same order. */

/* Detail slider: stops on a track, cumulative gold fill */
.fbx-slider {
  width: 100%;
  max-width: 480px;
}
.fbx-slider-track {
  position: relative;
  height: 42px;
}
.fbx-slider-line {
  position: absolute;
  top: 6px;
  left: 7px;
  right: 7px;
  height: 3px;
  border-radius: 2px;
  background: rgba(var(--w),0.12);
  pointer-events: none;
}
.fbx-slider-line--fill {
  right: auto;
  width: 0;
  background: var(--accent);
  transition: width 0.18s ease;
}
.fbx-slider-step {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
}
.fbx-knob {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  box-sizing: border-box;
  background: var(--bg-section);
  border: 2px solid rgba(var(--w),0.25);
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.fbx-knob-lbl {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
  transition: color 0.15s ease;
}
.fbx-slider-step.done  .fbx-knob,
.fbx-slider-step.active .fbx-knob {
  background: var(--accent);
  border-color: var(--accent);
}
.fbx-slider-step.active .fbx-knob {
  box-shadow: 0 0 0 4px rgba(216,168,79,0.18);
}
.fbx-slider-step.active .fbx-knob-lbl { color: var(--accent); }
.fbx-slider-step:hover  .fbx-knob-lbl { color: var(--text-main); }
/* padding/font-size/font-weight: see the shared .tab-secondary group
   (~line 3153) — not repeated here so there's one source of truth. */
.fbx-step-btn {
  font-family: var(--font-main);
  color: var(--text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.fbx-step-btn:hover  { color: var(--text-main); }
/* cumulative: every step up to the active one stays tinted */
.fbx-step-btn.done {
  color: var(--accent);
  background: rgba(216,168,79,0.14);
}
/* LEVEL 2 (category/subtabs) canonical active state — used by the plain
   category/position pickers (#fbxCats, #fbxPositions), by the
   Intermediate CAGED layer stepper (#cagedIntLayers) and, since the
   presentation migration, by the Advanced/Expert CAGED density stepper
   as well. There is no CAGED-only active state any more. */
.fbx-step-btn.active {
  color: var(--accent);
  background: rgba(216,168,79,0.08);
  border-color: rgba(216,168,79,0.28);
}

/* ── CORE / ADDITIONAL / REFERENCE ──
   The divider inside a category or scale row at Advanced and Expert. It is
   a LABEL, never a control: no border, no hit area, no hover, and it never
   receives .active — the click handlers select on [data-cat]/[data-scale],
   which this element deliberately does not carry.

   Sized and spaced to read as the heading of the chips after it rather
   than as one more chip: smaller, upper-case, tracked out, and with the
   row's own gap doubled on its left so the break lands before the label
   and not inside the group it names. */
.fbx-track-label {
  align-self: center;
  flex: 0 0 auto;
  margin-left: 0.35rem;
  padding-left: 0.75rem;
  border-left: 1px solid rgba(var(--w),0.12);
  font-family: var(--font-main);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.55;
  white-space: nowrap;
  pointer-events: none;
}
/* The first chip after a label keeps the row's ordinary gap; the rule
   above only opens space BEFORE the label. */

/* Legend */
.fbx-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1.1rem;
}
.fbx-leg-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.fbx-leg-item svg { flex: 0 0 auto; }
.fbx-leg-sep {
  width: 1px;
  align-self: stretch;
  min-height: 1.1rem;
  background: rgba(var(--w),0.12);
}

/* Shapes + map sections (stacked, full width) */
.fbx-shapes-section,
.fbx-map-section {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 1.25rem;
}
/* divider separating the controls from the visuals */
.fbx-shapes-section {
  margin-top: 1.4rem;
  padding-top: 1.4rem;
  border-top: 1px solid rgba(var(--w),0.06);
}

.fbx-shapes-controls {
  margin-bottom: 0.9rem;
}
.fbx-panel-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.fbx-panel-sub {
  margin-left: 0.4rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: none;
  opacity: 0.6;
}

/* Shapes header: chord name + notes on the left, panel label on the right */
.fbx-shapes-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}
.fbx-result-inline {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}
.fbx-result-inline .sv-name {
  font-size: 1.15rem;
  font-weight: 700;
}
.fbx-result-inline .sv-formula {
  font-size: 0.82rem;
  color: var(--text-muted);
}

.fbx-map-header {
  display: flex;
  justify-content: flex-end;
  padding: 0.4rem 0.75rem 0.1rem;
}

/* Outer card wrapping shapes + fretboard map as one unit */
.fbx-shapes-map-wrap {
  background: rgba(var(--w),0.03);
  border: 1px solid rgba(var(--w),0.06);
  border-radius: var(--radius-media);
  overflow: hidden;
}

/* Shapes frame: holds the ref+boxes row and the legend footer */
.fbx-shapes-frame {
  padding: 1.25rem 1rem 1rem;
}
.fbx-shapes-body {
  display: flex;
  align-items: flex-start;
  gap: 1.25rem;
}
/* legend footer inside the frame */
.fbx-shapes-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem 1rem;
  margin-top: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid rgba(var(--w),0.07);
}
.fbx-shapes-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  justify-content: space-between;
  width: 100%;
}

/* Reference column â€” sizes to its widest row (the chromatic scale) */
.fbx-ref {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.fbx-ref-row {
  display: flex;
  flex-direction: column;
  gap: 0.12rem;
  padding: 0.4rem 0.55rem;
  border-left: 2px solid transparent;
  border-radius: 0 6px 6px 0;
}
.fbx-ref-row.active {
  background: rgba(216,168,79,0.08);
  border-left-color: var(--accent);
}
.fbx-ref-name {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.3rem;
}
/* degree-over-note cells, aligned per note, kept on one line */
.fbx-ref-cells {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.28rem 0.42rem;
}
.fbx-ref-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.2;
  min-width: 1.1rem;
}
.fbx-ref-cell .fbx-ref-deg {
  font-size: 0.66rem;
  font-weight: 700;
  color: var(--accent);
}
.fbx-ref-cell .fbx-ref-note {
  font-size: 0.76rem;
  color: var(--text-muted);
}
.fbx-ref-cell .fbx-ref-alt {
  font-size: 0.76rem;
  color: var(--text-muted);
}
.fbx-ref-cell .fbx-ref-deg-alt {
  font-size: 0.66rem;
  font-weight: 700;
  color: var(--accent);
}

/* Mobile: hide the reference column, boxes take the full width */
@media (max-width: 720px) {
  .fbx-ref { display: none; }
  .fbx-shapes-frame { padding: 0.75rem 0.6rem 0.75rem; }
}
.fbx-shape-box {
  flex: 1 1 160px;
  max-width: 220px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  text-align: center;
  background: rgba(var(--w),0.02);
  border: 1px solid rgba(var(--w),0.06);
  border-radius: var(--radius-small);
  padding: 0.3rem 0.75rem;
}
.fbx-shape-box svg {
  width: 100%;
  height: auto;
}
.fbx-shape-letter {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
}

/* Map panel */
.fbx-map-panel {
  padding: 1rem;
  background: rgba(var(--w),0.03);
  border: 1px solid rgba(var(--w),0.06);
  border-radius: var(--radius-media);
}

/* Phase-1 placeholder text (replaced by real renders in later phases) */
.fbx-ph-title {
  font-size: 1.4rem;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: 0.05em;
}
.fbx-ph-sub {
  font-size: 0.85rem;
  color: var(--text-main);
}
.fbx-ph-note {
  font-size: 0.72rem;
  font-style: italic;
  color: var(--text-muted);
  opacity: 0.7;
}

.cof-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  padding-right: 2.2rem;
}

/* Toggle switch Maj/Min */
.cof-mode-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  user-select: none;
}

/* Progressions: the Maj/Min toggle only applies to the Basic tab â€” hidden
   (not the [hidden] attribute, so it can't lose a specificity fight with
   this class's own display:flex above) while a style tab is active. */
.prog-mode-toggle-hidden {
  display: none;
}
.cof-mode-toggle[hidden] { display: none; }
.cof-mode-toggle input { display: none; }
.cof-toggle-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.04em;
}
.cof-toggle-slider {
  position: relative;
  width: 34px;
  height: 18px;
  background: rgba(var(--w),0.1);
  border-radius: 9px;
  transition: background 0.2s;
}
.cof-toggle-slider::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--text-muted);
  transition: transform 0.2s, background 0.2s;
}
.cof-mode-toggle input:checked + .cof-toggle-slider {
  background: rgba(216,168,79,0.3);
}
.cof-mode-toggle input:checked + .cof-toggle-slider::after {
  transform: translateX(16px);
  background: var(--accent);
}

/* Column labels (Triad / 7th) inside chord chips */
.cof-col-label {
  font-size: 0.5rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  opacity: 0.38;
  font-weight: 500;
  line-height: 1;
  margin-top: 0.25rem;
}

/* Chord wrap: stacks vertically beside the CoF */
.cof-chords-wrap {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
  min-width: 0;
}
.cof-7th-toggle .tuner-mode-btn { flex: 1; }

/* Triads shown by default; 7ths hidden. Swap in show-7ths mode. */
.cof-chord-7th { display: none; }
.cof-chords.show-7ths .cof-chord-name { display: none; }
.cof-chords.show-7ths .cof-chord-7th  { display: block; }

.cof-chord-7th {
  font-size: 0.68rem;
  font-weight: 600;
  color: rgba(var(--w),0.45);
  line-height: 1;
  white-space: nowrap;
}
.cof-chord--maj .cof-chord-7th { color: rgba(216,168,79,0.6); }
.cof-chord--dim .cof-chord-7th { color: var(--text-muted); opacity: 0.4; }
.cof-chord--aug .cof-chord-7th { color: rgba(255,140,50,0.6); }

/* Blues note in progressions */
.prog-blues-note {
  font-size: 0.72rem;
  color: var(--text-muted);
  font-style: italic;
  margin: 0 0 0.75rem;
  padding: 0.4rem 0.6rem;
  border-left: 2px solid rgba(216,168,79,0.3);
}

/* Generic expand button â€” absolute top-right of card */
.tool-expand-btn {
  position: absolute;
  top: 0.85rem;
  right: 0.85rem;
  background: none;
  border: 1px solid rgba(var(--w),0.07);
  color: var(--text-muted);
  opacity: 0.5;
  border-radius: 6px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  cursor: pointer;
  transition: opacity 0.15s, border-color 0.15s, background 0.15s;
  flex-shrink: 0;
}

.tool-expand-btn:hover {
  opacity: 1;
  background: rgba(var(--w),0.05);
  border-color: rgba(var(--w),0.14);
}

/* Backdrop */
.tool-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.75);
  z-index: 200;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.tool-backdrop.active { display: block; }

/* Expanded overlay card */
.tool-card.tool-expanded {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(860px, 92vw);
  max-height: 92vh;
  overflow-y: auto;
  z-index: 201;
  box-shadow: 0 32px 80px rgba(0,0,0,0.6);
  background: var(--bg-card);
}

.tool-card.tool-expanded .cof-svg {
  max-width: 480px;
}

.tool-card.tool-expanded .tool-expand-btn {
  font-size: 0.85rem;
}

.cof-wrapper {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: center;
  gap: 1.2rem;
  width: 100%;
}

.cof-svg {
  width: 100%;
  max-width: 340px;
  flex-shrink: 0;
  height: auto;
}

@media (max-width: 480px) {
  .cof-wrapper {
    flex-direction: column;
    align-items: center;
  }
  .cof-svg {
    max-width: 260px;
  }
}

/* Scale row (Natural / Harmonic / Melodic) â€” collapses when not in minor mode */
.cof-scale-row {
  display: flex;
  justify-content: center;
  overflow: hidden;
  max-height: 0;
  margin-bottom: 0;
  transition: max-height 0.22s ease, margin-bottom 0.22s ease;
}
.cof-scale-row.visible {
  max-height: 2.8rem;
  margin-bottom: 0.8rem;
}

/* Triads/7ths toggle â€” hidden until a key is selected */
.cof-7th-toggle {
  overflow: hidden;
  max-height: 0;
  margin-bottom: 0;
  transition: max-height 0.22s ease, margin-bottom 0.22s ease;
}
.cof-7th-toggle.visible {
  max-height: 2.8rem;
  margin-bottom: 0.5rem;
}

.cof-seg {
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.cof-seg:hover {
  opacity: 0.8;
}

.cof-chords {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  align-content: flex-start;
  flex: 1;
}

.cof-chord {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  padding: 0.45rem 0.3rem 0.4rem;
  border-radius: 8px;
  flex: 1;
  min-width: 0;
  transition: box-shadow 0.15s;
}

.cof-chords.has-selection .cof-chord {
  box-shadow: 0 0 0 1.5px rgba(0,0,0,0.7), inset 0 0 0 1px rgba(var(--w),0.06);
}

.cof-chord--maj {
  background: rgba(216, 168, 79, 0.1);
  border: 1px solid rgba(216, 168, 79, 0.22);
}

.cof-chord--min {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.07);
}

.cof-chord--dim {
  background: rgba(255, 255, 255, 0.015);
  border: 1px solid rgba(255, 255, 255, 0.04);
}

.cof-chord--aug {
  background: rgba(255, 140, 50, 0.07);
  border: 1px solid rgba(255, 140, 50, 0.20);
}

.cof-roman {
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  opacity: 0.55;
  font-weight: 600;
  line-height: 1;
}

.cof-chord-name {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-main);
  line-height: 1;
  white-space: nowrap;
}

.cof-chord--maj .cof-chord-name { color: var(--accent); }
.cof-chord--dim .cof-chord-name { color: var(--text-muted); opacity: 0.55; }
.cof-chord--aug .cof-chord-name { color: #ff9450; }


/* Both triads + 7ths always visible (advanced CoF) */
.cof-chords--both .cof-chord-7th { display: block; }
.cof-chords--both .cof-chord { gap: 0.15rem; }

/* Info toggle button in card header */
.cof-info-btn {
  background: none;
  border: 1px solid rgba(var(--w),0.15);
  color: var(--text-muted);
  border-radius: 50%;
  width: 1.4rem;
  height: 1.4rem;
  font-size: 0.72rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: border-color .15s, color .15s;
}
.cof-info-btn:hover, .cof-info-btn.active { border-color: var(--accent); color: var(--accent); }
.cof-info-desc { display: none; }
.cof-info-desc.visible { display: block; }

/* ==========================================================
   SCALE VISUALIZER
   ========================================================== */

.scale-visualizer-card {
  grid-column: 1 / -1;
}

/* Compact intro text */
.sv-intro {
  font-size: 0.83rem;
  color: var(--text-muted);
  line-height: 1.6;
  margin: 0 0 1rem;
  padding: 0.7rem 1rem;
  background: rgba(255, 255, 255, 0.02);
  border-left: 2px solid rgba(216, 168, 79, 0.3);
  border-radius: 0 var(--radius-small) var(--radius-small) 0;
}

.sv-intro strong { color: var(--text-main); font-weight: 600; }
.sv-intro em     { color: var(--accent); font-style: normal; font-weight: 500; }

/* Controls: root section top, scale types below */
.sv-controls {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  margin-bottom: 1rem;
}

.sv-root-section {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 1rem;
}

.sv-root-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.deg-toggle {
  display: inline-flex;
  border: 1px solid rgba(var(--w),0.1);
  border-radius: var(--radius-small);
  overflow: hidden;
  flex-shrink: 0;
}
.deg-btn {
  padding: 0.28rem 0.6rem;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.deg-btn + .deg-btn {
  border-left: 1px solid rgba(var(--w),0.1);
}
.deg-btn.active {
  background: rgba(var(--w),0.08);
  color: var(--text-main);
}
.deg-btn:not(.active):hover {
  color: var(--text-main);
  background: rgba(var(--w),0.04);
}

.sv-section-label {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  opacity: 0.4;
  font-weight: 600;
}

.sv-roots {
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.sv-roots .scale-btn {
  padding: 0.25rem 0.5rem;
  font-size: 0.8rem;
}

/* Accidentals: stacked enharmonic names, smaller and visually recessed */
.sv-roots .scale-btn.sv-acc {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 0.15rem 0.38rem;
  color: rgba(181, 181, 181, 0.55);
  border-color: rgba(255, 255, 255, 0.05);
  line-height: 1;
}

.sv-roots .scale-btn.sv-acc:hover {
  color: rgba(234, 234, 234, 0.85);
  border-color: rgba(255, 255, 255, 0.15);
}

.sv-roots .scale-btn.sv-acc.active {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.4);
  background: rgba(216, 168, 79, 0.15);
}

.sv-acc-pri {
  font-size: 0.72rem;
  font-weight: 500;
}

.sv-acc-sec {
  font-size: 0.55rem;
  opacity: 0.5;
}

.sv-roots .scale-btn.sv-acc.active .sv-acc-sec {
  opacity: 0.65;
}

/* Open chords: sharps/flats rarely have an open-position shape. Dim, don't
   hide â€” the row must not change width when switching category, and a
   couple of accidentals (Bb, B) do have a real voicing worth reaching. */
.sv-roots--naturals-only .scale-btn.sv-acc {
  opacity: 0.35;
}
.sv-roots--naturals-only .scale-btn.sv-acc:hover,
.sv-roots--naturals-only .scale-btn.sv-acc.active {
  opacity: 1;
}

.sv-types {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.sv-type-group {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

/* Group label */
.sv-type-header {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 0.1rem;
}

.sv-type-title {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  opacity: 0.35;
  font-weight: 600;
}

.sv-level-header {
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding-bottom: 0.25rem;
  margin-top: 0.2rem;
  border-bottom: 1px solid;
}
.sv-level-header.sv-lv1 { color: #48c774; border-color: rgba(72,199,116,0.2); }
.sv-level-header.sv-lv2 { color: var(--accent); border-color: rgba(216,168,79,0.2); }
.sv-level-header.sv-lv3 { color: #e0823a; border-color: rgba(224,130,58,0.2); }
.sv-level-header.sv-lv4 { color: #c84d4d; border-color: rgba(200,77,77,0.2); }

.sv-type-note {
  font-size: 0.6rem;
  color: var(--text-muted);
  opacity: 0.3;
  font-style: italic;
  flex-basis: 100%;
}

.sv-type-row {
  display: flex;
  gap: 0.28rem;
  flex-wrap: wrap;
}

/* Scale buttons â€” clean, no level colors */
/* LEVEL 3 (filters) — sits below LEVEL 2 (.sv-cat-btn), subordinate via
   its smaller pill/padding rather than a separate font-size step (see
   the shared .tab-filter group, ~line 3162, for padding/font-size —
   not repeated here). .filter-chip, .mode-chip, .iv-chip and
   .metro-opt-btn (below) share the same values via that group. */
.scale-btn {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: var(--text-muted);
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
  white-space: nowrap;
}

.scale-btn:hover {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-main);
}

.scale-btn.active {
  background: rgba(216, 168, 79, 0.15);
  border-color: rgba(216, 168, 79, 0.4);
  color: var(--accent);
}

/* Generic disabled state for this shared pill (no user today — the
   Intermediate Pentatonic Dominant type it was added for is now enabled
   with its approved formula). Kept so any future non-selectable .scale-btn
   is dimmed and loses its hover lift instead of reading as clickable. */
.scale-btn:disabled {
  opacity: 0.38;
  cursor: not-allowed;
}
.scale-btn:disabled:hover {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
}

/* Info bar */
.sv-info {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 1rem;
}

.sv-info-toggle {
  align-self: flex-end;
  margin-top: 0.1rem;
}

.sv-info-top {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.sv-name {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--text-main);
  line-height: 1;
}

.sv-formula {
  font-size: 0.8rem;
  color: var(--text-muted);
  opacity: 0.8;
  letter-spacing: 0.03em;
}
/* THE MODAL IDENTITY LINE (#fbxModal), third and last line of the scale
   caption. Subordinate to the note names and the degrees above it — this
   states where the scale sits in its parent system, which is context for
   what those two lines already say, not a third reading of the scale. */
.sv-formula--modal {
  font-size: 0.74rem;
  opacity: 0.62;
}

.sv-desc {
  font-size: 0.8rem;
  color: var(--text-muted);
  opacity: 0.6;
  font-style: italic;
}

/* Blues note toggle */
.sv-blues-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: rgba(91, 155, 213, 0.07);
  border: 1px solid rgba(91, 155, 213, 0.2);
  color: rgba(91, 155, 213, 0.7);
  padding: 0.28rem 0.65rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  font-size: 0.78rem;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.sv-blues-btn:hover  { background: rgba(91,155,213,0.14); color: #5b9bd5; border-color: rgba(91,155,213,0.4); }
.sv-blues-btn.active { background: rgba(91,155,213,0.18); border-color: #5b9bd5; color: #5b9bd5; }

.sv-blues-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  flex-shrink: 0;
  transition: background 0.15s;
}

.sv-blues-btn.active .sv-blues-dot { background: #5b9bd5; }

/* Level badge (info bar only) */
.sv-level-badge {
  display: inline-block;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-pill);
  line-height: 1;
  white-space: nowrap;
  flex-shrink: 0;
}

.sv-lv1 { background: rgba(72,199,116,0.12); color: #48c774; border: 1px solid rgba(72,199,116,0.25); }
.sv-lv2 { background: rgba(216,168,79,0.12);  color: var(--accent); border: 1px solid rgba(216,168,79,0.25); }
.sv-lv3 { background: rgba(224,130,58,0.12);  color: #e0823a; border: 1px solid rgba(224,130,58,0.25); }
.sv-lv4 { background: rgba(200,77,77,0.12);   color: #c84d4d; border: 1px solid rgba(200,77,77,0.25); }

/* Hidden groups */
.sv-group-hidden { display: none; }
.sv-types.sv-expanded .sv-group-hidden {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

/* Expand button */
.sv-expand-btn {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.07);
  color: var(--text-muted);
  padding: 0.35rem 0.8rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  font-size: 0.78rem;
  cursor: pointer;
  margin-top: 0.2rem;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  align-self: flex-start;
}

.sv-expand-btn:hover {
  background: rgba(255, 255, 255, 0.05);
  color: var(--text-main);
  border-color: rgba(255, 255, 255, 0.12);
}

.sv-expand-icon {
  font-size: 0.75rem;
  opacity: 0.7;
  transition: transform 0.2s ease;
}

/* Scale Visualizer â€” category chips */
.sv-cats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0;
  margin-bottom: 1rem;
}

/* LEVEL 2 (category/subtabs) canonical size — lighter than Level 1
   .fb-tab: smaller font, less padding, pill shape kept but visually
   quieter. .mode-family-btn, .fbx-step-btn and .tuner-mode-btn (below)
   match these same values so every secondary-tab row in the portal
   reads at one consistent weight. padding/font-size/font-weight: see
   the shared .tab-secondary group (~line 3153) — not repeated here so
   there's one source of truth instead of two numbers to keep in sync. */
.sv-cat-btn {
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.1);
  color: var(--text-muted);
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.sv-cat-btn:hover {
  background: rgba(var(--w),0.07);
  color: var(--text-main);
}
/* LEVEL 2 (category/subtabs) canonical active state â€” dark gold tint, not
   a filled pill, so it stays visually subordinate to Level 1 main tabs. */
.sv-cat-btn.active {
  background: rgba(216,168,79,0.08);
  border-color: rgba(216,168,79,0.28);
  color: var(--accent);
}

/* Pill variant â€” aaneengesloten omkadering zoals tabgroepen */
.sv-cats--pill {
  display: inline-flex;
  flex-wrap: nowrap;
  gap: 0;
  border: 1px solid rgba(var(--w),0.08);
  border-radius: var(--radius-small);
  overflow: hidden;
}
.sv-cats--pill .sv-cat-btn {
  border: none;
  border-right: 1px solid rgba(var(--w),0.08);
  border-radius: 0;
  flex-shrink: 0;
}
.sv-cats--pill .sv-cat-btn:last-child { border-right: none; }

/* Wrappable pill variant for many buttons */
.sv-cats--pill-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}
.sv-cats--pill-wrap .sv-cat-btn {
  border-radius: var(--radius-pill);
}
.sv-cat-note {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin: 0 0 0.5rem;
  font-style: italic;
}

.sv-scales-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1rem;
}

.sv-type-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.sv-parent-btn {
  font-size: 0.75rem !important;
  padding: 0.2rem 0.6rem !important;
}

.sv-parent-key {
  font-size: 0.8rem;
  color: var(--accent);
  margin: 0.1rem 0 0.2rem;
  font-style: italic;
}

.sv-board-wrap {
  width: 100%;
}

.sv-board-wrap svg {
  width: 100%;
  height: auto;
  display: block;
}

/* ANTI-ZOOM CAP for cropped position windows (Arpeggio, Pentatonic,
   Diatonic and Intervals), desktop/tablet. Without this, a compact-window SVG (a
   smaller viewBox — see neckViewport() in fretboard.js) still inherits
   .sv-board-wrap svg's width:100% above and stretches to fill the SAME
   full column "All" uses, enlarging fret spacing/dot size/font size well
   past the normal 15-fret scale — a cropped position ends up looking
   zoomed in 2-3x rather than cropped.

   .fb-pos-compact (set by applyNeckViewport(), only for a real cropped
   window — never for "All") caps the wrapper at the same fraction of the
   column that this window's own true width is of the full neck's, both
   already measured in identical internal units by the tools' neckW().
   The child svg's existing width:100% then fills that capped box at the
   SAME effective scale as "All", not a larger one — so 5 frets look like
   5 frets cut out of the 15-fret neck.

   Desktop/tablet only: on a phone a proportionally tiny position would be
   unreadable, so below 701px a compact window uses the full width
   instead — the one place a position is deliberately larger than "All". */
@media (min-width: 701px) {
  .sv-board-wrap.fb-pos-compact {
    max-width: var(--fb-pos-max-width, 100%);
  }
}

@media (max-width: 700px) {
  .sv-controls {
    flex-direction: column;
    gap: 1rem;
  }

  /* Cropped position windows (5-7 frets) stay horizontal at every width,
     matching desktop's reading direction while keeping controls close
     below — they already fit a phone, and they fill its width rather than
     keeping the desktop anti-zoom cap above. Deliberately scoped with
     :not(.fb-vertical): the SAME elements become full 15-fret necks on
     "All", and must then be free to rotate (see the ORIENTATION SYSTEM
     block below) instead of being pinned horizontal by these resets. */
  #fb-panel-scales .sv-board-wrap:not(.fb-vertical),
  #fb-panel-arpeggios .sv-board-wrap:not(.fb-vertical),
  #fb-panel-caged .sv-board-wrap:not(.fb-vertical) {
    container-type: normal;
    max-width: none;
    margin: 0;
    aspect-ratio: auto;
    overflow: visible;
    display: block;
    min-width: 0;
  }

  #fb-panel-scales .sv-board-wrap:not(.fb-vertical) svg,
  #fb-panel-arpeggios .sv-board-wrap:not(.fb-vertical) svg,
  #fb-panel-caged .sv-board-wrap:not(.fb-vertical) svg {
    width: 100%;
    max-width: 100%;
    height: auto;
    transform: none;
  }

  #fb-panel-scales .sv-board-wrap:not(.fb-vertical) svg text,
  #fb-panel-arpeggios .sv-board-wrap:not(.fb-vertical) svg text,
  #fb-panel-caged .sv-board-wrap:not(.fb-vertical) svg text {
    transform: none;
  }
}

/* ==========================================================
   FRETBOARD ORIENTATION SYSTEM (Fretboard section ONLY)
   ----------------------------------------------------------
   Narrow PORTRAIT viewports get a VERTICAL neck (nut at top, frets
   downward, page scrolls for longer necks); landscape phones, tablets
   and desktop keep the existing horizontal neck. The media query below
   is explicitly `and (orientation: portrait)`: a landscape phone is
   narrow by width (667px on a 375-tall device) but a 15-fret neck fits
   it comfortably, so it must NOT rotate.

   Applies to every FULL-NECK Fretboard diagram: Notes, Intervals, and
   the "All" view of Diatonic/Pentatonic (#fbxMap), Arpeggio
   (#arpExerciseMap) and Intermediate CAGED (#cagedIntMap, always a full
   neck). Those middle two share their element with a cropped
   position window, which stays horizontal — neck-orientation.js decides
   that from data-neck-mode and only adds .fb-vertical to a full neck, so
   the selectors here can simply key off that class. CAGED's one-position
   islands never rotate. A future full-neck diagram should add its own
   wrap id to the selectors below to inherit the same treatment.

   neck-orientation.js (loaded only on fretboard.html) toggles the
   .fb-vertical class and supplies --fb-vert-scale (how much to scale
   the rotated neck to fill the available width) — this file only owns
   the rotation math itself.

   The rotation trick: rotate 90° around the top-left corner, anchored
   back to (0,0) with translateY(-100%) — this is the standard "turn a
   landscape box into a portrait one, nut corner pinned at the origin"
   transform. Text gets counter-rotated -90° (transform-box:fill-box,
   origin:center — same "flip a child around its own box" trick the
   left-handed mirror below already uses) so labels stay upright. Left-
   handed swaps translateY(-100%) for scaleY(-1): same rotation, but
   mirrored so low-E/high-e swap sides — see [data-handed="left"] below.
   ========================================================== */
@media (max-width: 700px) and (orientation: portrait) {
  .fretboard-page .sv-board-wrap.fb-vertical,
  .theory-neck-context.fb-vertical {
    overflow: hidden;
  }

  .fretboard-page .sv-board-wrap.fb-vertical svg,
  :is(#theory-panel-starter-fundamentals, #theory-panel-fundamentals, #theory-panel-melody, #theory-panel-harmony) .theory-neck-context.fb-vertical svg.alpha-svg {
    width: auto;
    height: auto;
    max-width: none;
    transform-origin: top left;
    transform: scale(var(--fb-vert-scale, 1)) rotate(90deg) translateY(-100%);
  }

  .fretboard-page .sv-board-wrap.fb-vertical svg text,
  :is(#theory-panel-starter-fundamentals, #theory-panel-fundamentals, #theory-panel-melody, #theory-panel-harmony) .theory-neck-context.fb-vertical svg.alpha-svg text {
    transform-box: fill-box;
    transform-origin: center;
    transform: rotate(-90deg);
  }

  /* Left-handed vertical: same rotation, mirrored so high-e ends up on
     the left and low-E on the right (the vertical equivalent of the
     horizontal scaleX(-1) mirror below). */
  [data-handed="left"] .fretboard-page .sv-board-wrap.fb-vertical svg,
  [data-handed="left"] :is(#theory-panel-starter-fundamentals, #theory-panel-fundamentals, #theory-panel-melody, #theory-panel-harmony) .theory-neck-context.fb-vertical svg.alpha-svg {
    transform: scale(var(--fb-vert-scale, 1)) rotate(90deg) scaleY(-1);
  }
  [data-handed="left"] .fretboard-page .sv-board-wrap.fb-vertical svg text,
  [data-handed="left"] :is(#theory-panel-starter-fundamentals, #theory-panel-fundamentals, #theory-panel-melody, #theory-panel-harmony) .theory-neck-context.fb-vertical svg.alpha-svg text {
    transform: rotate(90deg) scaleY(-1);
  }
}

/* Rotation hint — shown once (see neck-orientation.js) just below
   a vertical diagram. Plain inline text, never a modal/overlay, so it
   can never sit on top of or block the diagram itself. */
.fb-rotate-hint {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.6rem 0 0;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.fb-rotate-hint svg {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  opacity: 0.8;
}


/* ==========================================================
   CAGED SECTION
   ========================================================== */

.caged-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.8rem;
}

.caged-header-right {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.caged-shape-btns {
  display: flex;
  gap: 0.4rem;
}

.caged-btn {
  width: 38px;
  height: 38px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.caged-btn:hover {
  background: rgba(216, 168, 79, 0.1);
  border-color: rgba(216, 168, 79, 0.35);
  color: var(--text-main);
}

.caged-btn.active {
  background: rgba(216, 168, 79, 0.18);
  border-color: var(--accent);
  color: var(--accent);
}

/* Quality toggle buttons */
.caged-qual-btns {
  display: flex;
  gap: 0.3rem;
}

.caged-qual-btn {
  padding: 0.3rem 0.75rem;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.caged-qual-btn:hover {
  background: rgba(255, 255, 255, 0.07);
  color: var(--text-main);
}

.caged-qual-btn.active {
  background: rgba(216, 168, 79, 0.15);
  border-color: rgba(216, 168, 79, 0.4);
  color: var(--accent);
}

/* 5 shapes in a row, equal width */
.caged-shapes {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  width: 100%;
}

.caged-shape {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
}

.caged-shape svg {
  width: 100%;
  height: auto;
}

.caged-shape-letter {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
}

.caged-shape-pos {
  font-size: 0.72rem;
  color: var(--text-muted);
  opacity: 0.65;
}

@media (max-width: 700px) {
  .caged-shapes {
    flex-wrap: wrap;
  }
  .caged-shape {
    flex: 0 0 calc(33% - 0.5rem);
  }
}


/* ==========================================================
   PRACTICE TRACKER
   ========================================================== */

.tracker-top {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2rem;
  align-items: start;
  margin-bottom: 2rem;
}

.tracker-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.preset-btn {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--text-muted);
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-main);
  font-size: 0.85rem;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.preset-btn:hover {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-main);
}

.preset-btn.active {
  background: rgba(216, 168, 79, 0.15);
  border-color: rgba(216, 168, 79, 0.4);
  color: var(--accent);
}

.tracker-custom-input {
  padding: 0.6rem 1rem;
  background: #111;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-small);
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.875rem;
  width: 100%;
  max-width: 360px;
  transition: border-color 0.2s ease;
}

.tracker-custom-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.1);
}

.tracker-timer-block {
  text-align: right;
}

.tracker-time {
  font-size: 3rem;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  margin-bottom: 1rem;
}

.tracker-timer-controls {
  display: flex;
  gap: 0.6rem;
  justify-content: flex-end;
}

.session-timer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.session-timer-row .tracker-time {
  margin-bottom: 0;
}

.tracker-log {
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  padding-top: 1.5rem;
}

.tracker-empty {
  color: var(--text-muted);
  font-size: 0.875rem;
  text-align: center;
  padding: 1rem 0;
  margin: 0;
}

.tracker-entry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.85rem 1.2rem;
  border-radius: 10px;
  margin-bottom: 0.5rem;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.04);
}

.tracker-entry-info {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.tracker-entry-exercise {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text-main);
}

.tracker-entry-date {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.tracker-entry-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.tracker-entry-duration {
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.tracker-entry-delete {
  background: none;
  border: none;
  color: var(--text-muted);
  opacity: 0.35;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.15rem 0.35rem;
  border-radius: 4px;
  transition: opacity 0.15s, color 0.15s, background 0.15s;
}

.tracker-entry-delete:hover {
  opacity: 1;
  color: #e55;
  background: rgba(220, 50, 50, 0.1);
}


/* ==========================================================
   TUNER
   ========================================================== */

/* ---- Top row: instrument + tuning + mode toggle ---- */
.tuner-top-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.75rem;
}

@media (max-width: 480px) {
  /* Instrument select and the mode toggle are both flex:0 0 auto — on a
     narrow phone, three non-shrinking/non-wrapping items in one row
     (select + select + toggle) can add up to more than the card's width
     and overflow it. Let the row wrap instead. */
  .tuner-top-row {
    flex-wrap: wrap;
  }
  .tuner-instrument-select,
  .tuner-tuning-select {
    flex: 1 1 100%;
  }
}

.tuner-instrument-select {
  flex: 0 0 auto;
}

.tuner-tuning-select {
  flex: 1;
  min-width: 0;
}

.tuner-mode-btns {
  display: inline-flex;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-small);
  overflow: hidden;
  flex-shrink: 0;
}

/* padding/font-size/font-weight: see the shared .tab-secondary group
   (~line 3153). */
.tuner-mode-btn {
  font-family: var(--font-main);
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  letter-spacing: 0.02em;
}

.tuner-mode-btn:not(:last-child) {
  border-right: 1px solid rgba(255, 255, 255, 0.08);
}

.tuner-mode-btn.active {
  background: rgba(216, 168, 79, 0.10);
  color: var(--accent);
}

.tuner-mode-btn:not(.active):hover {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-main);
}

/* ---- Tuning controls ----
   These two live in the card's one control row now, beside the Mic.
   Two earlier homes did not work: alone on a right-aligned row above
   the strings they floated with nothing to belong to, and giving that
   row a "Reference tones" label anchored them at the cost of a whole
   line in a card that was already too tall. */
.tuner-tuning-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.tuner-edit-btn,
.tuner-reset-btn {
  background: none;
  border: 1px solid rgba(var(--w), 0.1);
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: 0.74rem;
  /* 40px at rest, 44px wherever the pointer is a finger — these two are
     the smallest hit targets left in the card. */
  min-height: 40px;
  padding: 0.3rem 0.75rem;
  white-space: nowrap;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.tuner-edit-btn:hover,
.tuner-reset-btn:hover {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.4);
  background: rgba(216, 168, 79, 0.06);
}

.tuner-edit-btn:focus-visible,
.tuner-reset-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.tuner-btn-icon {
  width: 0.95rem;
  height: 0.95rem;
  flex: none;
}

/* Pencil while the tuning is being played, check while it is being
   edited — the label swaps through i18n, the icon swaps here, so
   neither can overwrite the other. */
.tuner-edit-btn .tuner-btn-icon--done,
.tuner-edit-btn.is-editing .tuner-btn-icon--edit { display: none; }
.tuner-edit-btn.is-editing .tuner-btn-icon--done { display: block; }

.tuner-edit-btn.is-editing {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.4);
  background: rgba(216, 168, 79, 0.08);
}

.string-note-edit {
  width: 100%;
  min-width: 0;
  min-height: 34px;
  background: #171717;
  border: 1px solid rgba(var(--w), 0.12);
  color: var(--text-main);
  padding: 0.2rem 0.15rem;
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  font-size: 0.86rem;
  font-weight: 600;
  text-align: center;
  text-align-last: center;
  cursor: pointer;
}

.string-note-edit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.tuner-strings.is-editing .string-btn {
  cursor: default;
  gap: 0.35rem;
}

/* The visible circle is 32px; the tappable box is the whole button, and
   grows to 44px on a touch pointer. */
.string-off-btn {
  background: none;
  border: 1px solid rgba(var(--w), 0.14);
  color: var(--text-muted);
  line-height: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.string-off-btn svg {
  width: 13px;
  height: 13px;
}

.string-off-btn:hover {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.4);
}

.string-off-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (pointer: coarse) {
  .tuner-edit-btn,
  .tuner-reset-btn { min-height: 44px; }
  .string-off-btn { width: 44px; height: 44px; }
}

.string-btn--off {
  opacity: 0.45;
}

.string-btn--off .string-note-edit {
  text-decoration: line-through;
}

/* ---- Reference Hz row ---- */
.tuner-ref-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1.2rem;
}

.tuner-ref-label {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text-muted);
  opacity: 0.6;
  flex-shrink: 0;
  letter-spacing: 0.02em;
}

.tuner-ref-slider {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 3px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 99px;
  outline: none;
  cursor: pointer;
}

.tuner-ref-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  box-shadow: 0 0 6px rgba(216, 168, 79, 0.4);
}

.tuner-ref-slider::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
}

.tuner-ref-val {
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  opacity: 0.7;
  flex-shrink: 0;
  min-width: 5.5ch;
  letter-spacing: 0.02em;
}

.tuner-ref-reset {
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: var(--radius-small);
  color: var(--text-muted);
  opacity: 0.5;
  font-size: 0.8rem;
  padding: 0.15rem 0.4rem;
  cursor: pointer;
  font-family: var(--font-main);
  transition: opacity 0.15s, border-color 0.15s;
  flex-shrink: 0;
}

.tuner-ref-reset:hover {
  opacity: 1;
  border-color: rgba(255, 255, 255, 0.18);
}

/* ---- Strobe canvas ---- */
.tuner-strobe {
  display: block;
  width: 110px;
  height: 110px;
  margin: 0 auto 0.6rem;
  border-radius: 50%;
}

/* ---- THE TUNER'S STATE TOKENS ----
   Everything in this widget that carries a colour reads --tuner-live /
   --tuner-live-glow, and only the [data-tuner-state] blocks reassign
   them. That is what makes "gold is the accent, and green means in tune
   and nothing else" a rule the stylesheet enforces rather than a
   convention every new rule has to remember.

   --tuner-dev is the deviation as a signed fraction of the meter's half
   width (cents / 50, clamped to -1..1). practice.js writes it once per
   frame; the fill bar is scaleX(var(--tuner-dev)) off the centre line,
   so "which way and how far" is a shape, not just a moving dot. */
.tool-card--tuner {
  --tuner-live: var(--accent);
  --tuner-live-glow: rgba(216, 168, 79, 0.42);
  --tuner-green: #48c774;
  --tuner-warn: #e08a72;
  --tuner-dev: 0;
  overflow: visible;
  padding: 0;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.1);
  border-radius: 14px;
}

.tool-card--tuner[data-tuner-state="tuned"] {
  --tuner-live: var(--tuner-green);
  --tuner-live-glow: rgba(72, 199, 116, 0.45);
}

.tuner-hint {
  margin: 0.45rem 0 0;
  color: var(--text-muted);
  font-size: var(--type-small);
  line-height: 1.55;
}

#tool-tuner .tuner-card-header {
  padding: 1.1rem 1.45rem 0.9rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
}

.tuner-stage {
  padding: 1.15rem 1.45rem 0.95rem;
}

/* ---- The display: a group, not a box ----
   The note, the meter and the status line are the tuner; the strings
   and the mic are its controls. This wrapper keeps that grouping for
   the status line's sake and for spacing — and nothing else. It DID
   carry an inset panel with its own border and fill, which put a second
   frame inside the card's frame and read as clutter rather than as an
   instrument face. The separation is now done with spacing alone. */
.tuner-display {
  display: grid;
  grid-template-columns: minmax(116px, 0.34fr) minmax(0, 1fr);
  align-items: center;
  gap: 1.4rem;
  margin-top: 0;
}

.tuner-note-display {
  text-align: center;
}

.tuner-note-row {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 6.25rem;
  height: 6.25rem;
  border: 1px solid rgba(var(--w), 0.10);
  border-radius: 50%;
  background: rgba(var(--w), 0.02);
  line-height: 1;
  transition: border-color 0.3s ease, background 0.3s ease, box-shadow 0.3s ease;
}

/* A fine graduation on the inner rim — 24 hairlines, masked to a thin
   annulus so they never reach the middle. It is the one piece of
   decoration in the widget and it earns its place: it is what makes the
   circle read as an instrument face rather than as a plain badge. */
.tuner-note-row::before {
  content: '';
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: repeating-conic-gradient(
    from 0deg,
    rgba(var(--w), 0.5) 0deg 0.7deg,
    transparent 0.7deg 15deg);
  -webkit-mask: radial-gradient(closest-side, transparent 84%, #000 85%);
  mask: radial-gradient(closest-side, transparent 84%, #000 85%);
  opacity: 0.3;
  pointer-events: none;
}

/* Listening, nothing played yet: the ring breathes so the tuner reads
   as awake rather than broken. */
.tool-card--tuner[data-tuner-state="listening"] .tuner-note-row::after {
  content: '';
  position: absolute;
  inset: -1px;
  border: 1px solid rgba(216, 168, 79, 0.4);
  border-radius: 50%;
  animation: tuner-listen 2.4s ease-in-out infinite;
}

@keyframes tuner-listen {
  0%, 100% { opacity: 0.12; transform: scale(1); }
  50%      { opacity: 0.5;  transform: scale(1.03); }
}

.tool-card--tuner[data-tuner-state="tuned"] .tuner-note-row {
  border-color: rgba(72, 199, 116, 0.45);
  background: rgba(72, 199, 116, 0.05);
  box-shadow: 0 0 0 1px rgba(72, 199, 116, 0.16);
}

/* Denser rather than bigger: at 8.75rem the ring was mostly empty
   space, which made the idle state read as MORE hollow, not less. The
   note now fills 55% of a 6.25rem ring, so the display has presence at
   two thirds of the height. */
.tuner-note {
  display: block;
  font-size: 3.4rem;
  font-weight: 800;
  color: var(--text-main);
  line-height: 1;
  letter-spacing: -0.04em;
  transition: color 0.3s ease, text-shadow 0.3s ease;
}

.tuner-note.in-tune {
  color: var(--tuner-green);
  text-shadow: 0 0 34px rgba(72, 199, 116, 0.35);
}

.tuner-note.is-idle {
  color: var(--text-muted);
  opacity: 1;
  letter-spacing: 0;
}

/* THE IDLE PLACEHOLDER IS A DASH, NOT A PICTURE.
   It has been a plectrum watermark and then a quarter note, and both
   read as an ornament parked in the readout. A tuner at rest shows a
   neutral reading, so this shows one: an em dash, in the SAME element,
   at the SAME font-size and weight as the note letter that replaces it.
   Identical metrics is the whole point — it is why the letter can take
   over without anything moving. Dimmed rather than resized, so it reads
   as "no reading yet" instead of as a character.

   The "" after the slash is the CSS Speech alternative text: a screen
   reader gets nothing here, because #tunerStatus below already says
   "Play a string" in words. */
.tuner-note.is-idle::before {
  content: "—" / "";
  opacity: 0.35;
}

/* Pinned to the ring, NOT in flow beside the note. In flow it was the
   note and the octave that were centred as a pair, so the letter jumped
   sideways the moment an octave number appeared next to it — and the
   idle dash and the letter that replaces it have to sit on exactly the
   same spot. Out of flow, the note is always dead centre and the octave
   never moves it. At this ring size there is no room for it to collide
   with even a two-glyph note like F♯. */
.tuner-octave {
  position: absolute;
  top: 1.15rem;
  right: 0.75rem;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-muted);
  opacity: 0.7;
  margin: 0;
  line-height: 1;
}

.tuner-readout {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.7rem;
  margin: 0.35rem 0 0;
  /* Tall enough for the readout's real line box (27px), not for its
     font size — 1.5rem left a 3px gap that the strings row jumped
     across the moment a pitch was detected. */
  min-height: 1.75rem;
}

.tuner-hz {
  font-size: 0.76rem;
  color: var(--text-muted);
  opacity: 0.5;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.05em;
}

/* The actionable number, so it is the one that carries the state
   colour and the weight. The rule only draws while there is something
   to separate. */
.tuner-cents {
  font-size: 1rem;
  font-weight: 600;
  color: var(--tuner-live);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  transition: color 0.25s ease;
}

.tuner-cents:not(:empty) {
  padding-left: 0.7rem;
  border-left: 1px solid rgba(var(--w), 0.1);
}

/* ---- Meter ----
   auto | 1fr | auto, so the scale labels sit in the same column as the
   track and keep lining up with its ends however wide the chevrons are. */
.tuner-meter {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 0.55rem;
  row-gap: 0.4rem;
  margin-top: 0;
}

.tuner-dir {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  color: var(--text-muted);
  opacity: 0.16;
  transition: opacity 0.2s ease, color 0.2s ease;
}

.tuner-dir svg { width: 100%; height: 100%; }

/* Each chevron points at the centre, so it reads as "come this way"
   rather than "you are over there" — the same convention a pedal
   tuner's arrows use. */
.tool-card--tuner[data-tuner-state="flat"]  .tuner-dir--flat,
.tool-card--tuner[data-tuner-state="sharp"] .tuner-dir--sharp {
  color: var(--tuner-live);
  opacity: 1;
}

.tool-card--tuner[data-tuner-state="flat"] .tuner-dir--flat {
  animation: tuner-nudge-right 1.5s ease-in-out infinite;
}

.tool-card--tuner[data-tuner-state="sharp"] .tuner-dir--sharp {
  animation: tuner-nudge-left 1.5s ease-in-out infinite;
}

/* Nearly there: the chevron stays lit but stops pushing. */
.tool-card--tuner[data-tuner-near] .tuner-dir {
  animation: none;
}

@keyframes tuner-nudge-right {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(3px); }
}

@keyframes tuner-nudge-left {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(-3px); }
}

/* 24px, not 54px. It still has to carry a ruler, a centre mark, a
   target band and a needle — but a slab that tall read as a piece of
   hardware bolted onto the page rather than as part of the card. */
.tuner-track {
  grid-column: 2;
  position: relative;
  height: 24px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(var(--w), 0.06);
  overflow: hidden;
}

/* The centre: the one line on the scale that has to be unmistakable. */
.tuner-track::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: rgba(var(--w), 0.32);
  z-index: 4;
}

/* A tick every 10¢, off both edges, so the meter reads as a ruler and
   a deviation can be counted instead of guessed. */
.tuner-scale {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, rgba(var(--w), 0.13) 0 1px, transparent 1px),
    linear-gradient(to right, rgba(var(--w), 0.13) 0 1px, transparent 1px);
  background-size: 10% 34%, 10% 34%;
  background-position: 0 0, 0 100%;
  background-repeat: repeat-x, repeat-x;
  z-index: 1;
}

/* ±5¢ — the band the needle has to land in. */
.tuner-zone {
  position: absolute;
  left: 45%;
  width: 10%;
  top: 0;
  bottom: 0;
  background: rgba(72, 199, 116, 0.07);
  border-left: 1px solid rgba(72, 199, 116, 0.16);
  border-right: 1px solid rgba(72, 199, 116, 0.16);
  transition: background 0.25s ease, border-color 0.25s ease;
  z-index: 2;
}

/* Within 15¢: still flat or still sharp, but the target band lights up
   in the accent to say the next turn of the peg is a small one. Gold,
   not green — green is reserved for actually being there. */
.tool-card--tuner[data-tuner-near] .tuner-zone {
  background: rgba(216, 168, 79, 0.09);
  border-color: rgba(216, 168, 79, 0.5);
}

.tool-card--tuner[data-tuner-state="tuned"] .tuner-zone {
  background: rgba(72, 199, 116, 0.2);
  border-color: rgba(72, 199, 116, 0.45);
}

.tuner-fill {
  position: absolute;
  left: 50%;
  width: 50%;
  top: 26%;
  bottom: 26%;
  background: var(--tuner-live);
  opacity: 0.17;
  transform-origin: 0 50%;
  transform: scaleX(var(--tuner-dev));
  transition: transform 0.09s ease, background 0.25s ease;
  pointer-events: none;
  z-index: 3;
}

.tuner-needle {
  position: absolute;
  left: 50%;
  top: 3px;
  bottom: 3px;
  width: 3px;
  margin-left: -1.5px;
  background: var(--tuner-live);
  border-radius: 99px;
  box-shadow: 0 0 12px var(--tuner-live-glow);
  transition: left 0.08s ease, background 0.25s ease,
              box-shadow 0.25s ease, opacity 0.25s ease;
  z-index: 5;
}

.tool-card--tuner[data-tuner-state="idle"] .tuner-needle,
.tool-card--tuner[data-tuner-state="denied"] .tuner-needle {
  opacity: 0.26;
  box-shadow: none;
}

.tool-card--tuner[data-tuner-state="listening"] .tuner-needle {
  opacity: 0.45;
}

.tuner-track-labels {
  grid-column: 2;
  display: flex;
  justify-content: space-between;
  font-size: 0.66rem;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}

.tuner-track-labels span { opacity: 0.38; }
.tuner-track-labels span:nth-child(2) { opacity: 0.6; font-weight: 600; }

/* ---- Status ----
   Every message the tuner has — listening, too low, in tune, too high,
   permission refused — lands here and nowhere else, in a slot that is
   two lines tall whether or not there is anything to say, so no state
   change moves the strings or the mic button. */
.tuner-status {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  margin: 0.6rem 0 0;
  /* One line, reserved whether or not there is anything to say, so no
     state change moves the strings row. Reserving TWO cost 19px of
     blank in every ordinary state to protect against one message — the
     permission error — which is the only one that wraps, and which is
     allowed to push. */
  min-height: 1.5em;
  font-size: var(--type-small);
  line-height: 1.45;
  color: var(--text-muted);
  opacity: 0.62;
  transition: color 0.25s ease, opacity 0.25s ease;
}

.tool-card--tuner[data-tuner-state="flat"]  .tuner-status,
.tool-card--tuner[data-tuner-state="sharp"] .tuner-status {
  color: var(--text-main);
  opacity: 0.85;
}

.tool-card--tuner[data-tuner-state="tuned"] .tuner-status {
  color: var(--tuner-green);
  opacity: 1;
  font-weight: 600;
}

.tool-card--tuner[data-tuner-state="denied"] .tuner-status {
  color: var(--tuner-warn);
  opacity: 1;
}

/* ---- String buttons ----
   grid-auto-flow: column keeps six equal cells without relying on
   flex:1 + min-width:0, and takes the 4-, 5- and 7-string tunings the
   INSTRUMENTS table can produce without a second rule. */
.tuner-strings {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 0.45rem;
  margin-top: 0.2rem;
}

.tuner-strings.hidden {
  display: none;
}

.string-btn {
  min-width: 0;
  min-height: 62px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.5rem 0.15rem;
  background: rgba(var(--w), 0.03);
  border: 1px solid rgba(var(--w), 0.07);
  border-radius: 7px;
  color: inherit;
  cursor: pointer;
  font-family: var(--font-main);
  transition: background 0.14s ease, border-color 0.14s ease,
              box-shadow 0.18s ease, transform 0.1s ease;
}

/* Only the play-mode cell is a real button; the edit-mode cell is a
   container for a <select> and cannot take these. */
.string-btn--play:hover {
  background: rgba(216, 168, 79, 0.08);
  border-color: rgba(216, 168, 79, 0.22);
}

.string-btn--play:hover .string-note-lbl { color: var(--accent); }

.string-btn--play:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.string-btn--play:active {
  transform: translateY(1px);
  background: rgba(216, 168, 79, 0.14);
  border-color: rgba(216, 168, 79, 0.3);
}

.string-num {
  font-size: 0.66rem;
  color: var(--text-muted);
  opacity: 0.5;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.string-note-lbl {
  font-size: 1.4rem;
  font-weight: 700;
  color: var(--text-main);
  line-height: 1;
  letter-spacing: -0.02em;
  transition: color 0.2s ease, text-shadow 0.2s ease;
}

/* Per-string tuning state. Only reachable if updateStringColors() is
   wired back up — the shipped tick() is chromatic and highlights no
   string — but green still means in tune and nothing else, and the
   three degrees of "not there yet" share the accent. */
.string-btn.in-tune {
  border-color: rgba(72, 199, 116, 0.3);
  background: rgba(72, 199, 116, 0.07);
}

.string-btn.in-tune .string-note-lbl {
  color: var(--tuner-green);
  text-shadow: 0 0 12px rgba(72, 199, 116, 0.45);
}

.string-btn.close,
.string-btn.near,
.string-btn.off {
  border-color: rgba(216, 168, 79, 0.22);
  background: rgba(216, 168, 79, 0.05);
}

.string-btn.close .string-note-lbl,
.string-btn.near  .string-note-lbl,
.string-btn.off   .string-note-lbl { color: var(--accent); }

/* Playing: reference tone active. The ring expands once and settles,
   so a click is acknowledged even when the sound is not the point. */
.string-btn.playing {
  background: rgba(216, 168, 79, 0.12);
  border-color: rgba(216, 168, 79, 0.45);
  animation: tuner-string-ring 1.1s ease-out;
}

.string-btn.playing .string-note-lbl { color: var(--accent); }
.string-btn.playing .string-num { color: var(--accent); opacity: 0.85; }

@keyframes tuner-string-ring {
  0%   { box-shadow: 0 0 0 0 rgba(216, 168, 79, 0.4), 0 0 18px rgba(216, 168, 79, 0.16); }
  100% { box-shadow: 0 0 0 9px rgba(216, 168, 79, 0), 0 0 18px rgba(216, 168, 79, 0.1); }
}

/* ---- Controls ---- */
.tuner-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.65rem;
  margin-top: auto;
  padding: 0.85rem 1.45rem 0.95rem;
  border-top: 1px solid rgba(var(--w), 0.08);
}

#tool-tuner .tuner-controls button {
  min-height: 46px;
  border-radius: 8px;
}

/* Sized to its label, not to the card. A full-width gold bar made the
   Mic the heaviest thing on the card and pulled the eye away from the
   display it is supposed to feed. It is still the only gold thing while
   the tuner is idle, which is what makes it the obvious next action. */
.tuner-mic-btn {
  /* space-between puts it right while both fit on one line; the auto
     margin keeps it right on the narrow cards where the row wraps and
     a lone flex item would otherwise fall back to flex-start. */
  margin-left: auto;
  min-height: 44px;
  padding: 0.55rem 1.35rem;
  border-radius: 8px;
  font-family: var(--font-main);
  font-size: var(--type-small);
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  background: rgba(var(--w), 0.05);
  color: var(--text-main);
  border: 1px solid rgba(var(--w), 0.12);
  transition: background 0.18s ease, color 0.18s ease,
              border-color 0.18s ease, transform 0.1s ease;
}

.tuner-mic-btn:hover {
  background: rgba(var(--w), 0.09);
  border-color: rgba(var(--w), 0.2);
}

.tuner-mic-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.tuner-mic-btn:active { transform: translateY(1px); }

/* Idle — and after a refusal, where trying again IS the next action —
   the mic is the only gold thing in the card, so there is never a
   question about what to do first. Once it is listening the display
   does the talking and the button steps back to an outline. */
.tool-card--tuner[data-tuner-state="idle"]   .tuner-mic-btn,
.tool-card--tuner[data-tuner-state="denied"] .tuner-mic-btn {
  background: var(--accent);
  color: #141414;
  border-color: var(--accent);
}

.tool-card--tuner[data-tuner-state="idle"]   .tuner-mic-btn:hover,
.tool-card--tuner[data-tuner-state="denied"] .tuner-mic-btn:hover {
  background: #e3b866;
  border-color: #e3b866;
}

.tool-card--tuner:not([data-tuner-state="idle"]):not([data-tuner-state="denied"]) .tuner-mic-btn .btn-icon {
  color: var(--tuner-live);
}

.tool-card--tuner[data-tuner-state="listening"] .tuner-mic-btn .btn-icon {
  animation: tuner-mic-pulse 2s ease-in-out infinite;
}

@keyframes tuner-mic-pulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* THE ONE PLACE THIS CARD OPTS OUT OF A PAGE-WIDE RULE.
   .practice-page .btn-icon-text collapses to a 44px icon-only square on
   phones (see PRACTICE — RESPONSIVE, ~line 1525). That is right for a
   transport row of Play / Replay / Slow buttons sitting shoulder to
   shoulder, and wrong for both of these: the Mic is this card's single
   call to action and has to read as one, and a lone pencil glyph is not
   a legible name for "Custom tuning". Higher specificity than the
   page-wide rule, so source order does not matter. */
@media (max-width: 768px) {
  .practice-page .tuner-mic-btn {
    width: auto;
    height: auto;
    min-height: 44px;
    padding: 0.55rem 1.2rem;
  }

  .practice-page .tuner-edit-btn,
  .practice-page .tuner-reset-btn {
    width: auto;
    height: auto;
    min-height: 44px;
    padding: 0.3rem 0.8rem;
  }

  .practice-page .tuner-mic-btn .btn-label,
  .practice-page .tuner-edit-btn .btn-label,
  .practice-page .tuner-reset-btn .btn-label {
    display: inline;
  }
}

@media (max-width: 520px) {
  #tool-tuner .tuner-card-header,
  #tool-tuner .tuner-stage,
  #tool-tuner .tuner-controls {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  #tool-tuner .tuner-display {
    grid-template-columns: 1fr;
    gap: 0.8rem;
  }

  #tool-tuner .tuner-meter {
    margin-top: 0;
  }
}

/* One place to switch every tuner animation off. The needle still
   moves — it is the readout, not decoration — but it snaps rather
   than glides, and nothing breathes, nudges or pings. */
@media (prefers-reduced-motion: reduce) {
  .tool-card--tuner *,
  .tool-card--tuner *::before,
  .tool-card--tuner *::after,
  .tool-card--drone *,
  .tool-card--drone *::before,
  .tool-card--drone *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}


/* ==========================================================
   PRACTICE PAGE â€” RESPONSIVE
   ========================================================== */

@media (max-width: 1200px) {
  .practice-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 768px) {
  /* Clip overflow without creating a scroll container (preserves sticky + internal scrolls) */
  .platform-content {
    overflow-x: clip;
  }

  /* Stack all 2-column grids â€” minmax(0,1fr) prevents track expanding to content min-width */
  .practice-2col {
    grid-template-columns: minmax(0, 1fr);
  }

  .practice-2col > *,
  .practice-tools > * {
    min-width: 0;
  }

  .tool-card {
    padding: 1.4rem;
  }

  .tool-card--bar {
    padding: 1.2rem 1.4rem;
  }

  .tracker-top {
    grid-template-columns: 1fr;
  }

  .tracker-timer-block {
    text-align: left;
  }

  .tracker-timer-controls {
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}

@media (max-width: 700px) {
  .practice-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 480px) {
  .tool-card {
    padding: 1.2rem;
  }

  .tool-card--bar {
    padding: 1rem 1.2rem;
  }

  /* Remove fixed voicing width on small phones */
  .chord-voicing--primary {
    width: 100%;
    max-width: 100%;
  }

  /* Slightly smaller large numerics */
  .metro-bpm-display {
    font-size: 2.8rem;
  }

  .tracker-time {
    font-size: 2.4rem;
  }

  /* Tuner on a phone: the display stays the dominant element, it just
     stops being 140px of ring. Everything shrinks by the same step so
     the proportion between note, meter and strings is unchanged. */
  .tuner-note-row {
    width: 5.6rem;
    height: 5.6rem;
  }

  .tuner-note {
    font-size: 3rem;
  }

  .tuner-octave {
    font-size: 0.85rem;
    top: 1rem;
    right: 0.65rem;
  }

  .tuner-track {
    height: 22px;
  }

  .tuner-strings {
    gap: 0.3rem;
    margin-top: 1rem;
  }

  .string-btn {
    min-height: 54px;
    padding: 0.45rem 0.1rem;
  }

  .string-note-lbl {
    font-size: 1.35rem;
  }

  /* Smaller strobe circle on phone */
  .tuner-strobe {
    width: 120px;
    height: 120px;
  }

  /* Tuner top row: wrap so mode buttons don't push card wider than screen */
  .tuner-top-row {
    flex-wrap: wrap;
  }

  .tuner-mode-btns {
    width: 100%;
  }

  .tuner-mode-btn {
    flex: 1;
    text-align: center;
  }

  /* Metro bottom row: allow wrapping */
  .metro-row {
    flex-wrap: wrap;
  }

  /* CAGED: 2 shapes per row on small phones */
  .caged-shape {
    flex: 0 0 calc(50% - 0.375rem);
  }

  /* Tool nav: single scrollable row instead of wrapping */
  .tool-nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }

  .tool-nav::-webkit-scrollbar {
    display: none;
  }

  .tool-nav-link {
    flex-shrink: 0;
  }
}

/* ==========================================================
   MOBILE HAMBURGER DRAWER
   ========================================================== */

/* Mobile topbar â€” hidden on desktop */
.nav-mobile-topbar {
  display: none;
}

.nav-mobile-logo {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
}

.nav-mobile-logo img {
  /* Same wordmark artwork as the desktop sidebar's expanded logo
     (.platform-logo-full), just scaled down to fit the mobile topbar
     next to the hamburger/close control. */
  width: 140px;
  height: auto;
}

/* Hamburger button */
.nav-hamburger {
  background: none;
  border: none;
  cursor: pointer;
  /* A 44px target (14 October 2026); the bars keep their place against the gutter. */
  padding: 0;
  margin-right: -4px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  align-items: center;
}

/* Skip link: out of sight until a keyboard reaches it. */
.skip-link {
  position: fixed;
  top: 1rem;
  left: 1rem;
  z-index: 1000;
  padding: .75rem 1.25rem;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: #111;
  font: 600 var(--type-small)/1 var(--font-main);
  text-decoration: none;
  transform: translateY(-200%);
}
.skip-link:focus { transform: none; outline: 2px solid var(--text-main); outline-offset: 2px; }
main[tabindex="-1"]:focus { outline: none; }

.nav-hamburger span {
  display: block;
  width: 22px;
  height: 2px;
  /* Same value as the main site's hamburger (.nav-toggle span in
     styles.css) so both sites' menu icons read at the same weight. */
  background: rgba(255, 255, 255, 0.85);
  border-radius: 2px;
  transition: transform 0.2s ease, opacity 0.15s ease, width 0.2s ease;
  transform-origin: center;
}

.nav-hamburger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-hamburger.open span:nth-child(2) { opacity: 0; width: 0; }
.nav-hamburger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Overlay behind open drawer */
.nav-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 149;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.nav-overlay.open {
  display: block;
}

@media (max-width: 768px) {
  /* Belt-and-braces against any stray horizontal scrollbar (which, next
     to the drawer's own vertical scroll, can render as a scrollbar-
     corner "resize handle" in some mobile browsers) while the full-
     screen menu is the only thing that should scroll (vertically). */
  body {
    overflow-x: hidden;
  }

  /* Show mobile topbar */
  .nav-mobile-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 1.25rem;
    background: #0f0f0f;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    position: sticky;
    top: 0;
    z-index: 250;
    height: 56px;
    box-sizing: border-box;
  }

  /* Collapse doesn't make sense on the off-canvas drawer — it's already
     full-width and opened/closed by the hamburger, not shrunk to a rail. */
  .sidebar-toggle,
  .sidebar-collapse-action {
    display: none !important;
  }

  /* Sidebar becomes a full-screen mobile menu — covers the entire
     viewport (same dark background as the desktop sidebar), no visible
     edge/border and no rounded corners, since it's a takeover rather
     than an off-canvas panel. The mobile topbar (logo + hamburger/close,
     z-index 250) sits above this (z-index 200), so it reads as the
     menu's own header once open. */
  .platform-sidebar {
    position: fixed !important;
    top: 0 !important;
    left: -100vw !important;
    width: 100vw !important;
    height: 100vh !important;
    height: 100dvh !important;
    z-index: 200;
    transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    flex-direction: column !important;
    flex-wrap: nowrap !important;
    justify-content: flex-start !important;
    padding: calc(56px + clamp(0.75rem, 2vh, 1.5rem)) 1.5rem clamp(0.75rem, 2vh, 1.5rem) !important;
    border: none !important;
    border-radius: 0 !important;
    /* No scroll on the sidebar itself — the nav list scrolls internally
       (below) while the account block (avatar/email/icons) stays pinned
       at the bottom, always in view without scrolling the drawer. */
    overflow-y: hidden;
    overflow-x: hidden;
  }

  .platform-sidebar.open {
    left: 0 !important;
  }

  /* Logo stays put inside the topbar on mobile — hide the in-drawer one
     to avoid a duplicate, and let the nav/account fill the drawer. */
  .platform-sidebar .sidebar-header,
  .platform-sidebar .platform-logo {
    display: none !important;
  }

  /* Layout stays row (sidebar is fixed, out of flow) */
  .platform-layout {
    flex-direction: row !important;
  }

  /* Content takes full width */
  .platform-content {
    width: 100%;
    min-width: 0;
  }

  /* Nav links back to column in drawer. flex:1 + min-height:0 + its own
     overflow-y:auto is a safety net for very short/zoomed viewports — the
     tightened font-size/padding/gap below (mobile only) are sized to fit
     all 5 links without needing that scroll on ordinary phone heights. */
  .platform-nav {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    flex-direction: column !important;
    flex-wrap: nowrap !important;
    /* Centers the link list within its flex-grown space, so any leftover
       room (once the links already fit without scrolling) splits evenly
       above the first link and below the last, instead of all piling up
       at the bottom. */
    justify-content: center !important;
    /* Center each link (and its active pill) on the nav's own horizontal
       axis instead of the default stretch, which otherwise pads every
       link's background out to the width of the widest item (or the
       full drawer, depending on width). */
    align-items: center !important;
    overflow-x: visible !important;
    overflow-y: auto !important;
    width: 100% !important;
    padding: 0 !important;
    gap: clamp(0.3rem, 1.2vh, 0.75rem) !important;
    order: unset !important;
  }

  /* Text centered within the link's own (now content-sized) box, and the
     active pill capped so it reads as a compact tag, not a full-width bar. */
  .nav-link {
    justify-content: center !important;
    max-width: 240px;
  }

  /* Pinned to the bottom of the drawer, never scrolled out of view. */
  .platform-account {
    flex: 0 0 auto !important;
    order: unset !important;
    margin-top: clamp(0.5rem, 1.6vh, 1rem) !important;
    padding-bottom: env(safe-area-inset-bottom, 0);
  }

  /* Avatar+email box is centered as a unit via margin-inline:auto — same
     content axis .nav-link uses (240px cap), same shrink-to-fit approach.
     But the desktop base rule's padding is asymmetric (1.1rem left, 0
     right, meant to align the avatar with nav-link text when the sidebar
     is expanded and left-anchored) — carried into a centered box, that
     asymmetry skews the visual avatar+text group ~9px off the true
     center nav-link uses. Symmetric padding here, matching .nav-link's
     own (0.8rem 1.1rem) exactly, so the box's own center IS its content's
     visual center and both truly land on one axis, not just a coincidence.
     .account-icons isn't capped the same way — its 5 icons already
     total ~242px, just over 240px, so a matching cap would squeeze
     them; it already centers on the same full-width axis as the rest
     via its own justify-content:center below. */
  .account-card {
    max-width: 240px;
    margin-inline: auto;
    padding: 0.8rem 1.1rem;
  }

  /* Mobile-only trim so all 5 links fit without scrolling the drawer, on
     any phone height — clamp() ties font-size/padding to viewport height
     instead of one fixed rem value: a short device (e.g. iPhone SE)
     shrinks further, a taller one relaxes back up toward the original
     comfortable size, both capped so it never grows past desktop's size
     or shrinks past legibility. Horizontal padding stays fixed, so the
     tap target's width never shrinks — only the vertical footprint
     scales. Desktop's .nav-link (line ~220) is untouched. */
  .nav-link {
    font-size: var(--type-body) !important;
    padding: clamp(0.55rem, 2.1vh, 0.9rem) 1rem !important;
    flex-shrink: unset !important;
  }

  /* Tool nav: sticky below topbar, horizontal scroll */
  .tool-nav {
    top: 56px;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 0.65rem;
  }

  .tool-nav::-webkit-scrollbar { display: none; }

  .tool-nav-link {
    flex-shrink: 0;
  }

  /* Cards: contain overflow on mobile */
  .tool-card {
    min-width: 0;
    overflow: hidden;
  }

  #tool-tuner .tuner-display {
    margin-top: 0;
  }

  /* Expand buttons not useful on mobile â€” cards are already full-width */
  .tool-expand-btn {
    display: none;
  }

  /* Show degrees button: compact on mobile */
  .sv-toggle.tool-btn-secondary {
    font-size: 0.72rem;
    padding: 0.3rem 0.7rem;
  }

  /* Chord progressions: single column, no fixed height cap */
  .prog-list {
    grid-template-columns: 1fr;
    max-height: none;
  }

  /* Headers: remove desktop padding-right, allow wrap */
  .cof-card-header,
  .ks-topbar,
  .prog-topbar,
  .caged-topbar {
    padding-right: 0;
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  .cof-card-header .tool-title,
  .ks-topbar .tool-title,
  .prog-topbar .tool-title {
    flex: 1;
    min-width: 0;
  }
}

/* ==========================================================
   HYBRID SIDEBAR — LOCAL PROTOTYPE
   (docs/PRACTICE-PLAN.md, "Navigatie en sidebar")
   ----------------------------------------------------------
   The approved design, drawn: expanded is TEXT ONLY, the collapsed rail
   is ICONS with tooltips, the active page is a quieter dark instead of a
   gold pill, and the footer is TWO rows — a learning context and an
   account — instead of one identity line plus three loose icons.

   WHY THIS BLOCK SITS HERE. It has to come AFTER the mobile hamburger
   drawer above, because several of its rules answer that drawer's own
   !important overrides and would otherwise lose on source order. It has
   to come BEFORE the shared tabbed-page block and the Fretboard and
   Settings blocks further down, because three tests read a SLICE of this
   stylesheet from those banners and demand that every selector inside be
   scoped to their page. See the CSS-slice trap in the portal-design
   skill's visual-qa reference.

   Every structural element the rules below address (.nav-icon,
   .nav-link-label, .nav-tip, .sidebar-menu*, .learning-*, .account-*) is
   built by portal/js/sidebar.js, never by page markup — the sidebar
   block is duplicated in nine pages and this keeps it that way.
   ========================================================== */

/* ---------- the two states of one nav entry ---------- */

/* Expanded and mobile: text only. The icon exists in the DOM on every
   page but is only ever drawn in the collapsed rail. */
.nav-icon {
  display: none;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
}

.nav-link-label {
  min-width: 0;
}

/* The tooltip lives inside its trigger so it can never drift away from
   it. Drawn only in the collapsed rail, where it is the only readable
   name that trigger has. */
.nav-tip {
  display: none;
}

/* ACTIVE PAGE — a subtly lighter dark and brighter text. No gold rule,
   no glow, no border: that treatment is reserved for selection and
   focus, and a permanent gold pill on the current page made the sidebar
   compete with the page it was pointing at.

   Hover moves with it. A gold hover beside a neutral active state reads
   as "gold means current", which is exactly the association the new
   active state gives up. */
.platform-nav .nav-link:hover {
  background: rgba(var(--w), 0.05);
  color: var(--text-main);
}

.platform-nav .nav-link.active {
  background: rgba(var(--w), 0.07);
  color: var(--text-main);
  font-weight: 600;
}

.platform-nav .nav-link.active:hover {
  background: rgba(var(--w), 0.09);
}

/* Coming-soon entries stay quiet in both states. */
.platform-nav .nav-link--coming-soon:hover {
  background: transparent;
  color: rgba(var(--w), 0.42);
}

/* "Coming soon" after a pillar that has not opened yet (Jappe, 13 October
   2026): a quiet tag on the same line, never in the collapsed rail. */
.platform-nav .nav-link--coming-soon {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.nav-soon {
  padding: 0.1rem 0.45rem;
  font-size: var(--type-xs);
  font-weight: 500;
  line-height: 1.5;
  color: rgba(var(--w), 0.5);
  border: 1px solid rgba(var(--w), 0.14);
  border-radius: 6px;
  white-space: nowrap;
}
.sidebar-collapsed .nav-soon { display: none; }

/* Visible focus on every interactive element in the sidebar. Gold here
   is correct and is not the active-page treatment: the accent means
   selection, focus and primary action. */
.platform-sidebar :is(.nav-link, .nav-gear, .sidebar-menu-trigger, .sidebar-menu-item, .sidebar-menu-back, .learning-row, .learning-option, .level-btn, .sidebar-toggle):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- the collapsed rail ---------- */

/* The rail used to hide the navigation entirely — collapsing the sidebar
   meant giving up the navigation to gain page width. It now keeps it, as
   icons. */
.sidebar-collapsed .platform-nav {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding-right: 0;
}

.sidebar-collapsed .nav-icon {
  display: block;
}

.sidebar-collapsed .platform-nav .nav-link {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 12px;
}

/* CLIPPED, NOT display:none. The label is the entry's accessible name,
   and the rail would otherwise present five unnamed icons to a screen
   reader. Clipping keeps the name in the accessibility tree while taking
   no space. */
.sidebar-collapsed .nav-link-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- tooltips in the rail ---------- */

/* Required on every icon in the collapsed rail, and required on POINTER
   AND KEYBOARD both — which is the reason this is an element and not a
   title attribute: a native tooltip never appears on focus. aria-hidden
   in the markup, so it is never a second copy of the name. */
.sidebar-collapsed .nav-tip {
  display: block;
  position: absolute;
  left: calc(100% + 0.7rem);
  top: 50%;
  transform: translateY(-50%);
  z-index: 300;
  padding: 0.35rem 0.6rem;
  border-radius: 8px;
  background: #1c1c1c;
  border: 1px solid rgba(var(--w), 0.12);
  box-shadow: var(--shadow-soft);
  color: var(--text-main);
  font-size: var(--type-small);
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.sidebar-collapsed :is(.nav-link, .sidebar-menu-trigger):hover > .nav-tip,
.sidebar-collapsed :is(.nav-link, .sidebar-menu-trigger):focus-visible > .nav-tip {
  opacity: 1;
}

/* ==========================================================
   THE FOOTER — TWO ROWS, ONE COMPONENT

   The footer carries a LEARNING CONTEXT row (level, notation, language)
   and an ACCOUNT row (identity, Settings, Log out). They look alike on
   purpose — same height, same icon-text-chevron shape, same menu — so
   the sidebar reads as two of one kind of thing. They are kept apart on
   purpose too: a rule between them, and never one menu, because picking
   a level mid-practice and signing out are not the same act.
   ========================================================== */

.platform-account {
  gap: 0.15rem;
}

.sidebar-menu-wrap {
  position: relative;
}

/* One row shape for both triggers. */
.sidebar-menu-trigger {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  /* Tighter horizontally than a nav item, because the learning row has
     to hold three facts plus an icon plus a chevron inside a 260px
     sidebar — and "Intermediate · ABC · NL", the longest it can ever be,
     did not fit at the shipped padding. */
  padding: 0.6rem 0.6rem;
  border: none;
  border-radius: 10px;
  background: none;
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.2s ease;
}

.sidebar-menu-trigger:hover {
  background: rgba(var(--w), 0.05);
}

.sidebar-menu-chevron {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin-left: auto;
  color: var(--text-muted);
  transition: transform 0.2s ease;
}

.sidebar-menu-trigger[aria-expanded="true"] .sidebar-menu-chevron {
  transform: rotate(180deg);
}

/* ---------- the account row ---------- */

/* Name on the first line, "Beginner · ABC · EN" on the second, both
   inside the one button. The stack is what makes the two lines one row:
   the avatar stays vertically centred beside both of them, and the
   chevron stays at the end of the row rather than beside a line of it. */
.account-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.05rem;
  min-width: 0;
}

/* THE TWO FACTS. The level in full and the notation system's own name —
   what is being taught, and how it is written down.

   THE LANGUAGE IS NOT HERE. It keeps its row in the menu, its panel and
   its storage; what it lost is this line. A student reads the portal in
   one language and changes it once, if ever, and every word around this
   row is already visibly in it — so "EN" spent a third of a short line
   confirming what the screen answers by itself. The trigger's accessible
   name still says it, where the screen cannot.

   Diagram labels are absent for a different reason: four families can
   hold four different answers, so there is no one value to state.

   ONE LINE, NORMALLY. "Intermediate · Do–Re–Mi" is the longest this ever
   gets and it fits at every sidebar width the portal has, so the row
   stays two lines tall. The ellipsis below is a safety net for extreme
   zoom or a viewport nobody planned for — not a working state, and not a
   reason to widen the sidebar. */
.account-summary {
  display: block;
  max-width: 100%;
  font-size: var(--type-xs);
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1.45;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.account-summary[hidden] {
  display: none;
}

/* A token is one fact plus the separator that follows it, and it is
   indivisible: whatever gives way, "Do–Re–Mi" is not broken in half.
   The separator travels with the token in FRONT of it, so it can only
   ever end the visible text and never begin it. */
.account-summary-part {
  white-space: nowrap;
}

.account-summary-sep {
  margin-left: 0.24rem;
  color: rgba(var(--w), 0.3);
}

/* The name is the line that must stay readable, so it is the one that
   truncates last: the summary above gives up its tail first. */
.account-trigger .account-email {
  max-width: 100%;
  font-size: var(--type-small);
  color: var(--text-main);
}

/* Admin shows its role and active level. Students keep level + notation;
   account identity and the full preference controls remain in the menu. */
.account-trigger--admin .account-summary-notation,
.account-trigger--admin .account-summary-sep { display:none; }
.account-admin-icon,
.account-trigger .account-admin-label[hidden],
.account-trigger--admin #sidebarAccountAvatar,
.account-trigger--admin #sidebarAccountEmail {
  display: none;
}
.account-trigger--admin .account-admin-icon {
  display: block;
  width: 24px;
  height: 24px;
}

/* THE AVATAR COLUMN NARROWS, because what it used to line up with is
   gone. The shipped 32px wrap matched the width of the icons in the row
   beneath it — the notation toggle, the level picker, the language
   switch — so that the avatar circle and the first icon shared one
   centre. That row no longer exists: its controls are rows in this
   menu now. The column is the circle plus a hair, and the eight pixels
   it gives back are eight pixels the two lines of text keep. */
@media (min-width: 769px) {
  /* Both sit on the NAME's row only, so they stay level with the name
     however many lines the summary underneath it takes. */
  html:not(.sidebar-collapsed) .account-trigger .account-avatar-wrap {
    width: 28px;
    grid-area: avatar;
  }

  /* ONE TEXT COLUMN. The avatar has a fixed column of its own on the
     left; the name and the summary share the column beside it, so the
     two lines start on the same axis and the row reads as one block of
     text with a mark in front of it. The summary reaches into the
     chevron's column as well, because the chevron only occupies the
     first line — that is the extra room that keeps "Beginner · ABC ·
     EN" on one line.

     A grid rather than a rearranged flex box because the stack has to
     stop being a box at all: `display: contents` lets the name and the
     summary take their own places in the row. */
  html:not(.sidebar-collapsed) .account-trigger {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "avatar name chevron";
    align-items: center;
    column-gap: 0.5rem;
    row-gap: 0.02rem;
  }

  html:not(.sidebar-collapsed) .account-trigger .account-stack {
    display: contents;
  }

  html:not(.sidebar-collapsed) .account-trigger .account-email {
    grid-area: name;
  }

  html:not(.sidebar-collapsed) .account-trigger .account-summary {
    grid-area: summary;
  }

  html:not(.sidebar-collapsed) .account-trigger .sidebar-menu-chevron {
    grid-area: chevron;
    margin-left: 0;
  }
}

/* THE TAPERED RANGE lives in ONE place. Between 769px and 1280px the
   sidebar narrows from 260px to 220px and the footer row loses room.
   What is done about it is a small tightening of the row's own chrome —
   exactly as the nav labels already do.

   Those rules are NOT here. They are in the existing tapered-range block
   further down (SIDEBAR — nav labels in the tapered width range), because
   a second @media with the same two widths is the drift the design skill
   warns about — and tests/fretboard-neck.test.mjs reads the first such
   block in this file, so a duplicate does not merely look untidy, it
   takes the nav's own rule out of view. */

/* In the rail the row is its avatar and nothing else. */
.sidebar-collapsed .sidebar-menu-trigger {
  justify-content: center;
  padding: 0.5rem 0;
}

.sidebar-collapsed .account-summary,
.sidebar-collapsed .sidebar-menu-chevron {
  display: none;
}

/* ---------- the shared menu ---------- */

/* Opens upward: the trigger sits at the bottom of the sidebar, so a
   downward menu would run off the bottom of every screen.

   IT IS AS WIDE AS ITS WIDEST ROW, not as wide as the trigger. "Taal
   Nederlands" and "Notennamen Do–Re–Mi" are ordinary values, not edge
   cases, and at the trigger's own 182px they truncated to "Neder…" and
   "Do…" — a menu that hides the very value it exists to state. So it
   starts at the trigger's width and grows from there, capped so it never
   becomes a panel in its own right.

   THE SIDEBAR STOPS CLIPPING WHILE A MENU IS OPEN. The expanded sidebar
   keeps overflow-y:auto as a safety net for a long nav list, and that
   makes it a scroll box in BOTH directions — anything wider than the
   trigger would put a horizontal scrollbar under the whole sidebar and
   clip itself against it. The guard is lifted only while a menu stands
   open, which is the only moment anything needs to overhang, and it goes
   back the instant the menu closes. A browser without :has() simply
   keeps the old clipping behaviour, which is the state this replaced. */
.platform-sidebar:has(.sidebar-menu:not([hidden])) {
  overflow: visible;
  /* Lift the sticky sidebar's stacking context too, so a rail menu stays
     clickable where it extends over positioned page controls. */
  z-index: 220;
}

.sidebar-menu {
  position: absolute;
  bottom: calc(100% + 0.6rem);
  left: 0;
  right: auto;
  width: max-content;
  min-width: 100%;
  max-width: 264px;
  z-index: 220;
  padding: 0.4rem;
  background: var(--bg-card);
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: var(--radius-small);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
  animation: sidebar-menu-in 0.14s ease-out;
}

.sidebar-menu[hidden] {
  display: none;
}

@keyframes sidebar-menu-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* The rail is 88px wide, which is not a menu — so there the menu keeps a
   floor of its own rather than the rail's width. */
.sidebar-collapsed .sidebar-menu {
  min-width: 210px;
}

.sidebar-menu-list {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

/* THE LEARNING HALF, NAMED. The four preference rows sit under a heading
   of their own so they read as one group rather than as the first four
   items of an account menu — and so a screen reader announces "Learning
   preferences" before them. The rule below the group is what separates
   them from Settings and Log out. */
.sidebar-menu-groupwrap {
  padding-bottom: 0.15rem;
}

.sidebar-menu-group-title {
  display: block;
  padding: 0.3rem 0.7rem 0.3rem;
  font-size: var(--type-xs);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.sidebar-menu-group {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

/* One row shape for every command in a menu. They arrive as a .nav-gear
   anchor and a .nav-gear button, so width, height, padding and text
   alignment all have to be re-stated rather than inherited. */
.sidebar-menu-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.sidebar-menu-item svg {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  stroke: currentColor;
}

.sidebar-menu-item:hover {
  background: rgba(var(--w), 0.06);
  color: var(--text-main);
}

/* Settings marks itself on the Settings page, the same way the nav does
   and with the same quiet treatment. */
.sidebar-menu-item.active {
  background: rgba(var(--w), 0.07);
  color: var(--text-main);
}

.sidebar-menu-sep {
  height: 1px;
  margin: 0.35rem 0.4rem;
  background: rgba(var(--w), 0.08);
}

/* Log out is set apart rather than dressed up: a rule above it, and a
   warm tone only once the pointer is on it, so a misclick on the way to
   Settings is not the thing that ends a session. */
.sidebar-menu-item--danger:hover {
  background: rgba(203, 92, 76, 0.14);
  color: #e6907f;
}

/* Desktop has no scrim — the click-outside handler is enough, and a
   sheet backdrop over the whole portal would be theatre. */
.sidebar-menu-backdrop {
  display: none;
}

/* ---------- inside the learning menu ---------- */

/* PANELS, NOT A LONG LIST. The menu is one box that shows one panel at a
   time: four rows to start with, and a sub-panel for Level, Diagram
   labels and Language. Every panel but the visible one is `hidden`, so
   nothing off-screen is in the accessibility tree or in the arrow keys'
   way — a panel that is merely invisible is a panel a screen reader
   still reads out. */
.sidebar-menu-panel[hidden] {
  display: none;
}

.sidebar-menu-panel-head {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding-bottom: 0.35rem;
  margin-bottom: 0.3rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
}

/* The way back out, as a real control rather than a hint: it takes focus,
   it is on the Tab and arrow path, and it is the first thing in the
   panel — which is also where a screen reader meets it. */
.sidebar-menu-back {
  display: flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.35rem 0.45rem 0.35rem 0.3rem;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: var(--type-small);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.sidebar-menu-back:hover {
  background: rgba(var(--w), 0.06);
  color: var(--text-main);
}

.sidebar-menu-back-icon {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
}

/* The panel says which panel it is, once, beside the way out of it. */
.sidebar-menu-panel-title {
  min-width: 0;
  font-size: var(--type-xs);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.learning-panel-options {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

/* ---- a main-menu row ---- */

/* Name on the left, current value on the right, chevron after it. The
   value is what saves a trip into the panel just to read what is already
   set. */
.learning-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.55rem 0.6rem 0.55rem 0.7rem;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background 0.2s ease;
}

.learning-row:hover {
  background: rgba(var(--w), 0.06);
}

.learning-row-label {
  flex: 0 0 auto;
}

/* The value gives way before the name does: a name that has lost its
   last letters is a row nobody can identify, and the value is repeated
   inside the panel anyway. */
.learning-row-value {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  font-size: var(--type-small);
  font-weight: 500;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.learning-row-chevron {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  color: rgba(var(--w), 0.35);
}

/* ---- an option row in a sub-panel ---- */

/* One row shape for the level and language panels, INCLUDING the five
   level buttons — which arrive here as .level-btn chips (their own card
   background, border and centred text) because they are the page's own
   controls, moved rather than copied. Everything a chip needs and a menu
   row does not is unset here rather than in a second set of level
   buttons. */
.learning-option,
.learning-menu .level-btn {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.5rem 0.7rem;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--text-muted);
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.learning-option:hover,
.learning-menu .level-btn:hover {
  background: rgba(var(--w), 0.06);
  color: var(--text-main);
  border-color: transparent;
}

/* The current choice in a panel. Gold IS right here and is not the
   active-page treatment further up: this is a selection inside a menu,
   which is exactly what the accent means. */
.learning-option.active,
.learning-menu .level-btn.active {
  background: rgba(216, 168, 79, 0.12);
  border-color: transparent;
  color: var(--accent);
}

/* The short code the row above also shows, in a fixed column so the
   options line up as one list. */
.learning-code {
  flex: 0 0 auto;
  width: 2.1rem;
  font-size: var(--type-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: inherit;
  opacity: 0.75;
}

.learning-option-label {
  min-width: 0;
}

/* The note-names panel names two SYSTEMS rather than two codes, and
   "Do–Re–Mi" neither fits nor may break inside a column sized for "EN".
   It sizes to its own content and refuses to wrap: a system's name
   broken across three lines reads as three things. Only this panel does
   so; the language panel keeps the tight column that suits "EN" and
   "NL". */
.learning-panel--notation .learning-code {
  width: auto;
  min-width: 3.2rem;
  white-space: nowrap;
}

/* The clarification beside it gives way first — it is the half a reader
   can lose and still know which row they are on. */
.learning-panel--notation .learning-option-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A level nobody may pick yet. level.js already fades it and takes it out
   of pointer reach; in a menu it also has to say so, and the arrow keys
   step over it (sidebar.js filters on aria-disabled). The "coming soon"
   note level.js renders in ::after keeps working — it is the same
   element. */
.learning-menu .level-btn.level-locked {
  opacity: 0.4;
}

.learning-menu .level-btn.level-locked::after {
  display: inline;
  margin: 0 0 0 0.4rem;
  font-size: var(--type-xs);
}

/* ---------- inside the account menu ---------- */

.account-menu-head {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.55rem 0.7rem 0.7rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
  margin-bottom: 0.35rem;
}

.account-menu-eyebrow {
  font-size: var(--type-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The menu head is where both halves of the identity are shown, and each
   half is given the treatment its own kind of text deserves.

   THE NAME WRAPS, AT SPACES ONLY. A name is made of words, and words are
   where it may break; breaking inside one turns "Vandenberghe" into
   "Vandenber" plus "ghe" and makes a person's name look like a bug. It
   is allowed as many lines as it needs, because a name is short enough
   for that to stay a head rather than become a paragraph. */
.account-menu-name {
  font-size: var(--type-small);
  font-weight: 600;
  color: var(--text-main);
  overflow-wrap: normal;
}

/* THE ADDRESS IS ONE LINE, ALWAYS. It has no spaces to break at, so
   wrapping it meant breaking it mid-token — and the shape that produced
   most often was a full address with a lonely "com" underneath it, which
   reads as a mistake rather than as an address. One line and an ellipsis
   is honest about being shortened, and it keeps the head two lines tall
   whatever the address is.

   NOTHING IS LOST BY IT. The full address is still the element's text,
   so a screen reader reads all of it and the trigger's accessible name
   carries it too; a `title` hands it back on a desktop hover; and the
   Account tab of Settings — one row further down this very menu — shows
   it in full. */
.account-menu-email {
  font-size: var(--type-xs);
  font-weight: 500;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-menu-email[hidden] {
  display: none;
}

/* ---------- mobile ---------- */

@media (max-width: 768px) {
  /* THE DRAWER IGNORES THE COLLAPSE PREFERENCE.

     It always did in intent — collapse is meaningless for a full-screen
     menu, which is why the toggle is hidden here. But the preference is
     a class on <html>, and several of the rail's rules outrank the
     drawer's own, so a visitor who had collapsed the sidebar on desktop
     opened the phone menu and found NO NAVIGATION IN IT at all: the
     rail's `display: none` on .platform-nav won, and the drawer never
     said otherwise. The approved design settles it — mobile always shows
     the full menu with text labels, and there is no icon-only variant on
     a phone — so the rail's rules are undone here as a group. */
  .sidebar-collapsed .platform-sidebar {
    overflow-y: hidden;
  }

  .sidebar-collapsed .platform-nav {
    align-items: center;
    gap: clamp(0.3rem, 1.2vh, 0.75rem);
  }

  .sidebar-collapsed .platform-nav .nav-link {
    display: block;
    width: auto;
    height: auto;
    border-radius: 10px;
  }

  .sidebar-collapsed .nav-link-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
  }

  .sidebar-collapsed .account-card {
    justify-content: center;
    padding: 0.8rem 1.1rem;
  }

  .sidebar-collapsed .account-email,
  .sidebar-collapsed .sidebar-menu-chevron {
    display: block;
  }

  /* THE DRAWER SHOWS THE WHOLE ROW, whatever the desktop collapse
     preference says: a phone menu has no rail, so the name and the
     summary are both drawn and both step up a size.

     Same grid as the desktop row, and for the same reason: the avatar
     keeps a fixed column of its own and the name and the summary share
     the column beside it, so both lines start on one axis. Restated
     here rather than inherited, because the drawer's own rules would
     otherwise drop the stack back to a block. */
  .account-trigger,
  .sidebar-collapsed .account-trigger {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "avatar name chevron";
    align-items: center;
    column-gap: 0.5rem;
    row-gap: 0.02rem;
  }

  .account-stack,
  .sidebar-collapsed .account-stack {
    display: contents;
  }

  .account-trigger .account-avatar-wrap,
  .sidebar-collapsed .account-trigger .account-avatar-wrap {
    display: flex;
    grid-area: avatar;
  }

  .account-trigger .account-email,
  .sidebar-collapsed .account-trigger .account-email {
    grid-area: name;
    font-size: var(--type-body);
  }

  .account-trigger .sidebar-menu-chevron,
  .sidebar-collapsed .account-trigger .sidebar-menu-chevron {
    grid-area: chevron;
    margin-left: 0;
  }

  .account-summary,
  .sidebar-collapsed .account-summary {
    display: block;
    grid-area: summary;
    font-size: var(--type-xs);
  }

  .account-summary[hidden],
  .sidebar-collapsed .account-summary[hidden] {
    display: none;
  }

  /* NOTHING ON A PHONE IS ICON-ONLY. A tooltip needs a pointer to hover
     with, and a phone has none — so an icon with no words beside it is a
     control whose meaning has to be guessed at. Icon-only navigation is
     the COLLAPSED DESKTOP RAIL's alone, where a tooltip and an
     accessible name are both available.

     So on a phone the navigation is its text labels, the nav icons and
     their tooltips are drawn in neither preference state, and the three
     learning-preference rows are label, value and chevron — with no
     per-preference pictogram, which is exactly what a row already says
     in words. */
  .nav-icon,
  .sidebar-collapsed .nav-icon,
  .nav-tip,
  .sidebar-collapsed .nav-tip {
    display: none;
  }

  /* The exception, and it is not an exception to the rule above: Settings
     and Log out keep their line icon BESIDE their label, never instead of
     it. Two long-established marks, and the one place in the menu where a
     glance is enough to tell two rows apart. */
  .sidebar-menu-item svg {
    display: block;
  }

  /* Both footer rows are centred on the same axis the nav links use, and
     capped so they read as rows rather than as full-width bars. */
  .sidebar-menu-trigger,
  .sidebar-collapsed .sidebar-menu-trigger {
    max-width: 260px;
    margin-inline: auto;
    padding: 0.7rem 1.1rem;
  }

  /* THE MENUS BECOME SHEETS. A floating popover anchored to the last row
     of a full-screen drawer sits exactly where a thumb rests and where a
     later Toolbox bar would go. A bottom sheet is reachable, cannot be
     clipped by the drawer, and leaves the trigger visible behind its own
     scrim. */
  .sidebar-menu-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 255;
    background: rgba(0, 0, 0, 0.5);
  }

  .sidebar-menu-backdrop[hidden] {
    display: none;
  }

  .sidebar-menu,
  .sidebar-collapsed .sidebar-menu {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 260;
    width: auto;
    min-width: 0;
    max-width: none;
    padding: 0.5rem 0.75rem calc(0.75rem + env(safe-area-inset-bottom, 0));
    border: none;
    border-top: 1px solid rgba(var(--w), 0.1);
    border-radius: 16px 16px 0 0;
    animation: sidebar-sheet-in 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    /* The diagram panel carries four families of options; on a short
       phone that is taller than the room a sheet may take. */
    max-height: 80vh;
    overflow-y: auto;
  }

  .sidebar-menu-item,
  .learning-row,
  .learning-option,
  .learning-menu .level-btn {
    padding: 0.8rem 0.7rem;
    font-size: 0.95rem;
  }

  .sidebar-menu-back {
    padding: 0.55rem 0.6rem 0.55rem 0.4rem;
    font-size: 0.875rem;
  }

  @keyframes sidebar-sheet-in {
    from { transform: translateY(100%); }
    to   { transform: none; }
  }
}

/* Both entrances explain the same thing — where the menu came from — so
   both are dropped rather than shortened when motion is unwelcome. */
@media (prefers-reduced-motion: reduce) {
  .sidebar-menu {
    animation: none;
  }

  .sidebar-menu-chevron,
  .sidebar-menu-trigger,
  .sidebar-menu-item,
  .sidebar-menu-back,
  .learning-row,
  .learning-option,
  .learning-menu .level-btn,
  .nav-tip {
    transition: none;
  }
}


/* ==========================================================
   INTERVAL EXPLORER
   ========================================================== */

.iv-piano-hint {
  font-size: 10px;
  color: rgba(var(--w),0.28);
  font-style: italic;
  margin-left: auto;
  white-space: nowrap;
  line-height: 1.2;
  text-align: right;
}

.iv-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 2rem;
  margin-bottom: 12px;
}

/* On narrow phones a 4+ chip row (e.g. Accidentals' Sharp/Flat/Natural/All
   12) no longer fits in one line and wraps awkwardly. Below 480px, scroll
   the row horizontally instead — same "single scrollable row" pattern as
   .tool-nav above — rather than letting chips drop to a second line. */
@media (max-width: 480px) {
  .iv-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }

  .iv-chips::-webkit-scrollbar {
    display: none;
  }

  .iv-chip {
    flex-shrink: 0;
  }
}

/* font-size/padding: see the shared .tab-filter group (~line 3162). */
.iv-chip {
  font-weight: 700;
  font-family: 'Inter', sans-serif;
  border-radius: 5px;
  border: 1px solid rgba(var(--w),0.12);
  background: rgba(var(--w),0.05);
  color: rgba(var(--w),0.55);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
  letter-spacing: 0.03em;
}

.iv-chip:hover {
  background: rgba(var(--w),0.1);
  color: rgba(var(--w),0.8);
}

.iv-chip.active {
  background: rgba(216,168,79,0.15);
  border-color: rgba(216,168,79,0.5);
  color: var(--accent);
}

.iv-info {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
  padding: 8px 10px;
  background: rgba(var(--w),0.04);
  border-radius: 7px;
  min-height: 34px;
}

.iv-info-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.iv-badge {
  font-size: 0.75rem;
  font-weight: 800;
  padding: 2px 7px;
  border-radius: 4px;
  background: rgba(216,168,79,0.12);
  color: var(--accent);
  border: 1px solid rgba(216,168,79,0.35);
  letter-spacing: 0.04em;
  flex-shrink: 0;
}

.iv-name {
  font-size: 13px;
  font-weight: 600;
  color: rgba(var(--w),0.85);
}

.iv-st {
  font-size: 11px;
  color: rgba(var(--w),0.38);
}

/* Beginner's 5-chip Interval widget was cramped/plain compared to the
   piano widget above it â€” give it the same visual weight. */
.theory-lesson-widget .iv-chips {
  flex-wrap: wrap;
  overflow: visible;
  justify-content: flex-start;
  margin-top: 0;
  margin-bottom: 12px;
  gap: 10px;
}

/* Interval Explorer's own piano sits directly above its chip row —
   give it breathing room instead of the chips touching the keys. */
#ivPiano + .iv-chips {
  margin-top: 1.25rem;
}

/* Same spacing for the "What is Harmony" piano + chip row. */
#harmonyIntroWidget + .iv-chips {
  margin-top: 1.25rem;
}

/* Same spacing for the Musical Alphabet piano + White/Black Keys chip
   row (piano sits directly above the chips here — the empty #alphaIntro
   placeholder now comes before the piano, not after it). */
.alpha-piano-wrap + .iv-chips {
  margin-top: 1.25rem;
}

/* Same spacing for the Accidentals piano + chip row. */
#accidentalsPiano + .iv-chips {
  margin-top: 1.25rem;
}

/* Accidentals widget: since the piano fully re-renders its SVG markup on
   every tab click (same pattern as every other piano widget — no partial
   DOM updates), a CSS *transition* can't animate between old/new key
   color. A short pop-in keyframe on the freshly-inserted active key gives
   the same "the note changed" feedback without restructuring the render
   approach. */
@keyframes accKeyPop {
  from { opacity: 0; transform: scale(0.82); }
  to   { opacity: 1; transform: scale(1); }
}
.acc-highlight {
  animation: accKeyPop 0.32s ease;
  transform-box: fill-box;
  transform-origin: center;
}

.theory-lesson-widget .iv-chip {
  font-size: var(--type-small);
  padding: 0.4rem 0.75rem;
  border-radius: 7px;
}

.theory-lesson-widget .iv-info {
  padding: 14px 16px;
  border-radius: 9px;
  min-height: 70px;
  margin-top: 0;
  flex-wrap: wrap;
}

.theory-lesson-widget .iv-info:empty::before {
  content: attr(data-empty-hint);
  font-size: 13px;
  color: rgba(255, 255, 255, 0.32);
}

.theory-lesson-widget .iv-badge {
  font-size: 0.78rem;
  padding: 3px 8px;
}

.theory-lesson-widget .iv-name {
  font-size: 13px;
}

.theory-lesson-widget .iv-char {
  font-size: 12px;
}

.iv-sep {
  color: rgba(var(--w),0.2);
  font-size: 11px;
}

.iv-char {
  font-size: 11px;
  color: rgba(var(--w),0.5);
  font-style: italic;
  width: 100%;
}

.iv-notes {
  margin-left: auto;
  font-size: 12px;
  font-weight: 700;
  color: #d8a84f;
  letter-spacing: 0.04em;
}

.iv-song {
  font-size: 11px;
  color: rgba(var(--w),0.38);
  font-style: italic;
  white-space: nowrap;
}

.iv-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  margin-bottom: 6px;
}

.iv-group-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(var(--w),0.28);
  width: 100%;
  margin-bottom: 2px;
}

.iv-play-btn {
  font-size: 10px;
  font-weight: 600;
  font-family: 'Inter', sans-serif;
  padding: 3px 9px;
  border-radius: 4px;
  border: 1px solid rgba(var(--w),0.15);
  background: rgba(var(--w),0.06);
  color: rgba(var(--w),0.55);
  cursor: pointer;
  transition: background .12s, color .12s;
  flex-shrink: 0;
}

.iv-play-btn:hover {
  background: rgba(var(--w),0.12);
  color: rgba(var(--w),0.85);
}

.iv-play-group {
  display: flex;
  gap: 5px;
  flex-shrink: 0;
}

.iv-play-melody {
  border-color: rgba(109,184,122,0.3);
  color: rgba(109,184,122,0.7);
}

.iv-play-melody:hover {
  background: rgba(109,184,122,0.1);
  color: #6db87a;
}


/* ==========================================================
   MUSICAL ALPHABET
   ========================================================== */

.alpha-desc {
  font-size: 0.875rem;
  color: rgba(var(--w),0.62);
  margin: 0 0 0.75rem;
  line-height: 1.6;
}

.alpha-desc--section {
  margin-top: 1rem;
}

.alpha-link {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid rgba(216,168,79,0.35);
  transition: color 0.15s, border-color 0.15s;
}
.alpha-link:hover {
  color: #f0c060;
  border-bottom-color: rgba(240,192,96,0.7);
}

.alpha-intro {
  font-size: 12.5px;
  color: rgba(var(--w),0.45);
  margin: 4px 0 10px;
  line-height: 1.45;
}

.alpha-piano-wrap {
  margin: 0 0 4px;
  min-width: 0;
}

.alpha-widget-hint {
  font-size: 12.5px;
  color: rgba(var(--w),0.45);
  margin: 10px 0 0;
  line-height: 1.45;
}

.theory-lesson-widget .alpha-intro {
  margin: 10px 0 18px;
}

.theory-lesson-widget .alpha-intro:empty {
  margin: 0;
}

.theory-lesson-widget .alpha-systems {
  margin-top: 10px;
  gap: 8px;
}

.theory-lesson-widget .alpha-sys-item {
  padding: 9px 14px;
}

/* No extra margin here — the item's own flex `gap` (see .alpha-sys-item
   below) already spaces this from .alpha-sys-notes. A margin on top of
   that gap was double-spacing the label from the note sequence. */
.theory-lesson-widget .alpha-sys-desc {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: #d8a84f;
  letter-spacing: 0.02em;
  margin: 0;
}

.theory-lesson-widget .alpha-sys-notes {
  font-size: 13px;
  line-height: 1.5;
}

.alpha-formula {
  color: rgba(var(--w),0.82);
  font-weight: 600;
  letter-spacing: 0.04em;
}

.alpha-formula-legend {
  font-size: 11px;
  color: rgba(var(--w),0.35);
}

/* Melody tab — Scale Explorer */
/* Beginner compares a scale with a phrase in both languages.
   Intermediate retains its scale explorer. */
.melody-demo { display: none; }
body[data-fb-level="beginner"] .melody-demo {
  display: block;
  width: 100%;
}
body[data-fb-level="beginner"] .melody-legacy-explorer { display: none; }
.melody-legacy-explorer { width: 100%; }

/* Shared Theory piano anatomy. Option rows use .theory-widget-tabs above
   the visual. Standalone Play/Stop actions remain separate below it. */
.theory-piano-widget,
.theory-piano-stage {
  width: 100%;
  min-width: 0;
}
.theory-piano-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  width: 100%;
  margin: 1.25rem 0 0;
}
.theory-piano-actions .iv-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.melody-demo .melody-controls .melody-play {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
}
.melody-play[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(216,168,79,0.1);
}
.melody-play-icon { display: flex; width: 16px; height: 16px; }
.melody-play-icon svg { width: 100%; height: 100%; fill: currentColor; }

/* Guided lesson focus leaves the title below the fixed 56px mobile bar. */
@media (max-width: 768px) {
  .theory-lesson-title[tabindex="-1"] { scroll-margin-top: 76px; }
}

.scale-piano-card {
  margin: 4px 0 0;
}


.scale-piano-card + .iv-chips {
  margin-top: 1.25rem;
  margin-bottom: 14px;
}

/* Scale families compare vertically: full scales on the first row and
   their pentatonic counterparts directly below in matching columns. */
#scaleTypeChips {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  overflow: visible;
}

#scaleTypeChips .iv-chip {
  width: 100%;
  min-width: 0;
  white-space: normal;
  line-height: 1.25;
  text-align: center;
}

.scale-info-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.scale-info-text {
  flex: 1;
  min-width: 200px;
}

.scale-info-text .alpha-desc:last-child {
  margin-bottom: 0;
}

.alpha-svg {
  width: 100%;
  min-width: 360px; /* ceiling now lives once on .theory-lesson-widget itself
                        — every child (piano, neck diagrams, systems row)
                        inherits the same 640px cap automatically. */
  height: auto;
  display: block;
  margin: 0 auto;
}

/* Below ~400px the 360px floor above would force horizontal scroll on the
   wrapper. Drop the floor here so the piano/neck SVG scales all the way
   down to the viewport instead — proportions stay correct via viewBox,
   this is purely "no scroll", not a layout change. */
@media (max-width: 400px) {
  .alpha-svg {
    min-width: 0;
  }
}

.alpha-degs {
  display: flex;
  gap: 3px;
  margin: 0 0 10px;
}

.alpha-deg {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: rgba(var(--w),0.04);
  border-radius: 6px;
  padding: 5px 2px;
  min-width: 0;
}

.alpha-deg-n {
  font-size: 14px;
  font-weight: 700;
  color: #d8a84f;
  line-height: 1;
}

.alpha-deg-name {
  font-size: 10.5px;
  font-weight: 600;
  color: rgba(var(--w),0.85);
  line-height: 1;
  white-space: nowrap;
}

.alpha-deg-sol {
  font-size: 9px;
  color: rgba(var(--w),0.36);
  line-height: 1;
}

.alpha-deg-step {
  font-size: 8.5px;
  font-weight: 700;
  padding: 1px 4px;
  border-radius: 3px;
  line-height: 1.2;
}

.alpha-step-w {
  background: rgba(216,168,79,0.12);
  color: rgba(216,168,79,0.72);
}

.alpha-step-h {
  background: rgba(130,180,255,0.13);
  color: rgba(130,180,255,0.72);
}

.alpha-legend {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
}

.alpha-pill {
  font-size: 10px;
  color: rgba(var(--w),0.35);
  background: rgba(var(--w),0.04);
  border-radius: 4px;
  padding: 3px 7px;
}

.alpha-pill--w { color: rgba(216,168,79,0.62); }
.alpha-pill--h { color: rgba(130,180,255,0.62); }

.alpha-notation {
  margin-bottom: 10px;
}

.alpha-systems {
  display: flex;
  flex-direction: row;
  gap: 5px;
  margin-top: 10px;
}

.alpha-notation-hint {
  margin-top: 8px;
  font-size: 0.8rem;
  color: var(--text-muted);
  opacity: 0.8;
}

.theory-top-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  margin-bottom: 1.5rem;
  gap: 1.5rem;
}

/* Kaderloze lespagina binnen een Theory-tab â€” doorlopende secties i.p.v.
   losse .tool-card's, voor tabs die Ã©Ã©n samenhangend topic uitleggen
   (start: Fundamentals). Geen max-width meer op de sectie zelf â€” de
   tekst/widget-split hieronder begrenst de leeskolom, terwijl de widget
   de resterende breedte vult i.p.v. lege ruimte op brede schermen. */
.theory-lesson-section {
  padding: 1.1rem 0;
}

/* The first visible lesson gets the same top spacing at every level, even
   when earlier sections in the same panel are hidden by level filtering. */
[id^="theory-panel-"] > .theory-lesson-section:not([hidden]) {
  padding-top: 0.5rem;
}

.theory-start-here {
  margin: 0.65rem 0 0;
  color: var(--accent);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.theory-lesson-section:not([hidden]) ~ .theory-lesson-section:not([hidden]) {
  padding-top: 1.75rem;
}

.theory-section-divider {
  border: none;
  border-top: 1px solid rgba(var(--w),0.07);
  margin: 1.75rem 0 1.5rem;
}

.theory-sub-title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-main);
  margin: 0 0 0.9rem;
}

.theory-lesson-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
  /* Same measure as .theory-lesson-split below, so the title never reads
     wider (or narrower) than the text+widget block sitting under it.

     NOT centred. It used to carry margin-inline:auto, and on any screen
     wide enough to exceed the measure that pushed every Theory heading —
     "What is Harmony?", "Major Chords", all of them — about 59px right of
     the page's own h1 and tab row, so the page read as two layout systems
     stacked on each other. A capped block sits ON the axis; only the
     unused space on its right side grows. */
  max-width: var(--page-content-max);
}

/* font-size and color used to be declared here too, but .platform-page h2
   (0,1,1) always outweighs a single class (0,1,0) and wins both — they
   never actually rendered. Only what still wins stays: weight, and the
   margin reset below the more specific rule overrides anyway. */
.theory-lesson-title {
  font-weight: 700;
  margin: 0;
}

/* Focus target set by theory.js focusOverviewTarget() — a ring only
   on keyboard activation (:focus-visible), never after a mouse click. */
.theory-lesson-title[tabindex="-1"]:focus {
  outline: none;
}
.theory-lesson-title[tabindex="-1"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* Vertical gap ABOVE every Theory lesson heading, shared by all five
   levels (Starter / Beginner / Intermediate / Advanced / Expert) — one
   rule, no per-level overrides.

   The h2 above carries `margin: 0`, but `.platform-page h2` (portal-wide,
   `margin: 2.5rem 0 1rem`) is more specific and wins, so a 40px top
   margin was leaking in. Under the tab row that stacked with the tab
   strip's own 1.9rem margin-bottom and the first section's 0.5rem
   padding-top into a ~78px empty band between the tab divider and the
   first heading ("YOUR GUITAR" in Starter). This selector outweighs
   `.platform-page h2` and trims the top margin to ~14px, bringing that
   band to ~53px on desktop. The bottom margin is restated at its current
   value so the heading-to-body-text spacing is unchanged. */
.theory-lesson-header .theory-lesson-title {
  /* Lesson headings share the portal title role; summary/readiness headings
     outside the lesson header keep their existing typography. */
  font: 700 var(--type-title)/1.3 var(--font-serif);
  text-transform: none;
  letter-spacing: normal;
  color: var(--text-main);
  margin-top: 0.9rem;
  margin-bottom: 1rem;
}

.theory-lesson-header .theory-lesson-title::after {
  content: none;
  display: none;
}

/* The degree-formula badge under a lesson title (e.g. "Major Pentatonic
   Scales" / "1–2–3–5–6") — always its own line below the title, at every
   width, rather than inline beside it. Inline used to fit on wide
   desktop screens, but on narrow ones the combined width didn't fit and
   the badge's own tail (e.g. "5–6") wrapped onto an orphan second line
   right under the title's first word; block-always is simpler and reads
   the same everywhere instead of only fixing it below one breakpoint. */
.theory-title-formula {
  display: block;
  font-family: monospace;
  font-size: 0.72em;
  opacity: 0.6;
  font-weight: 400;
  margin-top: 0.3em;
}

/* Beginner topics can use a three-part guided lesson. All destinations stay
   visible in one compact selector, while the page shows only the selected
   chapter. The controls are deliberately quieter than the main Theory tabs. */
.theory-mini-lessons {
  max-width: var(--page-content-max);
  margin: 0.45rem 0 0.4rem;
  padding: 0 0 1.1rem;
}

.theory-mini-lessons-progress {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--type-xs);
  font-weight: 400;
  line-height: 1.4;
  text-align: center;
}

.theory-mini-lessons-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.55rem;
}

.theory-mini-lessons-list--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.theory-mini-lesson {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  min-width: 0;
  min-height: 48px;
  padding: 0.7rem 0.8rem;
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: var(--radius-small);
  background: rgba(var(--w), 0.025);
  color: var(--text-muted);
  font: inherit;
  font-size: var(--type-small);
  font-weight: 600;
  line-height: 1.35;
  text-align: left;
  cursor: pointer;
}

/* Dutch compound words must also fit the narrow three-column lesson row. */
.theory-mini-lesson > span:last-child {
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
  hyphens: auto;
}

.theory-mini-lesson:hover {
  border-color: rgba(var(--w), 0.18);
  color: var(--text-main);
}

.theory-mini-lesson.active {
  border-color: rgba(216, 168, 79, 0.55);
  background: rgba(216, 168, 79, 0.07);
  color: var(--text-main);
}

.theory-mini-lesson:focus-visible,
.theory-mini-lesson-step:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.theory-mini-lesson-number {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: var(--type-small);
  letter-spacing: 0.06em;
}

.theory-mini-lessons-footer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
  max-width: var(--page-content-max);
  margin: 1.5rem 0 0;
  padding: 1.15rem 0 0.2rem;
  border-top: 1px solid rgba(var(--w), 0.07);
}

/* One quiet progress marker at the decision point, not above the lesson.
   The final Starter lesson replaces Next with the next topic in the same slot. */
.theory-mini-lessons-footer > .theory-mini-lessons-progress {
  grid-column: 2;
  grid-row: 1;
}
.theory-mini-lessons-footer > .theory-mini-lesson-step {
  grid-column: 3;
  grid-row: 1;
  text-align: right;
  min-width: 0;
  overflow-wrap: anywhere;
}
.theory-mini-lessons-footer > .theory-mini-lesson-step:first-child {
  grid-column: 1;
  text-align: left;
}

.theory-mini-lesson-step {
  min-height: 44px;
  padding: 0.55rem 0;
  border: 0;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: var(--type-small);
  font-weight: 600;
  cursor: pointer;
}

.theory-mini-lesson-step:hover {
  color: var(--text-main);
}

.theory-mini-lesson-step.is-unavailable {
  visibility: hidden;
}

@media (max-width: 600px) {
  .theory-mini-lessons-list {
    gap: 0.4rem;
  }
  .theory-mini-lesson {
    justify-content: center;
    flex-direction: column;
    gap: 0.2rem;
    min-height: 62px;
    padding: 0.55rem 0.35rem;
    font-size: 0.75rem;
    text-align: center;
  }
}
/* Beginner Melody groups related scale families into chapters while keeping
   every explanation and neck diagram visible. The chapter introduces the shared
   idea, each lesson teaches one scale, and the comparison closes the chapter. */
.theory-scale-chapter-overview {
  margin-bottom: 0;
}

.theory-scale-chapter-header {
  /* Chapter titles follow the same shared title-to-split spacing as every
     Theory lesson. Keeping the header outside the split also guarantees
     that a tall comparison can never rise above its chapter title. */
}

/* Shared diatonic/pentatonic comparison, approved round variant.
   This numeric reference keeps the approved gold tonic; neck/piano root
   colours stay with their own component. Real text never scales as an SVG. */
.theory-scale-chapter-overview > .theory-scale-family-widget {
  --scale-node-size: 32px;
  --scale-altered: #dc9991;
  --scale-omitted: #94918a;
  min-width: 0;
  padding: 0;
  container: scale-family / inline-size;
}

.theory-scale-family-widget > div {
  width: 100%;
}

.theory-scale-comparison {
  background: var(--bg-main);
}

.theory-scale-family-row {
  padding: 0 0 12px;
}

.theory-scale-family-row + .theory-scale-family-row {
  border-top: 1px solid rgba(var(--w), 0.05);
  padding-top: 10px;
}

.theory-lesson-section .theory-scale-family-label {
  margin: 0 0 8px;
  color: var(--text-main);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.3;
}

.theory-scale-family-widget .theory-scale-track {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  position: relative;
  list-style: none;
  padding: 0;
  margin: 0;
}

.theory-scale-track::before {
  content: "";
  position: absolute;
  height: 1px;
  left: 6.25%;
  right: 6.25%;
  top: calc(var(--scale-node-size) / 2);
  background: rgba(var(--w), 0.16);
}

.theory-scale-track .theory-scale-note {
  position: relative;
  min-width: 0;
  margin: 0;
  padding: 0;
  text-align: center;
  list-style: none;
}

.theory-scale-tone {
  display: block;
  text-decoration: none;
}

.theory-scale-degree {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--scale-node-size);
  height: var(--scale-node-size);
  margin: auto;
  border-radius: 50%;
  background: var(--bg-main);
  box-shadow: inset 0 0 0 1px rgba(var(--w), 0.09);
  color: var(--text-main);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1;
}

.theory-scale-note-name {
  display: block;
  padding-top: 5px;
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1.2;
  white-space: nowrap;
}

.theory-scale-note.is-root .theory-scale-degree {
  background: var(--accent);
  box-shadow: none;
  color: var(--bg-main);
  font-weight: 600;
}

.theory-scale-note.is-root .theory-scale-note-name,
.theory-scale-note.is-octave .theory-scale-degree {
  color: var(--accent);
}

.theory-scale-note.is-octave .theory-scale-degree {
  box-shadow: inset 0 0 0 1px rgba(216, 168, 79, 0.42);
}

.theory-scale-note.is-altered .theory-scale-degree,
.theory-scale-family-widget .is-half {
  color: var(--scale-altered);
}

.theory-scale-step {
  position: absolute;
  top: -8px;
  left: 100%;
  transform: translateX(-50%);
  color: var(--text-muted);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1;
}

.theory-scale-note.is-omitted .theory-scale-degree,
.theory-scale-note.is-omitted .theory-scale-note-name {
  color: var(--scale-omitted);
  box-shadow: none;
}

.theory-scale-note.is-omitted .theory-scale-degree::after {
  content: "";
  position: absolute;
  width: 21px;
  height: 1px;
  background: var(--scale-omitted);
  transform: rotate(-45deg);
}

.theory-lesson-section .theory-scale-widget-legend {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 7px 18px;
  margin: 0;
  padding-top: 10px;
  border-top: 1px solid rgba(var(--w), 0.05);
  color: var(--text-muted);
  font-family: Inter, sans-serif;
  font-size: 0.875rem;
  line-height: 1.5;
  text-align: center;
}

.theory-scale-widget-legend > span {
  display: inline-flex;
  gap: 7px;
  align-items: center;
}

.theory-scale-widget-legend b {
  color: var(--text-main);
  font-weight: 500;
}

.theory-scale-omission-mark {
  position: relative;
  display: inline-block;
  flex: 0 0 13px;
  width: 13px;
  height: 13px;
}

.theory-scale-omission-mark::after {
  content: "";
  position: absolute;
  top: 6px;
  left: -1px;
  width: 15px;
  height: 1px;
  background: var(--text-muted);
  transform: rotate(-45deg);
}

.theory-scale-chapter > .theory-scale-chapter-lesson,
.theory-scale-chapter > .theory-scale-chapter-lesson:not([hidden]) ~ .theory-scale-chapter-lesson:not([hidden]) {
  margin-top: 1.65rem;
  padding: 1.65rem 0 0;
  border-top: 1px solid rgba(var(--w), 0.08);
}

.theory-scale-chapter-lesson-header {
  max-width: var(--page-content-max);
  margin-bottom: 1.25rem;
}

.theory-scale-chapter-lesson-title {
  margin: 0;
  color: var(--text-main);
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.35;
}

@container scale-family (max-width: 420px) {
  .theory-scale-comparison { --scale-node-size: 28px; }
  .theory-scale-degree { font-size: 0.875rem; }
  .theory-scale-note-name { font-size: 0.6875rem; }
  .theory-lesson-section .theory-scale-family-label { margin-bottom: 10px; }
  .theory-scale-step { top: -9px; }
}

/* Text left (520px readable measure) / widget right (needs real room —
   a 15-fret neck stops being legible below ~600px). The widget column
   never shrinks past that floor; once the viewport can't offer both at
   once, the breakpoint below stacks to one column instead of squeezing
   either side smaller than its usable size. Equal outer margins on both
   sides come from .platform-page's own uniform padding — this grid just
   fills that space edge to edge (text capped, widget absorbing the rest
   via 1fr), so no extra gap opens up on the widget's outer edge. */
/* Starter, Beginner and Intermediate all use this same lesson grid.
   Content may differ, but the column ratio, gutter, readable text measure,
   widget bounds and responsive breakpoint come from these shared rules. */
.theory-lesson-split {
  display: grid;
  /* ~45% text / 55% widget, fr-based so the ratio actually holds instead
     of just capping at fixed px — text and widget grow together as the
     container widens. Capped at 1400px and centered so ultra-wide screens
     don't stretch the row absurdly; below that it flexes down to its
     minmax floors before collapsing to one column at the 1400px
     breakpoint. */
  /* The same floors protect readable prose and full-size widgets at every
     level; when both no longer fit, the shared breakpoint stacks them. */
  grid-template-columns:
    minmax(var(--split-text-min, 280px), 45fr)
    minmax(var(--split-widget-min, 320px), 55fr);
  gap: 1rem;
  align-items: stretch;
  /* Capped, left-aligned — see .theory-lesson-header above for why this
     must not centre itself. */
  max-width: var(--page-content-max);
}

/* Sidebar collapse frees ~172px (260px → 88px). Rather than let that
   become dead space beside a fixed-width block, the whole split grows
   and re-balances toward an even 50/50 split — the widget (see
   .theory-lesson-widget below) is what actually gains the room; the gap
   stays the same so the two columns don't drift apart. The wider ceiling
   that lets it visibly grow comes from --page-content-max, which
   :root.sidebar-collapsed raises to 1580px for every page-level block. */
.sidebar-collapsed .theory-lesson-split {
  grid-template-columns:
    minmax(var(--split-text-min, 280px), 50fr)
    minmax(var(--split-widget-min, 320px), 50fr);
}

.theory-lesson-split--equal {
  grid-template-columns: 1fr 1fr;
  align-items: start;
  gap: 2rem;
}

/* Diagrams & TAB asks for two columns of the SAME width, and two things
   stand in the way of that. The widget column carries a 600px floor
   (sized for full fretboards), which on this page simply ate the text
   column's share; and a bare `1fr` track still cannot shrink below its
   content's min-content width, which the 15-fret neck pushes past
   1200px. minmax(0, 1fr) lets both tracks divide the row evenly and
   makes the diagrams size to their column instead of the reverse.

   Inside the two-column range only. The shared rule that collapses
   every split to one column below 1400px is a plain .theory-lesson-split
   selector, so anything more specific — this, or the --equal class —
   silently outranks it and would keep two columns down to phone width.
   Scoped to this panel as well: elsewhere that 600px floor does real
   work for full-neck diagrams. */
@media (min-width: 1401px) {
  #theory-panel-reading-music .theory-lesson-split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    gap: 1rem;
  }
  #theory-panel-reading-music .theory-lesson-widget {
    min-width: 0;
  }
}

/* Short caption under a widget's own diagram — smaller/muted, reads as a
   note about the image rather than body copy. */
.theory-widget-caption {
  font-family: Inter, sans-serif;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--text-muted);
  text-align: center;
  margin: 0.85rem 0 0;
}

/* Body-copy classes can share the same element as a diagram caption.
   The caption role wins inside a lesson widget: one 14/21 supporting-text
   standard across Starter, Beginner and Intermediate. */
.theory-lesson-section .theory-lesson-widget .theory-widget-caption {
  font-family: Inter, sans-serif;
  font-size: 0.875rem;
  line-height: 1.5;
}

/* Cross-reference in the lesson text — a plain muted sentence with one
   text link, deliberately not a button or CTA. */
.theory-widget-xref {
  text-align: left;
}

/* The one link in that sentence: the portal's accent, a quiet underline,
   no default browser blue. Scoped to .theory-widget-xref so it touches
   only these cross-references, not any other in-content anchor. */
.theory-widget-xref a {
  color: var(--text-muted);
  font-weight: 500;
  text-decoration: none;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}

.theory-widget-xref a:hover,
.theory-widget-xref a:focus-visible {
  color: var(--accent);
  text-decoration: underline;
}

.theory-widget-xref a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* The shared keyboard owns its type sizes. No level- or viewBox-specific
   mobile enlargements: those clipped long names on narrow black keys. */
svg.theory-piano {
  display: block;
  width: 100%;
  min-width: 0;
  height: auto;
  margin: 0;
}

/* Shared Theory diagram family. Only musical content/range varies by lesson.
   Attribute geometry remains useful to consumers; presentation is central. */
svg.theory-neck-svg {
  --theory-neck-dot-radius: 12px;
  width: 100%;
  min-width: 0;
  height: auto;
  display: block;
  margin: 0;
}
.theory-neck-svg .theory-neck-dot { r: var(--theory-neck-dot-radius); }
.theory-neck-svg .theory-neck-label { font-size: 11px; font-weight: 600; }
.theory-neck-svg .theory-neck-label--long { font-size: 9px; }
.theory-neck-svg .theory-neck-label--pair { font-size: 7.5px; }
.theory-neck-svg .theory-neck-guide { font-size: 12px; fill: rgba(var(--w), 0.5); }
.theory-neck-svg .theory-neck-inlay { r: 3px; fill: rgba(var(--w), 0.25); }
.theory-neck-svg .theory-neck-nut { fill: rgba(235,235,235,0.78); }
.theory-neck-svg .theory-neck-fret { stroke: rgba(var(--w), 0.1); stroke-width: 1.5px; }
/* Fit the existing prose/visual split instead of overflowing a narrow track.
   A position crop and a full neck share the skin, not the displayed range. */
.theory-lesson-widget:has(.theory-piano, .theory-neck-svg),
.sidebar-collapsed .theory-lesson-widget:has(.theory-piano, .theory-neck-svg) {
  min-width: 0;
  max-width: none;
  width: 100%;
  margin-inline: 0;
}

/* The instruction stays with the lesson text; its live result stays directly
   under the interactive drawing. It uses body-copy sizing, with gold and a
   modest weight as the only status treatment. */
.theory-lesson-section :is(.starter-interaction-feedback, .theory-widget-feedback) {
  width: 100%;
  margin: 0.85rem 0 0;
  color: var(--accent);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.85;
}
.theory-piano-key[role="button"],
.theory-neck-note[role="button"] { cursor: pointer; }
.theory-piano-key[role="button"]:focus-visible,
.theory-neck-note[role="button"]:focus-visible { outline: none; }
.theory-piano-key[role="button"]:focus-visible rect,
.theory-neck-note[role="button"]:focus-visible circle {
  stroke: var(--text-main);
  stroke-width: 2px;
}
/* Selection adds only a fine ring; keep the shared filled gold notes. */
.theory-neck-note[aria-pressed="true"] .theory-neck-dot {
  stroke: var(--text-main);
  stroke-width: 1.5px;
}
/* Equal drawing areas; SVG viewBox scaling preserves each instrument's
   original proportions. Narrow portrait necks retain their own sizing. */
@media (min-width: 701px) {
  #theory-panel-starter-fundamentals :is(svg.theory-piano, svg.theory-neck-svg) {
    aspect-ratio: 4 / 1;
  }
}
/* Starter's alternate diagram shares the clean Theory neck renderer.
   The lesson copy replaces the old duplicate visual-key legend. */
.starter-neck-stage #starterOpenStringNeck {
  margin: 0;
}


/* Widget column stretches to match the text column's height (whichever
   is taller), then distributes its own contents across that height
   instead of clumping at the top with dead space below.
   max-width is the ceiling every child inherits (width:100% of this),
   so no widget can read wider than another one next to it. */
.theory-lesson-widget {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: min(100%, 600px);
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
}

/* Grows with the split row's 50/50 re-balance above when the sidebar
   collapses, so the widget genuinely gets wider rather than the freed
   space landing as empty gap. */
.sidebar-collapsed .theory-lesson-widget {
  min-width: min(100%, 660px);
  max-width: 810px;
}

.int-foundations-map {
  margin-top: 0;
}

/* Secondary Intermediate visual in the same column as the primary widget.
   This preserves Beginner Theory's one text column / one visual column
   orientation instead of creating an alternating second grid row. */
.theory-widget-continuation {
  width: 100%;
  margin-top: 1.35rem;
  padding-top: 1.35rem;
  border-top: 1px solid rgba(var(--w),0.08);
}

/* Intermediate Theory overview: one editorial contents table rather than
   a dashboard of cards. Rows follow the same left edge and restrained rule
   system as the lessons they open. */
.theory-overview-intro {
  max-width: 720px;
  margin: 0 0 1.4rem;
  color: var(--text-muted);
  font-size: 0.95rem;
  line-height: 1.7;
}

.theory-overview-list {
  max-width: 1000px;
  border-top: 1px solid rgba(var(--w),0.1);
}

.theory-overview-row {
  display: grid;
  grid-template-columns: 2.25rem minmax(9rem, 0.7fr) minmax(18rem, 1.6fr);
  gap: 1rem;
  align-items: center;
  width: 100%;
  min-height: 4.25rem;
  padding: 0.85rem 0;
  border: 0;
  border-bottom: 1px solid rgba(var(--w),0.1);
  background: transparent;
  color: var(--text-main);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.theory-overview-number {
  color: var(--accent);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.theory-overview-name {
  font-size: 0.9rem;
  font-weight: 700;
}

.theory-overview-desc {
  color: var(--text-muted);
  font-size: 0.86rem;
  line-height: 1.5;
}

.theory-overview-row:hover .theory-overview-name,
.theory-overview-row:focus-visible .theory-overview-name {
  color: var(--accent);
}
.theory-overview-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

@media (max-width: 700px) {
  .theory-overview-row {
    grid-template-columns: 2rem 1fr;
    gap: 0.65rem;
    padding: 0.9rem 0;
  }
  .theory-overview-name {
    align-self: end;
  }
  .theory-overview-desc {
    grid-column: 2 / 3;
  }
}

.scale-type-blue-toggle {
  display: flex;
  justify-content: center;
  margin: 0 0 0.85rem;
}

.scale-type-blue-toggle[hidden] {
  display: none;
}

@media (max-width: 1400px) {
  .theory-lesson-split,
  .sidebar-collapsed .theory-lesson-split {
    grid-template-columns: 1fr;
  }
  .theory-lesson-widget,
  .sidebar-collapsed .theory-lesson-widget {
    min-width: 0;
  }
}

/* Opt-out for widgets whose own content is inherently small (a single
   chord diagram, a button row) — the 600–720px floor above is sized for
   fretboards/pianos; forcing a narrow widget's box to that same width
   just centers a small diagram inside a lot of dead space instead of
   centering the diagram itself. This shrinks the box to its content,
   still centered in the track via margin:auto. */
.theory-widget-narrow {
  width: fit-content;
  min-width: 0;
  max-width: 100%;
  /* Small, fixed-height content (a single chord diagram) shouldn't sit
     vertically centered against a taller text column — pin it near the
     top of the split row instead. */
  justify-content: flex-start;
}

/* Side-by-side pair of chord diagrams within one narrow widget (e.g. the
   two barre-chord shapes) — same gap/alignment language as
   .reading-chord-example uses for its chord progression row. */
.theory-widget-row {
  display: flex;
  flex-direction: row;
  gap: 2rem;
  align-items: flex-start;
}

/* The split row itself defaults to align-items:stretch, which forces the
   narrow chord-diagram column to the full height of the (taller) text
   column — flex-start above only repositions content within that
   over-tall box, it can't remove the leftover space below it. Shrink the
   row itself to its content instead, wherever a narrow widget is present. */
.theory-lesson-split:has(.theory-widget-narrow) {
  align-items: start;
}

/* A section split between Beginner and Intermediate copy can end up with
   its widget hidden entirely at Beginner (#tool-harmony-function: the
   #harmonyFamilies classification widget stays Intermediate-only). The
   two-column grid above still reserves the widget track's width even
   though nothing renders there — collapse to one column instead, same
   principle as a Fretboard tab that skips a step (page-layout.md). */
.theory-lesson-split:has(> .theory-lesson-widget[hidden]) {
  grid-template-columns: 1fr;
}

/* Shared readable measure for every Theory level. Starter's selector strip
   inherits the same text track, so its controls and dynamic line remain
   aligned with the prose without introducing a separate lesson layout. */
.theory-lesson-text {
  max-width: 520px;
}

/* The English Melody introduction has completed its content review. Keep the
   existing Dutch paragraphs available until their separate language review. */
.theory-copy-nl-preserved {
  display: none;
}

html:lang(nl) .theory-copy-nl-preserved:not([hidden]) {
  display: block;
}

html:lang(nl) .theory-copy-reviewed-en { display: none; }

/* Grows along with .theory-lesson-widget's own sidebar-collapsed rule
   above — same proportional increase — so the text column genuinely gets
   wider too instead of the freed space landing only on the widget side. */
.sidebar-collapsed .theory-lesson-text {
  max-width: 585px;
}

/* Compact lesson variant: a small group of chord diagrams / one piano fits
   beside prose without the full-neck widget's 600px floor. Auto-fit responds
   to actual content space, including sidebar changes, and stacks on mobile. */
.theory-lesson-split.theory-lesson-split--compact {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  gap: 2rem;
}
.theory-lesson-split--compact > .theory-lesson-widget {
  min-width: 0;
  max-width: 100%;
  width: 100%;
}
.theory-lesson-split--compact .theory-widget-caption {
  text-align: left;
}
.theory-lesson-split--compact .iv-chips {
  flex-wrap: wrap;
  overflow: visible;
}
.theory-lesson-split--compact .iv-chip {
  min-height: 44px;
}
.theory-example-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.theory-example-select {
  min-height: 44px;
  min-width: 5rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid rgba(var(--w), 0.18);
  border-radius: 6px;
  background: var(--bg-card);
  color: var(--text-main);
  font: inherit;
}
.theory-example-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.theory-example-note {
  color: var(--text-muted);
}
.theory-grips {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 8rem), 1fr));
  gap: 1rem;
  width: 100%;
}
.theory-grip {
  min-width: 0;
  margin: 0;
  text-align: center;
}
.theory-grip figcaption {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-height: 3rem;
}
.theory-grip figcaption strong {
  color: var(--text-main);
  font-size: 1rem;
}
.theory-grip figcaption span {
  color: var(--text-muted);
  font-size: 0.75rem;
}
.theory-grip svg {
  width: 100%;
  max-width: 180px;
  height: auto;
}
.theory-capo-table {
  width: 100%;
  margin-top: 1.25rem;
  border-collapse: collapse;
  font-size: 0.85rem;
}
.theory-capo-table caption {
  text-align: left;
  padding-bottom: 0.65rem;
  color: var(--text-muted);
}
.theory-capo-table th,
.theory-capo-table td {
  padding: 0.65rem 0.25rem;
  border-bottom: 1px solid rgba(var(--w), 0.1);
}
.theory-capo-table th {
  text-align: left;
  font-weight: 400;
  color: var(--text-muted);
}
.theory-capo-table td {
  text-align: center;
  white-space: nowrap;
  color: var(--text-main);
}
.theory-lesson-split--compact #cagedShapes .chord-caged-row {
  width: 100%;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 8rem), 1fr));
}
.theory-lesson-split--compact #cagedShapes .chord-caged-item {
  padding: 0;
}
.theory-lesson-split--compact #cagedShapes svg {
  max-width: 180px;
}

/* Full-width text section (no widget column) */
.theory-lesson-text--full {
  max-width: 720px;
}

/* alpha-list â€” simple bullet list used in theory text columns */
.alpha-list {
  padding-left: 1.25rem;
  margin: 0.25rem 0;
}
.alpha-list li {
  margin-bottom: 0.25rem;
}

/* Rhythm only: the note/rest tree on the right stretches its rows across
   the full column height (see .rhy-tree) â€” let the text paragraphs on the
   left spread out the same way instead of clumping at the top, so both
   columns keep growing together as content is added to either one. */
#tool-rhythm .theory-lesson-text {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

/* The note-values widget is shorter/denser than the text column tends to
   run, so the base .theory-lesson-widget centering (justify-content:
   center) left it starting noticeably lower than the left column's first
   line. Anchor it to the top instead so both columns line up. */
#tool-rhythm .theory-lesson-widget {
  justify-content: flex-start;
}

/* Feel & Groove's metronome widget, 15% narrower than its column — width
   only, height/font-sizes stay untouched, just centered in the leftover
   space. */
#tool-feel #metroTheoryWidget {
  max-width: 85%;
  margin-inline: auto;
}

/* Match the Chord Diagrams section's column layout exactly, so the TAB
   widget sits at the same horizontal position on the page. */
#tool-reading-tab .theory-lesson-widget {
  align-items: center;
  /* Sizing reference for the TAB below: the widget column itself, the
     same box the neck and piano SVGs fill with width:100%. Scoped to
     this section — it is not a page-wide container. */
  container-type: inline-size;
}
/* The TAB spans the widget column, like every diagram in Theory. A
   monospace block cannot do that with width:100% (the text would keep
   its own size and leave the rest of the box empty), so the type is
   sized to the column instead: --tab-cols is the block's real width in
   characters, set by renderTabExample(), and Courier New advances
   0.6em per character. 0.62 leaves a hair of margin against rounding.
   The cap keeps it from growing into a headline on a wide column; the
   floor keeps it legible on a narrow one, where overflow-x may scroll. */
#tool-reading-tab .reading-tab-example {
  width: 100%;
  font-size: clamp(0.75rem, calc(100cqi / (var(--tab-cols, 74) * 0.62)), 1rem);
  overflow-x: auto;
}

/* Scales (Melody, Beginner): the widget column (chips + piano + degree
   row + description + use-line + play button) is naturally taller than
   the short text column here — stretching both to equal height (the grid
   default) leaves a large dead gap under the text. Let each column take
   its own height, and center the widget block on the text column's
   height instead of pinning it to the top. */
#tool-scales-beginner .theory-lesson-split {
  align-items: center;
}

/* Narrower widget column truncates the 3-across notation cards
   (.alpha-sys-notes was tuned for a ~600-900px card) â€” stack them
   instead of cramming into a row that clips text with ellipsis. */
.theory-lesson-widget .alpha-sys-notes {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

/* Mobile: the 3-across row leaves each card only ~110px wide (of a
   ~335px screen), too narrow for "Do · Re · Mi · Fa · Sol · La · Si" to
   read as anything but a cramped 3-line wrap. Stack the 3 cards instead
   so each gets the widget's full width — every sequence then fits on
   one clean line (or a natural two, never mid-word-tight). */
@media (max-width: 600px) {
  .alpha-systems,
  .theory-lesson-widget .alpha-systems {
    flex-direction: column;
  }
}

.theory-lesson-section .alpha-desc {
  font-size: 1rem;
  line-height: 1.85;
  margin-bottom: 1.1rem;
}

.theory-key-list {
  margin: 0.6rem 0 0;
  padding-left: 1.2rem;
  list-style: disc;
}

.theory-key-list li {
  line-height: 1.7;
}

.theory-top-2col > * {
  min-width: 0;
}

.theory-blocks {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
  margin-top: 1.25rem;
}

.theory-block--wide {
  grid-column: 1 / -1;
}

.te-section {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid rgba(var(--w),0.06);
}

.te-aside {
  font-size: 0.68rem;
  color: var(--text-secondary);
  opacity: 0.4;
  align-self: center;
  margin-left: 0.5rem;
  font-style: italic;
}

.te-text {
  margin: 0 0 0 0;
  font-size: 0.9rem;
  line-height: 1.65;
  color: var(--text-secondary);
}
/* â”€â”€ Theory examples â”€â”€ */
.te-example {
  background: rgba(0,0,0,0.25);
  border-radius: var(--radius-small);
  padding: 0.5rem 0.75rem;
  margin-top: 0.4rem;
  overflow-x: auto;
}

.te-row {
  display: flex;
  align-items: baseline;
  gap: 0;
  flex-wrap: nowrap;
}

.te-row + .te-row { margin-top: 0.3rem; }

.te-row--minor { opacity: 0.6; }

.te-row--arpeg {
  align-items: center;
  gap: 0.4rem;
}

.te-row--degs {
  margin-top: 0.15rem;
}

.te-labeled-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.3rem;
}

.te-labeled-row:first-child { margin-top: 0; }

.te-row-label {
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
  opacity: 0.5;
  min-width: 4rem;
  flex-shrink: 0;
}

.te-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 1.8rem;
  text-align: center;
}

.te-note {
  font-family: var(--font-mono, monospace);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-secondary);
  line-height: 1.4;
}

.te-note.te-root  { color: var(--accent); }
.te-note.te-chord { color: var(--text); }
.te-note.te-flat  { color: var(--text); }
.te-note.te-dim   { opacity: 0.2; }
.te-note.te-add   { color: #9db89a; }
.te-deg.te-add    { color: #9db89a; opacity: 0.6; }

.te-deg {
  font-size: 0.68rem;
  color: var(--text-secondary);
  opacity: 0.5;
  line-height: 1.4;
}

.te-deg.te-dim { opacity: 0.12; }

.te-arrow {
  font-size: 0.8rem;
  color: var(--text-secondary);
  opacity: 0.4;
  flex-shrink: 0;
}

.te-chord-label {
  font-size: 0.75rem;
  color: var(--text-secondary);
  opacity: 0.55;
  margin-top: 0.5rem;
  letter-spacing: 0.03em;
}

.te-legend {
  font-size: 0.72rem;
  color: var(--text-secondary);
  opacity: 0.55;
  margin-top: 0.6rem;
}

.te-leg-item.te-root  { color: var(--accent); }
.te-leg-item.te-flat  { color: #7eb8d4; }

/* Pillars */
.te-pillars {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.te-pillar {
  flex: 1 1 140px;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.te-pillar-name {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
  opacity: 0.6;
}

.te-pillar-desc {
  font-size: 0.72rem;
  color: var(--text-secondary);
  opacity: 0.45;
  font-style: italic;
}

/* Evolution grid */
.te-evo-cols {
  display: grid;
  grid-template-columns: 4.5rem 1fr 1fr 1fr;
  gap: 0.4rem 0.5rem;
  margin-bottom: 0.4rem;
}

.te-evo-col-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
  opacity: 0.45;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid rgba(var(--w),0.06);
}

.te-evo-sub {
  font-weight: 400;
  opacity: 0.6;
  text-transform: none;
  letter-spacing: 0;
}

.te-evo-badge {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  color: #9db89a;
  letter-spacing: 0;
  text-transform: none;
  vertical-align: middle;
}

.te-evo-row {
  display: grid;
  grid-template-columns: 4.5rem 1fr 1fr 1fr;
  gap: 0.4rem 0.5rem;
  align-items: center;
  padding: 0.35rem 0;
  border-top: 1px solid rgba(var(--w),0.04);
}

.te-evo-row:first-of-type { border-top: none; }

.te-row--chrom {
  flex-wrap: nowrap;
  gap: 0.15rem;
}

.te-row--chrom .te-note {
  opacity: 0.55;
}

.te-row--chrom .te-note.te-root,
.te-row--chrom .te-note.te-chord {
  opacity: 1;
}

.te-row--chrom .te-cell--dim .te-note {
  opacity: 0.22;
}

.te-row--chrom .te-cell {
  min-width: 0;
  flex: 1 1 0;
}

.te-cell--enh {
  min-width: 0;
}

.te-cell--enh .te-note,
.te-cell--enh .te-deg {
  white-space: nowrap;
}

.te-cell--dim .te-note,
.te-cell--dim .te-deg {
  opacity: 0.22;
}

.te-example--single .te-evo-cols,
.te-example--single .te-evo-row {
  grid-template-columns: 0.35fr 0.7fr 1fr 1.3fr 2.4fr;
}

.te-example--single .te-evo-cols > *:not(:first-child),
.te-example--single .te-evo-row > .te-row:not(:first-child) {
  border-left: 1px solid rgba(var(--w),0.08);
  padding-left: 0.6rem;
}

.te-caged-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0 0.75rem;
}
.te-caged-divider::before,
.te-caged-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(var(--w),0.07);
}
.te-caged-divider span {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(var(--w),0.28);
}

.te-slash {
  opacity: 0.3;
  margin: 0 0.05em;
}

.te-legend-row {
  margin-top: 0.75rem;
  padding-top: 0.5rem;
  border-top: 1px solid rgba(var(--w),0.06);
  font-size: 0.72rem;
  color: var(--text-secondary);
  opacity: 0.6;
}

.te-leg-item.te-root  { color: var(--accent); }
.te-leg-item.te-chord { color: var(--text); }
.te-leg-item.te-add   { color: #9db89a; }

.te-closing {
  margin: 1rem 0 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--text-secondary);
  opacity: 0.7;
  font-style: italic;
}

.theory-block {
  background: rgba(var(--w),0.03);
  border: 1px solid rgba(var(--w),0.07);
  border-radius: var(--radius-small);
  padding: 1.1rem 1.25rem;
}

.theory-block-title {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.3rem;
}

.theory-block p {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.65;
  color: var(--text-secondary);
}

.theory-bottom-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.theory-caged-chord-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  align-items: stretch;
  margin-bottom: 1.5rem;
}

#tool-scales {
  margin-bottom: 0;
}

.caged-chord-panel {
  display: flex;
  flex-direction: column;
}

.caged-chord-panel .chord-diagram-wrap {
  display: none;
}

.caged-chord-panel #chordCaged {
  margin-top: auto;
}

.caged-chord-panel .chord-caged-wrap {
  border-top: none;
  margin-top: 0;
  padding-top: 0;
}

@media (max-width: 780px) {
  .theory-top-2col,
  .theory-bottom-2col,
  .theory-caged-chord-2col,
  .theory-blocks {
    grid-template-columns: 1fr;
  }

  .prog-roots-mobile { display: flex; }

  /* Chord Visualizer: root + quality stacked on mobile */
  .chord-root-cat-row {
    grid-template-columns: 1fr;
    gap: 0.75rem;
  }

  /* Chord diagram: stack SVG above info on mobile */
  .chord-diagram-inner {
    flex-direction: column;
  }

  .chord-info-side {
    padding-left: 0;
    border-left: none;
    border-top: 1px solid rgba(var(--w),0.07);
    padding-top: 0.75rem;
  }

  /* CAGED quality row: wrap on very small screens */
  .caged-type-row {
    flex-wrap: wrap;
  }
}

.alpha-sys-item {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: rgba(var(--w),0.04);
  border-radius: 6px;
  padding: 7px 8px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background .15s, border-color .15s;
}

.alpha-sys-item:hover {
  background: rgba(var(--w),0.08);
}

.alpha-sys-item.active {
  background: rgba(216,168,79,0.10);
  border-color: rgba(216,168,79,0.4);
}

.alpha-sys-name {
  font-size: 12px;
  font-weight: 700;
  color: #d8a84f;
  letter-spacing: 0.02em;
}

.alpha-sys-desc {
  display: none;
}

.alpha-sys-notes {
  display: block;
  min-width: 0;
  font-size: 12px;
  font-weight: 500;
  color: rgba(var(--w),0.5);
  font-family: 'Inter', sans-serif;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: 0.01em;
  line-height: 1.4;
}

.alpha-steps {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0;
  margin-top: 10px;
  flex-wrap: nowrap;
  padding: 2px 0;
  min-width: 0;
}

.alpha-snote {
  font-size: 11px;
  font-weight: 600;
  color: #d8a84f;
  padding: 2px 4px;
  border-radius: 4px;
  background: rgba(216,168,79,0.1);
  white-space: nowrap;
  flex-shrink: 1;
  min-width: 0;
  text-align: center;
}

.alpha-sep {
  font-size: 10px;
  font-weight: 700;
  padding: 2px 4px;
  flex-shrink: 0;
}

.alpha-sep--w {
  color: rgba(var(--w),0.3);
}

.alpha-sep--h {
  color: rgba(130,180,255,0.6);
  background: rgba(130,180,255,0.08);
  border-radius: 3px;
}

/* ==========================================================
   CHORD BUILDER
   ========================================================== */

.chord-builder-card {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.chord-builder-card .alpha-desc {
  margin-bottom: 1rem;
}

.chord-builder-card .cof-card-header {
  margin-bottom: 1.25rem;
}

.chord-root-cat-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1.5rem;
  align-items: start;
}

.chord-root-cat-row .sv-cats {
  flex-wrap: wrap;
  margin-top: 0.3rem;
  margin-bottom: 0;
}

.chord-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-right: 2.2rem;
}

.chord-body {
  display: flex;
  gap: 2.5rem;
  align-items: flex-start;
}

.chord-selector {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  flex: 1;
  min-width: 0;
}

.chord-roots {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
}

.chord-qualities {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.chord-quality-group {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.chord-quality-title {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  opacity: 0.55;
  font-weight: 600;
  padding-left: 0.1rem;
}

.chord-quality-title--toggle {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
  user-select: none;
  opacity: 0.75;
  transition: opacity 0.15s;
}

.chord-quality-title--toggle:hover { opacity: 1; }

.chord-quality-icon {
  font-size: 0.65rem;
  transition: transform 0.2s ease;
  display: inline-block;
}

.chord-quality-group--collapsed .chord-quality-row { display: none; }
.chord-quality-group--collapsed .chord-quality-icon { transform: rotate(-90deg); }

.chord-quality-row {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
}

.chord-btn {
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.08);
  color: var(--text-muted);
  padding: 0.35rem 0.7rem;
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  font-size: 0.82rem;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
  white-space: nowrap;
}

.chord-btn:hover {
  background: rgba(var(--w),0.08);
  color: var(--text-main);
}

.chord-btn.active {
  background: rgba(216,168,79,0.15);
  border-color: rgba(216,168,79,0.4);
  color: var(--accent);
}

.chord-diagram-wrap {
  margin-top: 0.5rem;
}

.chord-diagram-inner {
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}

.chord-voicing--primary {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 20%;
  flex-shrink: 0;
  gap: 0.7rem;
  opacity: 1 !important;
  align-self: flex-end;
}

.chord-voicing--primary svg {
  width: 100%;
  height: auto;
}



.chord-info-side {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-left: 1.25rem;
  border-left: 1px solid rgba(var(--w),0.07);
  align-self: flex-end;
}

.chord-desc {
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.4;
  margin-bottom: 0.85rem;
}

.chord-info-label {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  opacity: 0.4;
  font-weight: 600;
}

.chord-info-value {
  font-size: 0.85rem;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  margin-top: 0.2rem;
}

.chord-variants {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding-left: 1rem;
  border-left: 1px solid rgba(var(--w),0.07);
  padding-top: 3.2rem;
}

.chord-variants-label {
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  opacity: 0.35;
  font-weight: 600;
}

.chord-variants-row {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
}

.chord-voicing {
  flex-shrink: 0;
  width: 120px;
  opacity: 0.4;
  transition: opacity 0.15s;
}

.chord-voicing:hover { opacity: 0.7; }

.chord-voicing svg,
.chord-voicing--primary svg {
  width: 100%;
  height: auto;
}

.chord-unavailable {
  color: var(--text-muted);
  font-size: 0.875rem;
  text-align: center;
  padding: 2rem 0;
  margin: 0;
}

@media (max-width: 700px) {
  .chord-variants { display: none; }
}

/* â”€â”€ CAGED row in chord builder â”€â”€ */
.chord-caged-wrap {
  display: block;
  margin-top: 0;
}

.chord-caged-info {
  flex-shrink: 0;
  width: fit-content;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid rgba(var(--w),0.08);
  border-radius: 6px;
  background: rgba(var(--w),0.03);
  align-self: stretch;
}

.chord-caged-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 1rem;
}

.chord-caged-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  /* Matches .caged-island's horizontal padding so a chord diagram renders at
     the same size as a CAGED island â€” same viewBox, same available width. */
  padding: 0 0.75rem;
}

.chord-caged-item svg {
  width: 100%;
  height: auto;
}

.chord-caged-item--empty {
  opacity: 0.15;
  pointer-events: none;
}

.chord-caged-item--active .chord-caged-letter {
  color: var(--accent);
}

.caged-shapes { width: 100%; }

.chord-caged-letter {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-secondary);
  letter-spacing: 0.05em;
}

/* Voicing label above each chord diagram in the Chords tab (Open / Snaar 6) */
.chord-voicing-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
}

/* Chord-name label on Beginner grid cards (Open/Power/Barre chords) â€” an
   actual chord name like "Am" or "F7", so it must NOT be uppercased (that
   would visually turn the lowercase "m" into a capital "M"). */
.chord-grid-label {
  font-size: var(--type-item);
  font-weight: 700;
  color: var(--text-main);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Shown when a key has no shapes in the selected chord family */
.chord-empty-note {
  font-size: var(--type-small);
  color: var(--text-muted);
  margin: 1rem 0 0;
  max-width: 46ch;
}

/* Beginner grid cards (Open/Power/Barre): play button under each diagram. */
.chord-card-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  margin-top: 0.1rem;
}

/* The "I know this one" toggle on each Beginner chord card, restored with
   the rest of the frozen Beginner baseline. State lives in localStorage
   (portalLearnedChords, theory.js) so it survives a reload. */
.chord-learned-btn {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 0.75rem;
  line-height: 1;
  color: var(--text-muted);
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.14);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.1s ease;
}
.chord-learned-btn:hover  { border-color: rgba(var(--w),0.3); }
.chord-learned-btn:active { transform: scale(0.9); }
.chord-learned-btn.active {
  color: #1a1a1a;
  background: var(--accent);
  border-color: var(--accent);
}

.chord-caged-item--learned .chord-grid-label { color: var(--accent); }

@media (max-width: 700px) {
  #theoryTabs,
  .platform-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  /* At some narrow widths only a one-pixel fragment of the next Theory
     label reaches the clipping edge, which looks like a stray blue rule.
     Clip that final edge pixel without changing scrolling or tab widths. */
  #theoryTabs {
    clip-path: inset(0 1px 0 0);
  }

  #theoryTabs::-webkit-scrollbar,
  .platform-tabs::-webkit-scrollbar {
    display: none;
  }

  #theoryTabs > *,
  .platform-tabs > * {
    flex: 0 0 auto;
    white-space: nowrap;
  }

  /* Note markers and fret dots stay at their normal (desktop) size on
     phones too — people can always pinch-zoom, and enlarging them made
     the diagrams look oversized/cramped. */

  :is(.alpha-piano-wrap, .theory-lesson-widget) > svg:not(.theory-piano)[viewBox="0 0 500 128"] text[font-size="14"] { font-size: 17px; }
  :is(.alpha-piano-wrap, .theory-lesson-widget) > svg:not(.theory-piano)[viewBox="0 0 500 128"] text[font-size="12"] { font-size: 15px; }
  :is(.alpha-piano-wrap, .theory-lesson-widget) > svg:not(.theory-piano)[viewBox="0 0 500 128"] text[font-size="11"] { font-size: 13px; }
  :is(.alpha-piano-wrap, .theory-lesson-widget) > svg:not(.theory-piano)[viewBox="0 0 500 128"] text[font-size="10"] { font-size: 12px; }
  :is(.alpha-piano-wrap, .theory-lesson-widget) > svg:not(.theory-piano)[viewBox="0 0 500 128"] text[font-size="9.5"] { font-size: 11px; }
  :is(.alpha-piano-wrap, .theory-lesson-widget) > svg:not(.theory-piano)[viewBox="0 0 500 128"] text[font-size="9"] { font-size: 11px; }
  :is(.alpha-piano-wrap, .theory-lesson-widget) > svg:not(.theory-piano)[viewBox="0 0 500 128"] text[font-size="8.5"] { font-size: 10.5px; }

  .theory-mobile-contents {
    display: block;
    margin: -0.4rem 0 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-small);
    background: rgba(var(--w), 0.025);
  }

  .theory-mobile-contents summary {
    padding: 0.75rem 0.9rem;
    color: var(--text-main);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
  }

  .theory-mobile-contents nav {
    display: flex;
    flex-direction: column;
    padding: 0 0.9rem 0.8rem;
  }

  .theory-mobile-contents a {
    padding: 0.55rem 0;
    border-top: 1px solid rgba(var(--w), 0.07);
    color: var(--text-muted);
    font-size: 0.82rem;
    text-decoration: none;
  }

  .theory-lesson-section:not([hidden]) ~ .theory-lesson-section:not([hidden]) {
    padding-top: 2rem;
    border-top: 1px solid var(--border);
  }

  /* Notes is a full-neck reference, not a single playable shape. In
     portrait the orientation system rotates it vertical; in landscape it
     stays horizontal and simply fits the width (viewBox + width:100%),
     which is why there is no overflow/scroll rule here any more — a
     fretboard diagram must never scroll horizontally. */
  #notesMap:not(.fb-vertical) svg {
    width: 100%;
    max-width: 100%;
    height: auto;
    transform: none;
  }

  /* Every Fretboard tab/chip level stays on one swipeable row. Centering
     the active chip from fretboard.js leaves neighboring choices visible. */
  .fretboard-page :is(#fbTabs, #globalRoots, #fbxCats, #fbxTypes, #fbxPositions, #chordCats, #chordTypes, #arpCats, #arpItems, #intervalsPositions, #cagedIntPositions, #notesFretToggle) {
    display: flex;
    flex-wrap: nowrap !important;
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .fretboard-page :is(#fbTabs, #globalRoots, #fbxCats, #fbxTypes, #fbxPositions, #chordCats, #chordTypes, #arpCats, #arpItems, #intervalsPositions, #cagedIntPositions, #notesFretToggle)::-webkit-scrollbar {
    display: none;
  }

  .fretboard-page :is(#fbTabs, #globalRoots, #fbxCats, #fbxTypes, #fbxPositions, #chordCats, #chordTypes, #arpCats, #arpItems, #intervalsPositions, #cagedIntPositions, #notesFretToggle) > * {
    flex: 0 0 auto;
    scroll-snap-align: center;
    white-space: nowrap;
  }

  .fretboard-page #fbTabs .fb-tab[data-tab="scales"] {
    max-width: 7.5rem;
    white-space: normal;
    line-height: 1.15;
    text-align: center;
  }

  .fretboard-page #notesFretToggle {
    width: max-content;
    max-width: 100%;
  }

  /* Keep the fingering key readable beside the icon-only Play control.
     It remains contextual: [hidden] still removes it outside Fingering. */
  .fretboard-page .fretboard-sequence-actions .fretboard-fingering-legend:not([hidden]) {
    display: block;
    flex: 0 0 100%;
    min-width: 0;
    text-align: center;
  }

  .fretboard-page .fretboard-legend-second {
    display: block;
  }

  .fretboard-page .fretboard-legend-separator {
    display: none;
  }

  .fretboard-page .fretboard-sequence-actions {
    justify-content: center;
  }
}

/* Legend explaining the 1-2-3-4 finger numbers shown in Beginner diagrams */
.chord-finger-legend {
  font-size: 0.85rem;
  color: var(--text-muted);
  margin: 0.3rem 0;
  text-align: center;
}

/* In Fretboard these legends continue the explanatory copy above them. */
.fretboard-fingering-legend {
  text-align: left;
  margin: 0;
}

.fretboard-page .fretboard-legend-second {
  display: block;
}

.fretboard-page .fretboard-legend-separator {
  display: none;
}

/* Keep a new row of chord names visually separate from the play buttons
   belonging to the row above it. */
#chordDiagram .chord-caged-row {
  column-gap: 1rem;
  row-gap: 2.25rem;
}

.fretboard-sequence-actions {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  margin: 0.8rem 0 0.9rem;
}

.fretboard-sequence-controls {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* THE PLAYBACK STATUS LINE — Notes and the Starter neck.

   TRANSIENT, NOT A CAPTION. It says what is sounding WHILE it sounds and
   nothing at all at rest: the neck shows 15 frets and the exercise runs
   to the octave (NOTES_PLAY_MAX_FRET, fretboard.js), and this is where
   that is stated — at the moment it is true, not as standing copy under
   a diagram that has to carry it forever.

   IT IS THE FIRST CHILD of .fretboard-sequence-actions, so it sits
   between the neck and the transport buttons and never below them.
   flex-basis 100% takes the whole first line of that wrapping row; the
   row's own centring (.fretboard-page .fretboard-sequence-actions) then
   centres it over the buttons.

   THE HEIGHT IS ALWAYS RESERVED. min-height holds one line whether the
   element has text or not, so starting and stopping playback cannot move
   the buttons or the neck by a pixel. That is also why the JS sets and
   clears textContent rather than toggling display or [hidden]: an empty
   role="status" announces nothing and shows nothing, while the box it
   occupies never changes.

   No pill, no card, no border, no background, no animation — small
   secondary type, and nothing else. Never mirrored: the left-handed rule
   flips .sv-board-wrap svg only, and this <p> lives outside the SVG. */
.fretboard-sequence-status {
  flex: 0 0 100%;
  margin: 0;
  min-height: 1.35em;
  text-align: center;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--text-muted);
  pointer-events: none;
}

/* Spacing and the .tool-nav reset live in the FRETBOARD CONTROL SYSTEM
   at the end of this file, where the whole vertical rhythm is stated
   together. Only the empty-stepper guard is left here. */

/* An empty stepper (Beginner, where JS never fills it) would still render
   the wrapper's own padding as a stray blank strip. */
#fbxPositionNav:has(#fbxPositions:empty),
#arpPositionNav:has(#arpPositions:empty),
#intervalsPositionNav:has(#intervalsPositions:empty),
#cagedIntPositionNav:has(#cagedIntPositions:empty) {
  display: none;
}

/* ICON-ONLY at every width (desktop, tablet and phone alike) — Play,
   Repeat and Slow down are a transport row, and three identical square
   pills read as one control group far better than three differently
   sized text pills. Used to be text+icon above 480px and icon-only
   below it, which meant the row changed shape mid-page-width.

   One fixed square for all three, so the icons line up on a single
   optical axis and toggling Repeat/Slow down (which only changes
   colour, never size) can't shift the row horizontally. 2.5rem = 40px
   is the size the phone layout already used, kept unchanged. */
/* Theory's lesson diagrams get the SAME transport, by joining these
   selector lists rather than by restating the look: one accent-ringed
   40px circle with a ▶ in it, beside the diagram it sounds. It is a
   different class because the behaviour differs — fretboard.js binds
   every .fretboard-sequence-btn to its own neck sequencer, and a worked
   example in Theory has no neck to sequence and no Repeat/Slow down. */
.theory-play-btn,
.fretboard-sequence-btn,
.fretboard-sequence-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  min-width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  gap: 0;
  border: 1px solid rgba(216, 168, 79, 0.38);
  border-radius: var(--radius-pill);
  background: rgba(216, 168, 79, 0.08);
  color: var(--text-main);
  font: inherit;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

/* The label text stays in the DOM rather than being deleted: it is what
   fretboard.js swaps between "Play" and "Stop", it is what [data-i18n]
   translates, and it is the accessible name a screen reader falls back
   to if the aria-label ever went missing. Only its pixels go away —
   same .visually-hidden geometry used elsewhere in this file. */
.fretboard-sequence-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.theory-play-btn:hover,
.theory-play-btn:focus-visible,
.theory-play-btn.is-playing,
.fretboard-sequence-btn:hover,
.fretboard-sequence-btn:focus-visible,
.fretboard-sequence-btn.is-playing,
.fretboard-sequence-option:hover,
.fretboard-sequence-option:focus-visible,
.fretboard-sequence-option.is-active {
  border-color: var(--accent);
  background: rgba(216, 168, 79, 0.16);
  outline: none;
}

/* Keyboard focus needs to be tellable APART from hover now that the label
   is gone — the shared rule above gives both the same fill, which was
   readable while the text spelled the button out and isn't any more.
   Same ring the page's own .fb-tab uses, so Tab-ing through the page
   keeps one focus language. */
.theory-play-btn:focus-visible,
.fretboard-sequence-btn:focus-visible,
.fretboard-sequence-option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* PRESSED state for Repeat / Slow down. A slightly warmer fill was enough
   while "Repeat" stood next to the glyph; on a bare icon the on/off
   reading has to survive on colour alone, so an active toggle takes the
   full accent treatment — solid border, deeper fill, accent-coloured
   glyph. aria-pressed carries the same fact for screen readers. */
.fretboard-sequence-option.is-active {
  background: rgba(216, 168, 79, 0.28);
  border-color: var(--accent);
  color: var(--accent);
}

/* The transport sits under its diagram, centred on the same axis as the
   diagram above it — the widget column is a centred flex column. */
.theory-play-row {
  display: flex;
  justify-content: center;
  margin-top: 0.85rem;
}

/* Lighter at rest than Fretboard's, on purpose. There the button stands
   in a transport row beside Repeat and Slow down, where the group needs
   weight to read as one control. Here a single play sits under a worked
   example — and, at the chord diagrams, three of them in a row — so it
   stays quiet until you reach for it: no fill, a hairline ring, a
   smaller glyph. Hover and focus bring back the shared accent treatment
   above. The 40px box is untouched: it is already the smallest touch
   target the portal uses, and "finer" must not mean "harder to hit". */
.theory-play-btn {
  background: transparent;
  border-color: rgba(216, 168, 79, 0.22);
}
.theory-play-btn .fretboard-sequence-icon {
  font-size: 0.75rem;
}
.chord-caged-item .theory-play-btn {
  display: flex;
  margin: 0.5rem auto 0;
}

.fretboard-sequence-icon {
  color: var(--accent);
  line-height: 1;
}

/* Wraps the 3 chord-diagram legend lines (fingers / open / mute) in Reading
   Music. Stacked (each .chord-finger-legend's own margin above/below)
   on every viewport — mobile default, unchanged; desktop used to go
   side by side but Jappe wants it stacked there too now, centered
   within the widget column. */
.chord-finger-legend-row {
  text-align: center;
}

/* The O/X pair sits side by side on one row — only the fingers legend
   above it gets its own full-width line. */
.chord-finger-legend-pair {
  display: flex;
  justify-content: center;
  gap: 1.5rem;
}

.chord-finger-legend-pair .chord-finger-legend {
  margin: 0.3rem 0;
}

/* Switch-practice exercise (two chords side by side + prev/next) under the
   Beginner Open grid */
.chord-switch-practice {
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(var(--w),0.08);
}

.chord-switch-header {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.25rem;
}

.chord-switch-desc {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-bottom: 1rem;
}

.chord-switch-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
}

.chord-switch-row .chord-caged-item { max-width: 160px; }

.chord-switch-arrow {
  font-size: 1.5rem;
  color: var(--accent);
  flex-shrink: 0;
}

.chord-switch-nav {
  display: flex;
  justify-content: center;
  gap: 0.6rem;
  margin-top: 1rem;
}

@media (max-width: 560px) {
  .chord-switch-row { gap: 0.75rem; }
  .chord-switch-row .chord-caged-item { max-width: 120px; }
}

/* Section heading separating the voicing row from the CAGED row */
.chord-section-title {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: 1.25rem 0 0.6rem;
}

.caged-deg-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.4rem;
}

/* â”€â”€ CAGED page â”€â”€ */
.caged-global-bar {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
}

.caged-bar-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.caged-deg-row {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.75rem;
}

/* .caged-density-bar is gone: the Advanced/Expert CAGED controls sit in
   the shared .fb-controls group now, exactly like every other tab's. */

.caged-density-info {
  font-size: 0.8rem;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  min-height: 1.2em;
}

.caged-formula-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1rem;
  margin-bottom: 0.25rem;
}
.caged-formula-lbl {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.caged-formula {
  font-size: 0.85rem;
  color: var(--text-main);
  letter-spacing: 0.08em;
}

/* ==========================================================
   THE FIVE CAGED SHAPES — Fretboard → CAGED at Beginner
   ----------------------------------------------------------
   C · A · G · E · D IS ONE SERIES, AND IT IS READ ACROSS.
   The whole lesson of this view is that the same chord appears in five
   places up one neck, in that order. A grid that reflowed them to 3 + 2
   (and to 2 + 2 + 1, and to five stacked rows) broke the reading: the
   fourth shape sat under the first, so "further up the neck" became
   "further down the page", and the order stopped meaning anything.

   So: ONE ROW, NEVER WRAPPED. When the five no longer fit at a readable
   size, the ROW scrolls — not the page, and not the diagrams by getting
   smaller.

   THE WIDTH IS THE ROW'S OWN ARITHMETIC, NOT A LIST OF BREAKPOINTS.
   Five shapes and four equal gaps share the row, so one shape is
   (100% - 4 gaps) / 5 — clamped between a readable floor and a sane
   ceiling. Nothing here names a device width; widen the window and the
   diagrams grow with it until they hit the cap, narrow it and they stop
   at the floor and the row starts scrolling instead.

     --caged-shape-min  176px  the floor. The window each shape draws
                               into is 180 units wide, so this is the
                               width at which its 11.7-unit note labels
                               reach ~8.3px of cap height  about 90% of
                               the Notes tab's 9.3px (fbMarkerLabelSize,
                               fretboard.js). Below it the five stop
                               shrinking and the row scrolls instead.

     --caged-shape-max  258px  the ceiling. Past this a chord diagram is
                               a poster, not a chord  the same judgement
                               #fb-panel-chords .chord-caged-row makes
                               with its own 300px cap. Both numbers are
                               the earlier 186/272 rescaled by 180/190,
                               the crop below, so the DRAWN board keeps
                               the size it already had.

   EVERYTHING SCALES TOGETHER. The SVG is width:100% over a fixed
   viewBox, so board, markers, letters, X/O, barre and fret number all
   grow in step by construction. The shape LETTER above it is HTML and
   would not have, so .caged-island is an inline-size container and the
   letter is sized in cqw — one shape, one scale, heading included.

   BORDERLESS. The five used to be five bordered, filled cards, which
   made a row of chrome compete with the diagrams inside it and spent
   24px per shape on padding that the diagrams needed. The diagrams are
   the content; nothing else is drawn.

   Scoped to .caged-islands, which is emitted in exactly one place
   (#cagedBegIslands in fretboard.html). The Chords tab's own CAGED grid
   is .chord-caged-row and keeps its column rules untouched — it answers
   a different question (compare voicings) and is read one at a time.
   ========================================================== */
.caged-islands {
  /* THE SAME CONTENT EDGES AS A NECK DIAGRAM. This row is not special:
     it takes the page's own content column, exactly like .sv-board-wrap
     does on Notes, Arpeggio, Pentatonic and the rest. No margin of its
     own, no breakout, no second width rule — the C shape's left edge and
     the D shape's right edge land on the same two verticals a full neck
     does, because they are the same two verticals.

     space-between (with `gap` as the FLOOR, not the spacing) is what
     spends the leftover: five groups pinned edge to edge, the remainder
     divided evenly between them. When the row overflows there is no
     remainder, space-between degrades to flex-start, and the row scrolls
     from C with the gap holding the groups apart. */
  --caged-shape-min: 176px;
  --caged-shape-max: 258px;
  /* The FLOOR between two groups, not the final spacing — space-between
     adds the row's leftover on top of it. Also the exact spacing once the
     row overflows and starts scrolling, where there is no leftover. */
  --caged-shape-gap: 8px;
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: var(--caged-shape-gap);
  overflow-x: auto;
  overflow-y: hidden;
  justify-content: space-between;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* NO max-width HERE. It used to read `100%`, which is measured against
     the containing block and so re-clamped a box this row no longer sets
     itself; overflow-x already keeps the page from scrolling. */
}

.caged-islands::-webkit-scrollbar { display: none; }

.caged-island {
  flex-grow: 0;
  flex-shrink: 0;
  flex-basis: clamp(
    var(--caged-shape-min),
    (100% - 4 * var(--caged-shape-gap)) / 5,
    var(--caged-shape-max)
  );
  /* So the shape letter above the board can be sized against THIS
     shape's width rather than the viewport's. */
  container-type: inline-size;
  /* start, not center. A scrolling row has to REST on C: with `center`
     the browser snapped the nearest snap point to the middle of the row
     on first paint, which left the row already scrolled 86px in and the
     C shape half off the left edge. `start` makes the row's own origin a
     snap point, so it opens on C and each drag lands a whole shape
     against the left edge, in order. */
  scroll-snap-align: start;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

/* A LABEL, NOT A HEADLINE — and deliberately NOT on the cqw scale any
   more. Tied to the shape's width it grew with the diagrams and ended up
   at 30px, competing with the boards it was supposed to introduce. A
   fixed 1.3rem sits just above the Chords tab's own .chord-grid-label
   (0.95rem) — enough weight to head a column, quiet enough that the
   notes stay the loudest thing in the row. Centred over its own diagram
   by .caged-island-header, which is width:100% inside the shape. */
.caged-islands .caged-island-letter {
  font-size: 1.3rem;
  line-height: 1.2;
}

.caged-island-header {
  width: 100%;
  display: flex;
  align-items: baseline;
  justify-content: center;
}

.caged-island-letter {
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--text-primary);
  line-height: 1;
}

.caged-formula-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.caged-show-pill .sv-cat-btn {
  padding: 0.25rem 0.6rem;
  font-size: 0.78rem;
}

#tool-scales,
#tool-chords {
  padding: 0;
  background: none;
  border: none;
}

.sv-info {
  margin-top: 0.75rem;
  margin-bottom: 0.5rem;
}

.caged-deg-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.caged-island-neck-wrap {
  display: flex;
  align-items: flex-start;
  width: 100%;
  gap: 0.2rem;
}

.caged-island-pos {
  font-size: 0.6rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  width: 30px;
  text-align: right;
  padding-top: 26%;
  flex-shrink: 0;
}

.caged-island-neck {
  flex: 1;
  min-width: 0;
  /* The frame the fret caption is positioned against — it is the box the
     SVG fills, so a percentage here is a percentage of the drawing. */
  position: relative;
}

.caged-island-neck svg {
  width: 100%;
  height: auto;
  display: block;
}

/* THE FRET CAPTION — "3fr", "5fr", "10fr" — as HTML, not as SVG text.

   WHY IT IS NOT IN THE DRAWING. Everything inside the SVG scales with the
   shape, which is right for a board, its markers and its note names and
   wrong for this: one user-unit size read 14.4px at 1600 and 10.8px at
   390. Lifted out, it holds ~12.5px at every width — comfortably inside
   the 12-14px this label wants, and quiet against the notes and the
   title, which are the two things that should carry the diagram.

   WHERE IT SITS. Not a guess: the board's first string is at SL = 38 in a
   window that starts at ISLAND_BOX.x = 10 and is 180 wide (caged.js), so
   the board's left edge is (38-10)/180 = 15.5556% of the shape. The
   caption's RIGHT edge is pinned 7px inside that, which is the 6-10px a
   chord chart leaves, and translate(-100%) hangs its width to the left.
   Its `top` is written per shape by cropIslandBox(), from the fret the
   range actually starts on.

   OVERHANG IS BOUNDED AND HARMLESS. On the narrowest shapes a four-glyph
   caption reaches ~5px past its own box into the 8px gap. It cannot be
   clipped at the row's left edge, because the first shape is always the
   LOWEST on the neck — at most "4fr", never wide enough to reach past
   the box, and usually an open shape with no caption at all. */
.caged-island-fret {
  position: absolute;
  left: calc(15.5556% - 7px);
  transform: translate(-100%, -50%);
  font-size: 0.78rem;
  line-height: 1;
  font-weight: 500;
  color: rgba(var(--w), 0.62);
  white-space: nowrap;
  pointer-events: none;
}

/* The left-handed mirror flips the SVG, so the low-fret edge of the board
   moves to the right of the box and the caption follows it. The caption
   itself is never mirrored — it is HTML, outside the flipped SVG. */
[data-handed="left"] .caged-island-fret {
  left: auto;
  right: calc(15.5556% - 7px);
  transform: translate(100%, -50%);
}


.chord-voicing-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.chord-voicing-diagram svg {
  width: 160px;
  height: auto;
  display: block;
}

.chord-voicing-nav {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.chord-voicing-btn {
  background: none;
  border: 1px solid rgba(var(--w),0.15);
  border-radius: 4px;
  color: var(--text-main);
  font-size: 1.3rem;
  line-height: 1;
  width: 2rem;
  height: 2rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s;
}
.chord-voicing-btn:hover:not(:disabled) {
  border-color: rgba(var(--w),0.4);
}
.chord-voicing-btn:disabled {
  opacity: 0.25;
  cursor: default;
}

.chord-voicing-counter {
  font-size: 0.78rem;
  color: var(--text-muted);
  min-width: 90px;
  text-align: center;
}



/* ==========================================================
   RHYTHM TREE
   ========================================================== */

.theory-rhythm-col {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-bottom: 1.5rem;
  gap: 1.5rem;
}

@media (max-width: 700px) {
  .theory-rhythm-col { grid-template-columns: 1fr; }
}

@font-face {
  font-family: "Bravura Rhythm";
  src: url("../fonts/bravura/Bravura.woff2") format("woff2");
  font-display: block;
}

/* One measured score layout at every level; only its rows change. */
.theory-lesson-widget:has(> .rhy-tree) { min-width: 0; }

.rhy-tree {
  container-type: inline-size;
  width: 100%;
  min-width: 0;
  margin-top: 4px;
  color: var(--text-main);
}
.rhy-tree[hidden] { display: none; }
.rhy-meta {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: baseline;
  padding: 0 4px 12px;
  color: var(--text-muted);
  font-size: 0.75rem;
}
.rhy-meta > :first-child { color: var(--text-main); font-weight: 600; }
.rhy-beat-ruler {
  display:grid;
  grid-template-columns:180px minmax(0,1fr);
  gap:12px;
  align-items:end;
  width:100%;
  padding:0 4px;
}
.rhy-beat-ruler-track { display:block; min-width:0; width:100%; }
.rhy-shared-ruler { display:block; width:100%; height:22px; overflow:visible; }
.rhy-shared-ruler .rhy-count.is-current { fill:var(--accent); font-weight:700; }
.rhy-row {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  width: 100%;
  min-width: 0;
  padding: 10px 4px;
  border: 0;
  border-top: 1px solid rgba(var(--w), 0.1);
  border-radius: 0;
  background: transparent;
  color: var(--text-main);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.rhy-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.rhy-label { display: flex; align-items: center; gap: 10px; min-width: 0; }
.rhy-name { font-size: var(--type-small); font-weight: 600; line-height: 1.4; }
.rhy-play-icon {
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(var(--w), 0.23);
  border-radius: 50%;
  color: var(--text-muted);
}
.rhy-play-icon svg { width: 18px; height: 18px; fill: currentColor; }
.rhy-icon-stop, .rhy-playing .rhy-icon-start { display: none; }
.rhy-playing .rhy-icon-stop { display: block; }
.rhy-row:hover .rhy-play-icon, .rhy-playing .rhy-play-icon {
  color: var(--accent);
  border-color: var(--accent);
}
.rhy-playing .rhy-name { color: var(--accent); }
.rhy-notes { display: block; min-width: 0; width: 100%; }
.rhy-score { display: block; width: 100%; height: 84px; overflow: visible; }
.rhy-symbol { font-family: "Bravura Rhythm"; font-size: 36px; fill: currentColor; }
.rhy-stem, .rhy-ledger { stroke: currentColor; stroke-width: 1.2; }
.rhy-dot { fill: currentColor; }
.rhy-barline { stroke: rgba(var(--w), 0.25); stroke-width: 1; }
.rhy-beam { stroke: currentColor; stroke-width: 3; stroke-linecap: butt; }
.rhy-count { font: 12px var(--font-family, system-ui, sans-serif); fill: var(--text-muted); text-anchor: middle; }
.rhy-count--beat { fill: var(--text-main); font-weight: 600; }
.rhy-event.is-current { color: var(--accent); }
.rhy-count.is-current { fill: var(--accent); font-weight: 700; }
.rhy-tuplet { font: italic 11px Georgia, serif; fill: currentColor; text-anchor: middle; }
.rhy-tuplet-bracket { fill: none; stroke: currentColor; stroke-width: 1; }
.rhy-playhead { opacity: 0; stroke: var(--accent); stroke-width: 1; pointer-events: none; }
.rhy-playing .rhy-playhead { opacity: 0.5; }
.rhy-hint { margin: 12px 4px 0; color: var(--text-muted); font-size: 0.75rem; line-height: 1.65; }

/* Use the widget's width, including narrow desktop split columns. */
@container (max-width: 540px) {
  .rhy-beat-ruler { grid-template-columns:minmax(0,1fr); }
  .rhy-beat-ruler > :first-child { display:none; }
  .rhy-row { grid-template-columns: minmax(0, 1fr); gap: 4px; padding: 10px 4px 6px; }
  .rhy-label { flex-direction: row-reverse; justify-content: space-between; }
}
@media (prefers-reduced-motion: reduce) {
  .rhy-playing .rhy-playhead { display: none; }
}

/* Preserved Dutch strumming presentation, pending approved translation. */
.rhy-strum-comparison .rhy-row,
.rhy-strum-comparison .rhy-beat-ruler { grid-template-columns:150px minmax(0,1fr); }
.rhy-strum-comparison .rhy-beat-ruler > :first-child { display:block; }
.rhy-strum-comparison .rhy-label { flex-direction:row; justify-content:flex-start; }
.rhy-label-copy { display:flex; flex-direction:column; gap:4px; min-width:0; }
.rhy-duration { font-size:0.75rem; line-height:1.4; color:var(--text-muted); }
.rhy-score--strum { height:102px; }
.rhy-strum-direction { font:600 12px var(--font-family,system-ui,sans-serif); fill:var(--accent); text-anchor:middle; }
.rhy-strum-comparison .rhy-row { padding-block:9px; }

/* Approved B: one playing pattern with a permanent pulse and hand guide.
   Uses the Rhythm clock/player, but deliberately not Note Values notation. */
.strum-study { width:100%; min-width:0; container-type:inline-size; }
.strum-heading { display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:8px; margin-bottom:22px; }
.strum-heading strong { font-size:var(--type-item); font-weight:600; }
.strum-heading > span { color:var(--text-muted); font-size:var(--type-small); white-space:nowrap; }
.strum-stage { display:grid; grid-template-columns:minmax(0,1fr) 130px; gap:20px; }
.strum-beats { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:6px; min-width:0; --strum-divisions:1; }
.strum-divisions-2 { --strum-divisions:2; }
.strum-divisions-3 { --strum-divisions:3; }
.strum-divisions-4 { --strum-divisions:4; }
.strum-beat { min-width:0; display:flex; flex-direction:column; text-align:center; }
.strum-beat-number { font-size:var(--type-item); font-weight:600; padding-bottom:12px; font-variant-numeric:tabular-nums; }
.strum-beat-face { display:grid; align-content:center; gap:8px; min-width:0; min-height:122px; padding:14px 3px; flex:1; border-radius:var(--radius-small); background:var(--bg-main); }
.strum-beat.has-hit .strum-beat-face { background:rgba(216,168,79,0.07); }
.strum-beat.is-current .strum-beat-number { color:var(--accent); }
.strum-beat.is-current .strum-beat-face { box-shadow:inset 0 -3px var(--accent); }
.strum-slots, .strum-counts { display:grid; grid-template-columns:repeat(var(--strum-divisions),minmax(0,1fr)); }
.strum-slot { display:flex; flex-direction:column; align-items:center; gap:4px; color:var(--accent); min-width:0; }
/* The arrow alone names the stroke (14 October 2026): larger where a beat holds one or two, the old size where it holds three or four. */
.strum-slot svg { width:30px; height:38px; }
:is(.strum-divisions-3, .strum-divisions-4) .strum-slot svg { width:22px; height:29px; }
.strum-slot.is-on { color:var(--text-main); }
.strum-counts { font-size:12px; color:var(--text-muted); }
.strum-counts .is-current { color:var(--accent); font-weight:600; }
/* Long translated sustain labels must fit a beat without widening its grid. */
.strum-ring { min-width:0; overflow-wrap:anywhere; color:var(--text-muted); font-size:12px; }
.strum-ring::before { content:''; display:block; height:2px; width:60%; margin:0 auto 14px; background:rgba(var(--w),0.2); }
.strum-cue { border-left:1px solid rgba(var(--w),0.14); padding-left:18px; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; }
.strum-cue-label { font-size:10px; letter-spacing:0.06em; text-transform:uppercase; color:var(--text-muted); }
.strum-cue-symbol { height:62px; display:grid; place-items:center; color:var(--accent); }
.strum-cue-symbol svg { width:36px; height:42px; }
.strum-cue.is-return .strum-cue-symbol { color:var(--text-muted); }
.strum-slot svg, .strum-cue svg { fill:none; stroke:currentColor; stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; }
.strum-cue-word { font-size:14px; font-weight:500; min-height:23px; }
.strum-cue-caption { font-size:12px; color:var(--text-muted); min-height:38px; }
.strum-focus .strum-tip { font-size:0.85rem; line-height:1.6; color:var(--text-muted); margin:20px 0 0; min-height:44px; }
.strum-actions { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-top:22px; }
.strum-play { display:inline-flex; align-items:center; gap:9px; border:1px solid var(--accent); border-radius:var(--radius-small); color:var(--bg-main); background:var(--accent); font:600 0.85rem var(--font-main); padding:10px 15px; min-height:44px; cursor:pointer; }
.strum-play::before { content:'▶'; font-size:12px; }
.strum-play[aria-pressed="true"]::before { content:'■'; }
.strum-play:hover { filter:brightness(1.1); }
.strum-status { color:var(--text-muted); font-size:0.75rem; }
.strum-legend { color:var(--text-muted); font-size:0.8rem; margin-top:12px; }
.strum-variations { display:flex; flex-wrap:wrap; gap:6px; margin:-4px 0 20px; }
.strum-variations button { background:transparent; color:var(--text-muted); border:1px solid rgba(var(--w),0.2); border-radius:var(--radius-small); min-height:44px; padding:7px 10px; font:500 0.75rem/1.4 var(--font-main); cursor:pointer; }
.strum-variations button[aria-pressed="true"] { color:var(--accent); border-color:var(--accent); background:rgba(216,168,79,0.07); }
.strum-play:focus-visible, .strum-variations button:focus-visible, .strum-focus:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
/* Discrete highlights only: no motion, including with reduced motion. */
@container (max-width: 600px) {
  .strum-stage { grid-template-columns:minmax(0,1fr); gap:16px; }
  .strum-cue { border-left:0; border-top:1px solid rgba(var(--w),0.14); padding:12px 0 0; display:grid; grid-template-columns:42px 1fr; text-align:left; column-gap:10px; justify-items:start; }
  .strum-cue-label { display:none; }
  .strum-cue-symbol { grid-row:1 / 3; height:44px; }
  .strum-cue-caption { min-height:0; }
}
@container (max-width: 420px) {
  .strum-beats:not(.strum-divisions-1) { grid-template-columns:repeat(2,minmax(0,1fr)); row-gap:14px; }
  .strum-beat-face { min-height:106px; }
}
@container (max-width: 420px) {
  .rhy-strum-comparison .rhy-row,
  .rhy-strum-comparison .rhy-beat-ruler { grid-template-columns:minmax(0,1fr); }
  .rhy-strum-comparison .rhy-beat-ruler > :first-child { display:none; }
  .rhy-strum-comparison .rhy-label { flex-direction:row-reverse; justify-content:space-between; }
}



/* Feel comparison: listening rows share the Rhythm family, with timing-scaled
   note positions instead of a staff. The existing lesson split is unchanged. */
.feel-comparison { width: 100%; min-width: 0; container-type: inline-size; }
.feel-comparison[hidden] { display: none; }
.feel-comparison .feel-subdivisions { max-width: 100%; flex-wrap: nowrap; margin-bottom: 0.75rem; }
.feel-comparison .feel-subdivisions button { flex: 0 1 auto; white-space: nowrap; }
.feel-row { grid-template-columns: 156px minmax(0, 1fr); gap: 14px; padding: 12px 4px; }
.feel-row .rhy-label { flex-direction: row; justify-content: flex-start; }
.feel-row-label { display: grid; gap: 3px; }
.feel-pair-label { font-size: 0.72rem; line-height: 1.5; color: var(--text-muted); }
.feel-score { display: block; min-width: 0; }
.feel-score svg { display: block; width: 100%; height: 78px; overflow: visible; }
.feel-beat-guide { stroke: rgba(var(--w), 0.12); stroke-width: 1; stroke-dasharray: 2 4; }
.feel-count { fill: var(--text-muted); font: 13px var(--font-family, system-ui, sans-serif); text-anchor: middle; }
.feel-note { fill: var(--text-main); color: var(--text-main); }
.feel-note--beat { fill: var(--accent); color: var(--accent); }
.feel-note.is-current { fill: var(--accent); color: var(--accent); }
.feel-note.is-current ellipse { stroke: var(--accent); stroke-width: 3; }
.feel-stem { stroke: currentColor; stroke-width: 1.4; }
.feel-beam { fill: none; stroke: var(--text-main); stroke-width: 3; }
.feel-comparison .feel-feedback { margin: 0.65rem 4px 0; color: var(--accent); font-size: 0.85rem; line-height: 1.6; }
@container (max-width: 420px) {
  .feel-row { grid-template-columns: minmax(0, 1fr); gap: 2px; padding: 10px 4px 8px; }
  .feel-row .rhy-label { flex-direction: row-reverse; justify-content: space-between; }
  .feel-row-label { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
  .feel-score svg { height: 72px; }
  .feel-count { font-size: 16px; }
}

/* =================================================
   LEVEL VIEW FILTER (theory page, non-beginner)
   ================================================= */
.level-view-filter {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  margin-bottom: 1.25rem;
  flex-wrap: wrap;
}
.lvf-legend {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-muted);
}
.lvf-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.875rem;
  color: rgba(var(--w),0.55);
  user-select: none;
  transition: color 0.15s;
}
.lvf-option:hover { color: rgba(var(--w),0.85); }
.lvf-radio {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.lvf-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid rgba(var(--w),0.25);
  flex-shrink: 0;
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.lvf-radio:checked ~ .lvf-dot {
  border-color: var(--accent);
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(216,168,79,0.18);
}
.lvf-radio:checked ~ .lvf-label {
  color: rgba(var(--w),0.9);
  font-weight: 500;
}

.lvf-option--locked {
  cursor: default;
  pointer-events: none;
  opacity: 0.5;
}

.lvf-soon {
  font-style: normal;
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}
.lvf-options-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin-top: 0.75rem;
}

/* Rhythm's opening comparison uses abstract steps rather than notation: the
   following lesson is responsible for teaching actual note and rest symbols. */
.rhythm-concept {
  display: grid;
  gap: 0.55rem;
  width: 100%;
}

.rhythm-concept-row {
  display: grid;
  grid-template-columns: auto minmax(150px, 0.8fr) minmax(180px, 1fr);
  align-items: center;
  gap: 0.8rem;
  width: 100%;
  min-height: 72px;
  padding: 0.75rem 0.85rem;
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: var(--radius-small);
  background: rgba(var(--w), 0.025);
  color: var(--text-main);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.rhythm-concept-row:hover {
  border-color: rgba(var(--w), 0.2);
  background: rgba(var(--w), 0.045);
}

.rhythm-concept-row.rhy-playing {
  border-color: rgba(216, 168, 79, 0.55);
  background: rgba(216, 168, 79, 0.07);
}

.rhythm-concept-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.rhythm-concept-row .rhy-play-icon {
  width: 20px;
  height: 20px;
}

.rhythm-concept-copy {
  display: grid;
  gap: 0.18rem;
  min-width: 0;
}

.rhythm-concept-copy strong {
  color: var(--text-main);
  font-size: 0.9rem;
  font-weight: 650;
}

.rhythm-concept-copy span {
  color: var(--text-muted);
  font-size: 0.75rem;
  line-height: 1.4;
}

.rhythm-concept-track {
  display: grid;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.rhythm-concept-track--pulse {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.rhythm-concept-track--pattern {
  grid-template-columns: repeat(8, minmax(0, 1fr));
}

.rhythm-concept-track i {
  display: block;
  height: 9px;
  border: 1px solid rgba(var(--w), 0.18);
  border-radius: 999px;
  background: rgba(var(--w), 0.035);
}

.rhythm-concept-track--pulse i {
  width: 13px;
  height: 13px;
  margin: auto;
  border: 0;
  background: rgba(var(--w), 0.28);
}

.rhythm-concept-track--pattern i.is-sound {
  border-color: rgba(216, 168, 79, 0.52);
  background: rgba(216, 168, 79, 0.72);
}

.rhythm-concept-track i.is-current {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.16);
  background: var(--accent);
}

.rhythm-concept-feedback {
  margin: 0.25rem 0 0;
  color: var(--accent);
  font-size: 0.85rem;
  line-height: 1.5;
  text-align: center;
}

@media (max-width: 600px) {
  .rhythm-concept-row {
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.55rem 0.7rem;
    min-height: 84px;
  }
  .rhythm-concept-track {
    grid-column: 2;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .rhythm-concept-row,
  .rhythm-concept-track i {
    transition: border-color 0.15s, background-color 0.15s, box-shadow 0.15s;
  }
}
/* Approved pulse-clock variant: a cycle makes pulse visible; notation stays
   in the horizontal note-value family. No new controls for higher levels. */
/* Centred within its lesson widget field, not across the page. */
/* What is Rhythm? (variant B, 14 October 2026): the Metronome's ring and controls
   (rhythm-clock.css #tool-metronome), with the bar's rhythm drawn on the ring. */
.rhythm-clock { display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 400px; margin-inline: auto; }
.rhythm-clock-face { width: 100%; position: relative; }
.rhythm-clock-face > svg { display: block; width: 100%; overflow: visible; }
.rhythm-clock-orbit { fill: none; stroke: rgba(var(--w), .12); stroke-width: 2; }
.rhythm-clock-arc { fill: none; stroke: var(--accent); stroke-width: 8; stroke-linecap: round; transition: opacity .2s ease; }
.rhythm-clock-arc--rest { stroke: rgba(var(--w), .4); stroke-width: 3; stroke-dasharray: 1 10; }
.rhythm-clock[data-phase="pulse"] .rhythm-clock-arc { opacity: .3; }
.rhythm-clock-arc.is-sounding { filter: drop-shadow(0 0 8px rgba(216, 168, 79, .55)); }
.rhythm-clock-beat circle { fill: #1a1a1a; stroke: rgba(var(--w), .22); stroke-width: 2; transition: fill .08s ease; }
.rhythm-clock-beat text { fill: var(--text-main); font: 700 var(--type-item)/1 var(--font-main); text-anchor: middle; dominant-baseline: central; }
.rhythm-clock-beat--first circle { stroke: var(--accent); }
.rhythm-clock-beat--first text { fill: var(--accent); }
.rhythm-clock-beat.is-current circle { fill: var(--accent); stroke: var(--accent); filter: drop-shadow(0 0 14px rgba(216, 168, 79, .4)); }
.rhythm-clock-beat.is-current text { fill: #141414; }
.rhythm-clock-centre { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; gap: 4px; pointer-events: none; }
.rhythm-clock-meter { color: var(--text-main); font: 700 var(--type-display)/1 var(--font-main); }
.rhythm-clock-meta { color: var(--text-muted); font: 600 var(--type-xs)/1.4 var(--font-main); letter-spacing: .1em; text-transform: uppercase; font-variant-numeric: tabular-nums; }
.rhythm-clock-cue { min-height: 20px; color: var(--accent); font: 500 var(--type-small)/20px var(--font-main); }
.rhythm-clock-play { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-width: 11rem; height: 56px; margin-top: 24px; padding: 0 32px; border: 0; border-radius: 999px; background: var(--accent); color: #141414; font: 700 var(--type-item)/1 var(--font-main); box-shadow: 0 8px 24px rgba(0, 0, 0, .3); cursor: pointer; }
.rhythm-clock-play:hover { background: #e3b866; }
.rhythm-clock-play:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.rhythm-clock-play-icon { width: 16px; height: 16px; fill: currentColor; }
/* The shared transport draws icon and label and shrinks Play on a phone; like the Metronome's, this is the main
   action under the circle and keeps its pill, size and word everywhere. */
.rhythm-clock .rhythm-clock-play.pb-play.pb-play { width: auto; min-width: 11rem; max-width: none; height: 56px; min-height: 56px; flex: none; margin-top: 24px; padding: 0 32px; gap: 10px; border: 0; border-radius: 999px; color: #141414; box-shadow: 0 8px 24px rgba(0, 0, 0, .3); }
.rhythm-clock .rhythm-clock-play.pb-play::after { display: block; font: 700 var(--type-item)/1 var(--font-main); }
.rhythm-clock-icon-stop, .rhythm-clock.is-playing .rhythm-clock-icon-start { display: none; }
.rhythm-clock.is-playing .rhythm-clock-icon-stop { display: block; }
.rhythm-clock-legend { display: flex; gap: 20px; margin: 16px 0 0; color: var(--text-muted); font: 400 var(--type-small)/1.6 var(--font-main); }
.rhythm-clock-legend span { display: inline-flex; align-items: center; gap: 8px; }
.rhythm-clock-key { display: inline-block; width: 22px; height: 4px; border-radius: 2px; background: var(--accent); }
.rhythm-clock-key--rest { height: 0; border-top: 3px dotted rgba(var(--w), .45); background: none; border-radius: 0; }
.rhythm-clock-phase { margin: 4px 0 0; text-align: center; color: var(--text-main); font: 500 var(--type-small)/21px var(--font-main); }
@media (prefers-reduced-motion: reduce) {
  .rhythm-clock-arc, .rhythm-clock-beat circle { transition: none; }
}
/* Shared rest lesson: each level introduces its own values above one mixed
   bar, using the same reference, score and audio component. */
.rhy-rest-reference { width: 100%; margin-bottom: 0.75rem; }
.rhy-reference-title { margin: 0 4px 0.35rem; color: var(--text-muted); font-size: 0.75rem; }
.rhy-rest-values { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 92px), 1fr)); gap: 0.6rem 0.4rem; margin: 0; }
.rhy-rest-value { text-align: center; }
.rhy-rest-value dt { margin: 0; color: var(--text-main); }
.rhy-rest-name { display: block; font-size: 0.75rem; line-height: 1.4; }
.rhy-rest-value svg { display: block; width: 44px; height: 45px; margin: 0 auto; }
.rhy-rest-value dd { margin: 0; color: var(--text-muted); font-size: 0.75rem; white-space: nowrap; }
.theory-lesson-widget .rhy-feedback { margin: 0.65rem 4px 0; color: var(--accent); font-size: 0.85rem; line-height: 1.6; }
.rhy-mixed-example .rhy-row { grid-template-columns: minmax(0, 1fr); gap: 4px; }
.rhy-mixed-example .rhy-label { flex-direction: row-reverse; justify-content: space-between; }
.theory-timesig-widget .ts-demo-footer { color: var(--accent); }
.theory-lesson-widget:has(> .feel-comparison) { min-width: 0; }

/* Song form has two deliberately different views. The section path is a
   structural overview; the excerpt uses the same four-measure grammar as the
   Song Library so learners recognise it later as music, not as another card. */
.theory-lesson-widget:has(> .song-form-explorer) { min-width: 0; }
.song-section-path {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.65rem 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.song-section-path li {
  display: grid;
  gap: 0.18rem;
  min-width: 0;
  min-height: 58px;
  padding: 0.62rem 0.7rem;
  border-top: 1px solid rgba(var(--w), 0.18);
  border-left: 1px solid rgba(var(--w), 0.08);
  color: var(--text-main);
  font-size: 0.86rem;
  font-weight: 650;
}
.song-section-path li:nth-child(3n) { border-right: 1px solid rgba(var(--w), 0.08); }
.song-section-path li:nth-child(4),
.song-section-path li:nth-child(6),
.song-section-path li:nth-child(8) { border-top-color: var(--accent); }
.song-section-path-number {
  color: var(--text-muted);
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
.song-section-asides {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin-top: 0.85rem;
}
.song-section-asides span {
  padding: 0.3rem 0.55rem;
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: 999px;
  color: var(--text-muted);
  font-size: 0.76rem;
  line-height: 1.3;
}
.song-section-path + .song-section-asides + .theory-widget-caption { margin-top: 0.75rem; }

.song-form-explorer { width: 100%; min-width: 0; container-type: inline-size; }
.song-form-explorer[hidden] { display: none; }
.song-form-controls { display: grid; gap: 0.35rem; margin: 0; }
.song-form-controls label { color: var(--text-muted); font-size: 0.75rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; }
.song-form-controls select { width: 100%; min-width: 0; min-height: 44px; }
.song-form-context {
  margin-top: 0.65rem;
  padding: 0.75rem 0.85rem;
  border-radius: var(--radius-small);
  background: rgba(var(--w), 0.035);
}
.song-form-description { margin: 0; color: var(--text-main); font-size: 0.84rem; line-height: 1.55; }
.song-form-reference { margin: 0.45rem 0 0; color: var(--text-muted); font-size: 0.8rem; line-height: 1.45; }
.song-form-reference strong { color: var(--accent); font-weight: 650; }

.song-form-map {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.55rem 0;
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}
.song-form-map li { min-width: 0; margin: 0; padding: 0; }
.song-form-step {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.12rem;
  align-content: start;
  width: 100%;
  min-height: 64px;
  padding: 0.5rem 0.6rem;
  border: 0;
  border-top: 1px solid rgba(var(--w), 0.18);
  border-left: 1px solid rgba(var(--w), 0.08);
  border-radius: 0;
  background: transparent;
  color: var(--text-main);
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition: border-color 0.15s, background-color 0.15s;
}
.song-form-map li:nth-child(4n) .song-form-step { border-right: 1px solid rgba(var(--w), 0.08); }
.song-form-step:hover { background: rgba(var(--w), 0.04); }
.song-form-step:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.song-form-step.is-related { border-top-color: var(--accent); }
.song-form-step.is-selected { border-top: 2px solid var(--accent); background: rgba(216,168,79,0.09); }
.song-form-number { color: var(--text-muted); font-size: 0.68rem; font-variant-numeric: tabular-nums; }
.song-form-step-copy { display: grid; gap: 0.08rem; min-width: 0; }
.song-form-name { color: var(--text-main); font-size: 0.82rem; font-weight: 650; line-height: 1.35; overflow-wrap: anywhere; }
.song-form-bars { color: var(--text-muted); font-size: 0.72rem; line-height: 1.3; }
.song-form-explorer .song-form-position { margin: 0.75rem 0 0; color: var(--accent); font-size: 0.82rem; line-height: 1.45; }

.song-form-excerpt {
  margin-top: 0.65rem;
  padding-top: 0.75rem;
  border-top: 1px solid rgba(var(--w), 0.1);
}
.song-form-excerpt-title {
  margin: 0 0 0.55rem;
  color: var(--text-muted);
  font-family: var(--font-serif);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.song-form-chords {
  --song-chart-rule: rgba(var(--w), 0.22);
  --song-chart-rule-strong: rgba(var(--w), 0.52);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  min-width: 0;
}
.song-form-chart-bar {
  position: relative;
  display: flex;
  align-items: stretch;
  min-width: 0;
  min-height: 62px;
  padding: 0 0.58rem;
  border-left: 2px solid var(--song-chart-rule);
}
.song-form-chart-bar:last-child { border-right: 2px solid var(--song-chart-rule); }
.song-form-chart-bar.is-first { border-left-color: var(--song-chart-rule-strong); }
.song-form-chart-bar.is-last { border-right-color: var(--song-chart-rule-strong); }
.song-form-chart-bar.is-first::before,
.song-form-chart-bar.is-last::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--song-chart-rule-strong);
  pointer-events: none;
}
.song-form-chart-bar.is-first::before { left: 4px; }
.song-form-chart-bar.is-last::after { right: 4px; }
.song-form-chart-grid { position: relative; flex: 1 1 auto; min-width: 0; }
.song-form-chart-chord {
  position: absolute;
  top: 50%;
  display: inline-flex;
  align-items: baseline;
  transform: translateY(-50%);
  color: var(--text-main);
  font-size: clamp(0.82rem, 2.2cqi, 1.02rem);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}
.song-form-chart-chord--1-of-1 { left: 0; width: 100%; }
.song-form-chart-chord--1-of-2 { left: 0; width: 50%; }
.song-form-chart-chord--2-of-2 { left: 50%; width: 50%; }
.song-form-chart-root { font-size: 1em; }
.song-form-chart-suffix { margin-left: 0.025em; font-size: 0.86em; }
.song-form-chart-bass { margin-left: 0.09em; font-size: 0.78em; }
.song-form-library-link { display: inline-block; margin-top: 0.75rem; color: var(--accent); font-size: 0.82rem; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
.song-form-library-link[hidden] { display: none; }

@container (max-width: 460px) {
  .song-section-path { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .song-section-path li:nth-child(3n) { border-right: 0; }
  .song-section-path li:nth-child(2n) { border-right: 1px solid rgba(var(--w), 0.08); }
  .song-form-map { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .song-form-map li:nth-child(4n) .song-form-step { border-right: 0; }
  .song-form-map li:nth-child(2n) .song-form-step { border-right: 1px solid rgba(var(--w), 0.08); }
  .song-form-chart-bar { min-height: 56px; padding-inline: 0.38rem; }
}
@media (prefers-reduced-motion: reduce) {
  .song-form-step { transition: none; }
}

.song-struct-legend {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid rgba(var(--w),0.06);
}

.song-legend-item {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 0.88rem;
  line-height: 1.55;
  padding: 5px 2px;
  border-bottom: 1px solid rgba(var(--w),0.04);
}

.song-legend-item:last-child { border-bottom: none; }

.song-struct-numeral-note {
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 0 0 0.6rem;
}

.song-struct-numeral-note strong { color: var(--accent); }

.song-struct-presets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0.6rem;
  margin-bottom: 1rem;
}

.song-struct-preset {
  text-align: center;
  background: rgba(var(--w),0.025);
  border: 1px solid rgba(var(--w),0.06);
  border-radius: 10px;
  padding: 0.6rem 0.85rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s, background 0.15s;
}

.song-struct-preset:hover { background: rgba(var(--w),0.045); border-color: rgba(var(--w),0.1); }
.song-struct-preset.active { background: rgba(216,168,79,0.1); border-color: var(--accent); }
.song-struct-preset.active .song-struct-preset-label { color: var(--accent); }

.song-struct-preset-label {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text-main);
}

.song-legend-name {
  font-weight: 600;
  color: var(--text-main);
  min-width: 80px;
  flex-shrink: 0;
}

.song-legend-desc {
  color: var(--text-muted);
}

/* This is editorial context inside the song explorer, rather than live
   widget feedback. Keep it as one quiet panel, using the shared caption size. */
.song-struct-desc {
  font-size: 0.875rem;
  color: var(--text-muted);
  line-height: 1.5;
  padding: 10px 12px;
  background: rgba(var(--w),0.04);
  border-radius: 9px;
  margin-bottom: 1rem;
}

.song-struct-ref {
  display: block;
  margin-top: 4px;
  color: var(--text-muted);
}

.song-struct-ref strong { color: var(--accent); }

/* Section-role list in the Song Structure intro text — same visual
   language as the widget's own legend (.song-struct-legend /
   .song-legend-item) so the two read as one consistent pattern instead
   of two different list styles. */
.theory-role-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 1.1rem;
  padding: 0;
}

.theory-role-item {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 0.88rem;
  line-height: 1.55;
  padding: 5px 2px;
  border-bottom: 1px solid rgba(var(--w),0.04);
}

.theory-role-item:last-child { border-bottom: none; }

.theory-role-name {
  font-weight: 600;
  color: var(--text-main);
  min-width: 80px;
  flex-shrink: 0;
}

.theory-role-desc {
  color: var(--text-muted);
}


/* ==========================================================
   TERMINOLOGY GLOSSARY
   ========================================================== */

/* Same measure as every other Theory section, so the description, search
   field and glossary stop at the same right-hand line the lesson splits
   do. This rule used to centre the whole tool as well; it does not any
   more — the glossary starts on the page axis like everything else. */
#tool-terminology {
  max-width: var(--page-content-max);
}

.term-search {
  display: block;
  width: 100%;
  max-width: 360px;
  margin: 0.9rem 0 1.2rem;
  padding: 0.55rem 0.8rem;
  background: rgba(var(--w),0.03);
  border: 1px solid rgba(var(--w),0.1);
  border-radius: var(--radius-small);
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.85rem;
}

.term-search:focus { outline: none; border-color: var(--accent); }

.term-glossary {
  columns: 2;
  column-gap: 2.5rem;
}

@media (max-width: 860px) {
  .term-glossary { columns: 1; }
}

.term-letter {
  break-inside: avoid-column;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
  margin: 1.1rem 0 0.3rem;
  padding-bottom: 0.2rem;
  border-bottom: 1px solid rgba(216,168,79,0.25);
}

.term-letter:first-child { margin-top: 0; }

.term-item {
  break-inside: avoid-column;
  padding: 0.5rem 0;
  border-bottom: 1px solid rgba(var(--w),0.04);
}

.term-name {
  display: block;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 2px;
}

.term-def {
  display: block;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
}

.term-empty {
  font-size: 0.85rem;
  color: var(--text-muted);
  padding: 1rem 0;
}


/* ==========================================================
   READING MUSIC
   ========================================================== */

.reading-chord-example {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 12px 4px;
  width: 100%;
}

.reading-chord-example .chord-caged-item {
  /* Grow to fill whatever width the widget column actually has (up to a
     sensible ceiling) instead of sitting at a fixed size — and never
     shrink below a legible floor. With 4 diagrams in a row this wraps to
     two rows of two on narrower containers instead of squeezing all four
     down to an unreadable width. */
  flex: 1 1 190px;
  min-width: 160px;
  max-width: 230px;
}

/* Reading Music's three worked chords span the widget column, so this
   lesson's diagram is drawn larger than elsewhere: it is the ONE chord
   this lesson teaches you to read, so it can afford the room, and one
   large well-drawn diagram beats a row of small ones. Centred in the
   widget column, capped so it never balloons. Only this lesson — a
   diagram elsewhere still sizes to its content. */
#tool-reading-chord-diagram .reading-chord-example .chord-caged-item {
  max-width: 250px;
}

/* Diagrams & TAB keeps its reading matter in the text column: the key to
   a diagram and the sentence naming what it shows are things you read,
   and stacking them under the picture made the widget column run long
   against a short text column. Left-aligned with the prose, one step
   quieter than it — a note about the diagram, not body copy. */
.reading-widget-note,
.chord-finger-legend--inline {
  margin: 0.35rem 0 0;
  text-align: left;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.chord-finger-legend--inline {
  margin-top: 0.9rem;
}

.reading-chord-example ~ .chord-finger-legend,
#readingTabExample ~ .chord-finger-legend {
  text-align: center;
  margin: 0.5rem 0 0;
}
.reading-chord-example ~ .chord-finger-legend + .chord-finger-legend,
#readingTabExample ~ .chord-finger-legend + .chord-finger-legend {
  margin-top: 0;
}

.reading-chord-caption {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--accent);
}

/* The selector and responsive measure form one centered widget. */
.theory-timesig-widget { min-width: 0; }
.theory-timesig-action { margin: 0 0 0.75rem; color: var(--accent); font-weight: 600; text-align: center; }
#tsGrid,
.measures-example-wrap {
  width: 100%;
  max-width: 660px;
  margin-inline: auto;
}
.measures-example-wrap {
  margin-top: 0.85rem;
  padding: 0.85rem 1rem 0.75rem;
  border: 1px solid rgba(var(--w), 0.08);
  border-radius: var(--radius-small);
  background: rgba(var(--w), 0.025);
}

.reading-tab-example--melody {
  margin-top: 0.75rem;
}

.reading-tab-example {
  font-family: "Courier New", monospace;
  font-size: 0.95rem;
  line-height: 1.7;
  color: var(--text-main);
  /* No card/frame — plain text, same width convention as the neck-diagram
     SVGs elsewhere in Theory (width:100% of the widget column). */
  width: 100%;
  height: auto;
  max-height: none;
  white-space: pre;
  /* overflow-x:auto without an explicit overflow-y lets the browser
     compute overflow-y as auto too (per spec: a non-visible overflow-x
     paired with a visible overflow-y promotes the visible side to auto)
     — that's what was giving this a nested vertical scrollbar on
     narrow layouts. TAB may still scroll horizontally; vertically it
     must always expand with its content and let the page scroll. */
  overflow-x: auto;
  overflow-y: visible;
}

/* Real cut-out photographs on the dark portal background. */
.lesson-photo {
  margin: 0;
}

.lesson-photo-img {
  display: block;
  width: 100%;
  height: auto;
  margin: 0 auto;
}

/* ==========================================================
   STARTER THEORY — content-specific presentation
   ----------------------------------------------------------
   The lesson grid itself is the shared Beginner grid above. Only the
   intrinsic presentation of Starter photographs and controls lives here.
   ========================================================== */

.starter-lesson {
  --starter-photo: 340px;
}

/* One idea per block: intro + anatomy, then Guitar Types; neck, then
   Tuning; grammar, then the annotated/clean Am pair. The rule between two
   blocks is what says "new idea" without spending a second h2 on it —
   the tab's own h2 has to keep naming the whole lesson. */
.starter-block {
  max-width: var(--page-content-max);
}

.starter-block + .starter-block {
  margin-top: 2.6rem;
  padding-top: 2.2rem;
}

/* Both photo widgets centre their own image inside whatever box they are
   given (.guitar-anatomy is a flex row with justify-content:center, and
   it is shared with nothing else). In a split that box was the widget
   column and centring was right; here the box is the full visual width,
   so centring would float a 420px photograph in the middle of the page
   while the text it belongs to starts at the left margin. Pin them to
   the same axis instead. */
.starter-split .guitar-anatomy {
  justify-content: center;
}

.starter-split .ga-stage {
  max-width: var(--starter-photo);
}

/* The posture photograph is centred the same way, by an auto margin on
   the image inside a figure that spans the whole visual. Shrink the
   figure to the picture instead, so the photo starts on the axis and
   its caption stays directly under IT rather than under the empty half
   of the block. */
.starter-visual .lesson-photo {
  width: fit-content;
}

/* A widget caption is centred portal-wide, which was right when the
   column it centred in was the narrow half of a split. Here the visual
   is up to 1000px, so a one-line note about a 260px tuning list would
   land 500px away from the thing it describes. On the axis instead,
   like everything else in a Starter block, and held to the same
   editorial measure as the prose so a long caption cannot outrun it. */
/* The sentence changes with the selected guitar. It uses the same type size
   and line-height as the lesson paragraphs so it does not look secondary;
   its muted colour and position identify it as the selector's live readout. */
.starter-type-note {
  margin: 0;
}

/* One compact choice controls both the instrument and its posture example. */
.starter-picker {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.85rem;
}
.starter-types {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.7rem;
}
.starter-type {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.5rem 0.65rem;
  text-align: center;
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: var(--radius-card);
  background: rgba(var(--w), 0.04);
  color: var(--text-muted);
  font-family: var(--font-main);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.starter-type:hover { background: rgba(var(--w), 0.07); color: var(--text-main); }
.starter-type:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.starter-type[aria-checked="true"] {
  background: rgba(216, 168, 79, 0.1);
  border-color: rgba(216, 168, 79, 0.42);
  color: var(--accent);
}
.starter-type-img {
  display: block;
  width: 40px;
  height: 60px;
  object-fit: contain;
  border-radius: 4px;
  background: rgba(var(--w), 0.03);
}
.starter-type-name {
  display: block;
  color: inherit;
  font-size: var(--type-small);
  font-weight: 600;
  line-height: 1.35;
}
.starter-comparison-label {
  display: block;
  color: var(--text-main);
  font-size: var(--type-small);
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
}
.starter-type-desc { font-size: 1rem; line-height: 1.85; color: var(--text-muted); }
@media (max-width: 560px) {
  .starter-types { gap: 0.45rem; }
  .starter-type { padding: 0.55rem 0.3rem 0.6rem; }
}

/* ---- Chord-diagram grammar ---------------------------------------
   The four marks, each one shown as itself in the left column: a real
   dot drawn in CSS, and O, X and 1-4 set as the text they actually are
   on a diagram. No icon font and no glyph stand-ins — what the learner
   sees here has to be what they will see on the diagram below. */
.starter-grammar {
  list-style: none;
  margin: 0.2rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.starter-grammar-row {
  display: grid;
  grid-template-columns: 2.4rem 1fr;
  gap: 0.8rem;
  align-items: start;
}

.starter-grammar-key {
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--text-main);
  text-align: center;
  line-height: 1.6;
  white-space: nowrap;
}

/* ::before rather than the span itself, so the circle sits on the first
   line of the sentence beside it however many lines that sentence runs
   to — the same alignment the O and the X get for free from being text. */
.starter-grammar-key--dot::before {
  content: "";
  display: inline-block;
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 50%;
  background: currentColor;
  vertical-align: -0.02em;
}

.starter-grammar-text {
  font-size: 0.875rem;
  line-height: 1.6;
  color: rgba(var(--w), 0.62);
}

/* Read from the real instrument to the blank grid, then the worked Am shape. */
#theory-panel-starter-chords {
  --starter-chord-size: 230px;
  --starter-play-chord-size: 200px;
  --starter-chord-gap: 0.55rem;
}
.starter-chord-pair {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--starter-chord-gap);
  align-items: start;
}
.starter-chord-pair > * { min-width: 0; }
.starter-chord-study,
.starter-chord-real {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  margin: 0;
}
.starter-chord-real { gap: 0.5rem; }
.starter-real-neck {
  display: block;
  width: min(100%, 195px);
  height: auto;
  overflow: hidden;
}
@media (max-width: 900px) {
  .starter-chord-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .starter-chord-real { grid-column: 1 / -1; }
}
@media (max-width: 420px) {
  .starter-chord-pair { grid-template-columns: 1fr; }
  .starter-chord-real { grid-column: auto; }
}

/* ---- Photo gallery (awaiting assets) -----------------------------
   NOT USED YET, and named here on purpose. The Posture lesson's two
   intended groups — ~4 playing positions and ~4 hand close-ups — are
   2x2 on desktop and stacked on a phone. The markup for them is not
   written, because the photographs do not exist and a placeholder card
   would be worse than an honest absence; this is the layout they drop
   into when they do. */
.starter-gallery {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.4rem;
  max-width: 720px;
}

.starter-gallery-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.starter-gallery-figure img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-media);
}

.starter-gallery-caption {
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-muted);
}

@media (max-width: 600px) {
  .starter-gallery {
    grid-template-columns: 1fr;
    max-width: 340px;
  }
}

/* Starter's TAB gutter carries the six string names now, not digits
   (2026-09-16) — up to 16 characters wide (a 3-letter "Sol|" label plus
   the 11-character fret content and its closing "|"), still short
   enough that the portal-wide mobile clamp on .reading-tab-example is
   still wrong for it: it would park this short block at its 0.6rem
   floor with most of the column empty — numbers and names meant to be
   READ. 16ch at 1rem is still only ~155px in a 335px column.
   Scoped: Beginner and the Reading tool keep the clamp.

   THE WIDGET COLUMN IS FULL WIDTH NOW (no .theory-widget-narrow on the
   wrapper in theory.html — same footprint as Strings & Frets' own neck
   one tab over), but the <pre> itself stays sized to its own short
   content and centred within that column, rather than stretching a
   six-line, ~16-character block across it. */
.starter-lesson .reading-tab-example {
  font-size: 1rem;
  letter-spacing: 0;
  width: max-content;
  max-width: 100%;
  margin: 0 auto;
}

/* ==========================================================
   GUITAR ANATOMY — a photograph with the portal's own labels
   ----------------------------------------------------------
   Starter's "Your Guitar". The photo (portal/assets/img/nylon.png) is a
   FIXED ASSET: never cropped, never recoloured, never redrawn, and it
   carries no text of its own. Every label is a portal element sitting
   over it, which is the only way the same picture can read in English
   and in Dutch.

   THE LABELS SIT ON THE BACKGROUND, NOT ON THE WOOD. The guitar occupies
   roughly the middle half of a 2:3 frame, so the dark vignette either
   side is free space. That is why this needs no second column, no card
   and no extra width - the callouts live inside the image's own box.

   TWO LAYERS, ONE COORDINATE SYSTEM. The leader lines are an SVG with a
   0-100 viewBox and preserveAspectRatio:none, so its units ARE
   percentages of the image box; the labels are HTML positioned with the
   same percentages. Lines therefore scale with the photo while label
   text keeps a real font-size - which is the whole reason the text is
   not in the SVG, where it would shrink to nothing on a phone.
   ========================================================== */
.guitar-anatomy {
  margin: 0;
  display: flex;
  justify-content: center;
}

/* The image's exact box, and the positioning context for both layers.
   max-height keeps the whole guitar on screen without cropping; width
   follows from the 2:3 aspect ratio rather than being set, so the photo
   can never be stretched. */
.ga-stage {
  position: relative;
  display: block;
  width: 100%;
  max-width: 420px;
  aspect-ratio: 1024 / 1536;
}

.ga-photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.ga-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  stroke: rgba(var(--w), 0.72);
  stroke-width: 1;
  fill: rgba(var(--w), 0.85);
}
.ga-lines .ga-line-underlay { stroke: rgba(0, 0, 0, 0.8); stroke-width: 3; fill: none; }
.ga-lines circle { stroke: rgba(0, 0, 0, 0.85); }

/* One physical pixel at every size: without this, preserveAspectRatio
   "none" scales the stroke by each axis separately and the horizontal
   leaders would thin out as the box widens. */
.ga-lines line,
.ga-lines circle {
  vector-effect: non-scaling-stroke;
}

.ga-tag {
  position: absolute;
  top: var(--ga-y);
  transform: translateY(-50%);
  font-size: var(--type-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.2;
  color: var(--text-muted);
  white-space: nowrap;
  pointer-events: none;
}

/* A label's ANCHOR is the edge its leader line leaves from, so the two
   sides are mirror images: a left label ends at --ga-x and grows
   leftward, a right label starts there and grows rightward. */
.ga-tag--left {
  right: calc(100% - var(--ga-x));
  padding-right: 0.4rem;
  text-align: right;
}

.ga-tag--right {
  left: var(--ga-x);
  padding-left: 0.4rem;
  text-align: left;
}

/* Narrow phones: the frame itself is only ~290px across, so the labels
   are brought in a little and set one step smaller. They stay real text
   at a real size - the photo is what gives way, not the typography. */
@media (max-width: 480px) {
  .ga-stage {
    max-width: 300px;
  }
  .ga-tag {
    font-size: var(--type-xs);
    letter-spacing: 0;
  }
  .ga-tag--left  { padding-right: 0.25rem; }
  .ga-tag--right { padding-left: 0.25rem; }
}

/* Practice TAB Trainer shows one short eight-character measure on six
   lines. Keep it compact and readable; matching each line to the full
   answer-row width made the widget unnecessarily tall. */
#tabTrainerTab.reading-tab-example {
  width: max-content;
  max-width: 100%;
  font-size: 1.25rem;
  line-height: 1.3;
  overflow: visible;
}

@media (max-width: 768px) {
  /* A 6-string TAB line (~75+ monospace chars) is wider than any phone
     at a legible font-size — clamp() shrinks it as far as reasonable
     first, `overflow-x: auto` (already the default above) is the last-
     resort fallback so it scrolls inside its own box instead of the
     page overflowing horizontally. Desktop's 0.95rem is untouched. */
  .reading-tab-example {
    font-size: clamp(0.6rem, 2.4vw, 0.85rem);
    letter-spacing: -0.02em;
  }

  /* The Reading-tool instance now sizes its type to its own column (see
     the container-query rule on #tool-reading-tab above), which already
     shrinks it on a phone — so this only restores the box behaviour the
     shared mobile rule above sets, and leaves that sizing alone. */
  #tool-reading-tab .reading-tab-example {
    width: 100%;
    height: auto;
    max-height: none;
    letter-spacing: -0.02em;
    overflow-x: auto;
    overflow-y: visible;
  }
  #tabTrainerTab.reading-tab-example {
    width: max-content;
    font-size: 1.15rem;
    line-height: 1.3;
    letter-spacing: 0;
    overflow: visible;
  }
}


/* ==========================================================
   HARMONY â€” CHORD FUNCTION
   ========================================================== */

.harmony-families {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.harmony-family {
  padding: 0.85rem 1rem;
  border-radius: 10px;
  background: rgba(var(--w),0.025);
  border: 1px solid rgba(var(--w),0.06);
  border-left: 3px solid rgba(var(--w),0.15);
}

.harmony-family--tonic       { border-left-color: var(--accent); }
.harmony-family--subdominant { border-left-color: #6ba3d6; }
.harmony-family--dominant    { border-left-color: #d66b6b; }

.harmony-family-name {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text-main);
  margin-bottom: 0.5rem;
}

.harmony-family--tonic       .harmony-family-name { color: var(--accent); }
.harmony-family--subdominant .harmony-family-name { color: #6ba3d6; }
.harmony-family--dominant    .harmony-family-name { color: #d66b6b; }

.harmony-family-chords {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.harmony-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.4rem 0.7rem;
  border-radius: var(--radius-small);
  background: rgba(var(--w),0.04);
  min-width: 46px;
}

.harmony-chip-roman {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--text-muted);
}

.harmony-chip-name {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text-main);
}

.harmony-family-desc {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
  margin: 0;
}

/* Beginner's static C-major steps visual — a peer of .harmony-families
   above, not a replacement: same chip shape, no click handler, no
   category colours (Beginner does not get the tonic/subdominant/dominant
   classification, only the practical four). I/IV/V/vi are marked with a
   border AND their own role word below the chord name, so the distinction
   survives without colour vision — never colour alone. */
.harmony-steps-beginner {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.harmony-steps-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.harmony-chip--emph {
  border: 1px solid var(--accent);
  background: rgba(var(--w),0.05);
}

/* The formal function name (Tonic/Subdominant/Dominant, on I/IV/V only —
   HRM-02's three terms, already used in the Intermediate copy above).
   Bolder and in the main text colour, sitting above the plain-language
   .harmony-chip-role gloss (home/movement/tension) it already had. */
.harmony-chip-function {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--text-main);
  margin-top: 0.2rem;
}

.harmony-chip-role {
  display: block;
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--accent);
  margin-top: 0.15rem;
}

.harmony-steps-resolve {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-small);
  background: rgba(var(--w),0.025);
  border: 1px solid rgba(var(--w),0.06);
}

.harmony-steps-arrow {
  color: var(--accent);
  font-weight: 700;
}

.harmony-steps-resolve-label {
  font-size: 0.78rem;
  color: var(--text-muted);
}


/* ==========================================================
   TIME SIGNATURES
   ========================================================== */

.ts-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.65rem;
  margin: 0;
}
.ts-card {
  min-width: 0;
  min-height: 3.75rem;
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: var(--radius-small);
  background: rgba(var(--w), 0.025);
  color: var(--text-main);
  padding: 0.65rem 0.75rem;
  cursor: pointer;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.7rem;
  transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.ts-card:hover { background: rgba(var(--w), 0.05); border-color: rgba(var(--w), 0.16); }
.ts-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ts-card.ts-selected {
  border-color: rgba(216, 168, 79, 0.72);
  background: rgba(216, 168, 79, 0.07);
  box-shadow: inset 0 0 0 1px rgba(216, 168, 79, 0.35);
}
.ts-card.ts-playing { background: rgba(216, 168, 79, 0.12); }
.ts-num,
.ts-demo-fraction {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 0.82;
  font-weight: 700;
  letter-spacing: -0.03em;
}
.ts-num { min-width: 1.45rem; font-size: 1.05rem; }
.ts-num span:first-child,
.ts-demo-fraction span:first-child {
  padding-bottom: 0.13em;
  border-bottom: 1px solid currentColor;
}
.ts-num span:last-child,
.ts-demo-fraction span:last-child { padding-top: 0.13em; }
.ts-selected .ts-num,
.ts-playing .ts-num { color: var(--accent); }
.ts-beats {
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.23rem;
  flex-wrap: wrap;
}
.ts-beat {
  width: 0.3rem;
  height: 0.3rem;
  border-radius: 50%;
  background: rgba(var(--w), 0.22);
  flex: 0 0 auto;
}
.ts-beat--accent { width: 0.42rem; height: 0.42rem; background: var(--accent); }
.ts-beat-sep { width: 0.2rem; flex: 0 0 auto; }
.ts-demo { min-width: 0; }
.ts-demo-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 1rem;
}
.ts-demo-fraction { min-width: 2.75rem; font-size: 1.5rem; color: var(--accent); }
.ts-demo-copy {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.18rem;
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.35;
}
.ts-demo-copy strong { color: var(--text-main); font-size: 0.92rem; font-weight: 650; }
.ts-demo-kicker {
  color: var(--accent);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.ts-measure-track {
  display: flex;
  min-width: 0;
  margin-top: 0.65rem;
  border-block: 1px solid rgba(var(--w), 0.12);
  border-inline: 2px solid rgba(var(--w), 0.48);
  background: rgba(0, 0, 0, 0.08);
}
.ts-measure-group {
  min-width: 0;
  flex: var(--ts-group-size) 1 0;
  display: grid;
  grid-template-columns: repeat(var(--ts-group-size), minmax(0, 1fr));
}
.ts-measure-group--separated { border-left: 2px solid rgba(var(--w), 0.2); }
.ts-measure-unit {
  min-width: 0;
  min-height: 3.4rem;
  padding: 0.55rem 0.15rem 0.45rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  border-left: 1px solid rgba(var(--w), 0.055);
  color: var(--text-muted);
}
.ts-measure-group .ts-measure-unit:first-child { border-left: 0; }
.ts-measure-unit--group-start { background: rgba(216, 168, 79, 0.035); }
.ts-measure-unit--bar-start { background: rgba(216, 168, 79, 0.13); color: var(--accent); }
.ts-measure-unit-note {
  font-family: "Bravura Rhythm", "Times New Roman", serif;
  font-size: 1.35rem;
  line-height: 1;
}
.ts-measure-unit-count { font-size: 0.75rem; font-weight: 700; line-height: 1; }
.ts-demo-footer {
  margin: 0.65rem 0 0;
  color: var(--text-muted);
  font-size: 0.72rem;
  line-height: 1.4;
}
@media (max-width: 560px) {
  .ts-grid { gap: 0.45rem; }
  .ts-card { min-height: 4rem; padding: 0.55rem 0.5rem; gap: 0.45rem; }
  .measures-example-wrap { padding: 0.9rem 0.85rem 0.8rem; }
  .ts-demo-head { gap: 0.7rem; }
  .ts-demo-fraction { min-width: 2.2rem; font-size: 1.5rem; }
  .ts-measure-unit { min-height: 4rem; padding-inline: 0.05rem; }
  .ts-measure-unit-note { font-size: 1.1rem; }
  .ts-measure-unit-count { font-size: 0.75rem; }
}

/* ==========================================================
   SCALE DEGREE STRIP (Chord Progressions)
   ========================================================== */

.deg-strip {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
  margin: 8px 0 6px;
}

.deg-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: var(--bg-main);
  border-radius: var(--radius-small);
  padding: 5px 2px 4px;
  text-align: center;
}

.deg-roman {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-main);
  line-height: 1;
}

.deg-roman--dom { color: var(--accent); }

.deg-name {
  font-size: 0.56rem;
  color: var(--text-muted);
  line-height: 1.2;
  letter-spacing: 0.01em;
}

#progRoots { justify-content: flex-start; }

.deg-func {
  font-size: 0.52rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  line-height: 1;
}

/* ==========================================================
   MODES
   ========================================================== */

.mode-families {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 8px;
}

/* font-size/padding: see the shared .tab-secondary group (~line 3153). */
.mode-family-btn {
  font-weight: 700;
  font-family: 'Inter', sans-serif;
  border-radius: 5px;
  border: 1px solid rgba(var(--w),0.12);
  background: rgba(var(--w),0.05);
  color: rgba(var(--w),0.55);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
  letter-spacing: 0.03em;
}

.mode-family-btn:hover {
  background: rgba(var(--w),0.09);
  color: rgba(var(--w),0.8);
}

/* LEVEL 2 (category/subtabs) canonical active state â€” dark gold tint,
   matching .sv-cat-btn.active instead of a neutral white highlight. */
.mode-family-btn.active {
  background: rgba(216,168,79,0.08);
  border-color: rgba(216,168,79,0.28);
  color: var(--accent);
}

.mode-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 1.5rem;
  margin-bottom: 24px;
}

.mode-chips::-webkit-scrollbar { display: none; }

/* font-size/padding: see the shared .tab-filter group (~line 3162). */
.mode-chip {
  font-weight: 700;
  font-family: 'Inter', sans-serif;
  border-radius: 5px;
  border: 1px solid rgba(var(--w),0.12);
  background: rgba(var(--w),0.05);
  color: rgba(var(--w),0.55);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.mode-chip-alias {
  font-size: 0.65rem;
  font-weight: 500;
  opacity: 0.7;
  letter-spacing: 0.02em;
}

.mode-type-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-top: 10px;
  margin-bottom: 4px;
}

.mode-chip:hover {
  background: rgba(var(--w),0.09);
  color: rgba(var(--w),0.8);
}

.mode-chip.active { background: color-mix(in srgb,#d8a84f 16%,transparent); border-color: color-mix(in srgb,#d8a84f 50%,transparent); color:#d8a84f; }

.mode-widget-panel {
  display: flex;
  flex-direction: column;
}

.mode-widget-toggle {
  margin-bottom: 0.85rem;
}

.mode-family-tabs {
  margin-bottom: 1rem;
}

.mode-detail {
  background: rgba(var(--w),0.03);
  border: 1px solid rgba(var(--w),0.07);
  border-radius: 8px;
  padding: 12px 14px;
}

.mode-detail-header {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.mode-detail-name {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-main);
}

.mode-detail-alias {
  font-size: 0.75rem;
  color: var(--text-muted);
  font-style: italic;
}

.mode-detail-genre {
  margin-left: auto;
  font-size: 0.7rem;
  color: var(--text-muted);
  letter-spacing: 0.03em;
}

.mode-notes {
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.mode-meta {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.mode-meta-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.mode-meta-label {
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  min-width: 110px;
  flex-shrink: 0;
}

.mode-meta-value {
  font-size: 0.8rem;
  color: var(--text-main);
}

.mode-char-chord {
  font-weight: 700;
  font-family: monospace;
  letter-spacing: 0.03em;
}

.mode-view-row {
  display: flex;
  margin-bottom: 8px;
}

.mode-desc {
  font-size: 0.82rem;
  color: var(--text-main);
  line-height: 1.55;
  margin-bottom: 10px;
  padding: 8px 10px;
  background: rgba(var(--w),0.03);
  border-left: 2px solid rgba(var(--w),0.12);
  border-radius: 0 5px 5px 0;
}

.mode-note-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0;
  margin-bottom: 10px;
}

.mode-note-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 40px;
  padding: 8px 10px;
  background: rgba(var(--w),0.04);
  border-radius: 6px;
  border: 1px solid rgba(var(--w),0.07);
}

.mode-note-cell.is-root   { border-color: rgba(216,168,79,0.5); background: rgba(216,168,79,0.08); }
.mode-note-cell.is-octave { border-color: rgba(216,168,79,0.25); background: rgba(216,168,79,0.04); opacity: 0.6; }
.mode-note-cell.is-octave .mode-note-name { color: var(--accent); }
.mode-note-cell.is-faded  { opacity: 0.35; }

.mode-note-name {
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-main);
  line-height: 1;
}

.mode-note-cell.is-root .mode-note-name { color: var(--accent); }

.mode-note-deg {
  font-size: 0.68rem;
  color: var(--text-muted);
  margin-top: 3px;
  line-height: 1;
}

.mode-step {
  font-size: 0.72rem;
  font-weight: 700;
  color: rgba(var(--w),0.35);
  padding: 0 4px;
  align-self: center;
  flex-shrink: 0;
  letter-spacing: 0;
}

.mode-step--half { color: rgba(var(--w),0.8); }
.mode-step--aug  { color: #9a7baa; }

/* Beginner: larger note cells */
.mode-note-row--lg .mode-note-cell {
  min-width: 46px;
  padding: 10px 12px;
}

.mode-note-row--lg .mode-note-name { font-size: 1.1rem; }
.mode-note-row--lg .mode-note-deg  { font-size: 0.72rem; }

.mode-play-over {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(216,168,79,0.08);
  border: 1px solid rgba(216,168,79,0.25);
  border-radius: 5px;
  padding: 4px 10px;
  margin-bottom: 8px;
  font-size: 0.78rem;
  color: var(--accent);
  font-weight: 600;
}

/* ==========================================================
   SCAN MODAL  (v9.8)
   ========================================================== */

/* FAB */
.scan-fab {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 50%;
  background: var(--accent);
  color: #000;
  border: none;
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow-main);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 900;
  transition: transform 0.15s, box-shadow 0.15s;
}
.scan-fab:hover { transform: scale(1.08); box-shadow: 0 22px 50px rgba(0,0,0,.45); }

/* Backdrop */
.scan-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.7);
  z-index: 910;
  backdrop-filter: blur(4px);
}

/* Modal */
.scan-modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 920;
  background: var(--bg-card);
  border: 1px solid rgba(var(--w),.08);
  border-radius: var(--radius-media);
  box-shadow: var(--shadow-main);
  width: min(560px, 95vw);
  max-height: 90vh;
  overflow-y: auto;
  padding: 1.75rem;
}

.scan-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
}
.scan-modal-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
}
.scan-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 1.1rem;
  cursor: pointer;
  padding: 0.25rem 0.4rem;
  border-radius: var(--radius-small);
}
.scan-close:hover { color: var(--text-main); background: rgba(var(--w),.06); }

/* Drop zone */
.scan-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 2px dashed rgba(var(--w),.15);
  border-radius: var(--radius-media);
  padding: 2.5rem 1.5rem;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  text-align: center;
}
.scan-drop:hover,
.scan-drop.drag-over { border-color: var(--accent); background: rgba(216,168,79,.06); }
.scan-drop input[type="file"] { display: none; }
.scan-drop-icon { font-size: 2.5rem; }
.scan-drop-text { font-size: 0.95rem; font-weight: 500; }
.scan-drop-hint { font-size: 0.8rem; color: var(--text-muted); }

/* Preview — one or more photos of the same song */
.scan-preview-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 0.6rem;
  margin-bottom: 1rem;
}
.scan-preview-item {
  border-radius: var(--radius-small);
  overflow: hidden;
  background: #111;
  aspect-ratio: 3 / 4;
}
.scan-preview-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---------- the scanned page(s), during review (step 3) ----------
   The same photos already sitting in currentImages for step 2's grid,
   shown again beside the corrected reading so a mistake like "D7" read
   as "D" can be checked against the actual handwriting instead of
   memory. <details> so it can be collapsed once checked - the review
   form under it is already long, and a small screen has little room to
   spare for a second copy of what is already saved. */
.scan-review-photos { margin: 0 0 1.1rem; }
.scan-review-photos summary {
  margin: 0 0 0.5rem;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  cursor: pointer;
  /* The disclosure triangle stays: it is the only visible sign that
     this heading folds the photos away. */
}
.scan-review-photos-grid {
  /* Reads as reference material, not as the upload grid it is copied
     from - one visual step smaller, and never the whole width a single
     photo would otherwise claim on a wide screen. */
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
}
/* Each thumbnail is a <button> (keyboard-reachable enlarge), so the
   browser's button chrome is reset to leave only the photo. */
.scan-review-photos-grid .scan-preview-item {
  padding: 0;
  font: inherit;
  cursor: zoom-in;
  border: 1px solid rgba(var(--w),.1);
}
.scan-review-photos-grid .scan-preview-item:hover { border-color: var(--accent); }
.scan-review-photos-grid .scan-preview-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The enlarge itself: one overlay reused for whichever thumbnail was
   tapped. Above the modal (920) and its backdrop (910), on its own
   backdrop so a tap anywhere outside the photo closes it. No animation
   needed either way - this is a still image, not a transition worth
   spending motion on, so there is nothing here to gate behind
   prefers-reduced-motion. */
.scan-photo-lightbox {
  position: fixed;
  inset: 0;
  z-index: 930;
  background: rgba(0,0,0,.86);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4vh 4vw;
}
.scan-photo-lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius-small);
  box-shadow: var(--shadow-main);
}
.scan-photo-lightbox-close {
  position: absolute;
  top: 1rem;
  right: 1.2rem;
  background: rgba(0,0,0,.5);
  border: 1px solid rgba(255,255,255,.2);
  color: #fff;
  font-size: 1.1rem;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 50%;
  cursor: pointer;
}
.scan-photo-lightbox-close:hover { background: rgba(0,0,0,.7); }

/* Fields */
.scan-fields { display: flex; flex-direction: column; gap: 0.75rem; }
.scan-label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.85rem; color: var(--text-muted); }
.scan-label-hint { font-weight: 400; opacity: 0.8; }
.scan-input {
  background: var(--bg-section);
  border: 1px solid rgba(var(--w),.1);
  border-radius: var(--radius-small);
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.9rem;
  padding: 0.55rem 0.75rem;
  width: 100%;
}
.scan-input:focus { outline: none; border-color: var(--accent); }
.scan-textarea { resize: vertical; font-family: monospace; font-size: 0.82rem; line-height: 1.6; }
.scan-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }

/* Section editor — one row per section, name + bars ("G | D | Em | C") */
.scan-sections { display: flex; flex-direction: column; gap: 0.5rem; }
.scan-section-row {
  display: grid;
  grid-template-columns: 1fr 2fr auto;
  gap: 0.5rem;
  align-items: center;
}
.scan-section-name { font-weight: 600; }
.scan-section-bars { font-family: monospace; font-size: 0.85rem; }
.scan-section-remove {
  background: transparent;
  border: 1px solid rgba(var(--w),.1);
  border-radius: var(--radius-small);
  color: var(--text-muted);
  width: 2.2rem;
  height: 2.2rem;
  flex-shrink: 0;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.scan-section-remove:hover { color: #e07070; border-color: rgba(224,112,112,.4); }
.scan-add-section { margin-top: 0; }

@media (max-width: 560px) {
  .scan-section-row { grid-template-columns: 1fr; }
}

/* Buttons */
.scan-btn-primary {
  width: 100%;
  padding: 0.7rem 1.25rem;
  background: var(--accent);
  color: #000;
  border: none;
  border-radius: var(--radius-small);
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  margin-top: 0.5rem;
  transition: opacity 0.15s;
}
.scan-btn-primary:hover { opacity: 0.88; }
.scan-btn-secondary {
  width: 100%;
  padding: 0.6rem 1.25rem;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid rgba(var(--w),.1);
  border-radius: var(--radius-small);
  font-size: 0.9rem;
  cursor: pointer;
  margin-top: 0.4rem;
  transition: color 0.15s, border-color 0.15s;
}
.scan-btn-secondary:hover { color: var(--text-main); border-color: rgba(var(--w),.25); }

.scan-actions { display: flex; flex-direction: column; gap: 0; margin-top: 1rem; }

/* THE INTAKE FIELDS, on the preview step. They sit between the photos
   and Analyse because that is the order of the decision: this is the
   chart, this is what it is called, now spend the call. */
.scan-intake { margin: 1rem 0; }
/* CP8.4: the two fields stopped being a toll, so the step has to say
   so. Quiet, above them, and never styled as a warning - "optional" is
   information, not a problem. */
.scan-intake-lead {
  margin: 0 0 0.75rem;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* WHAT WAS ON THE PAGE - a line above the review form, not a panel.
   It is information the admin needs before they read the chart, so it
   is quiet: no border, no colour, nothing that competes with the fields
   under it. The one sentence that says what will NOT be saved is the
   only part allowed to look like a caution. */
.scan-found { margin: 0 0 1rem; }
.scan-found-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.scan-found-item {
  font-size: 0.75rem;
  padding: 0.2rem 0.55rem;
  border: 1px solid rgba(var(--w),.12);
  border-radius: 999px;
  color: var(--text-secondary);
  white-space: nowrap;
}
/* SAVED versus ONLY READ, and the difference is not carried by colour
   alone: each list has its own written heading above it (see
   renderDetected in songs.js), so the distinction survives for a reader
   who cannot tell the two borders apart. */
.scan-found-label {
  margin: 0.75rem 0 0.35rem;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.scan-found-label:first-child { margin-top: 0; }
.scan-found-item--saved { border-color: rgba(216,168,79,.3); color: var(--text-secondary); }
.scan-found-item--pending { border-style: dashed; color: var(--text-muted); }
.scan-found-note {
  margin: 0.6rem 0 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* THIS MAY ALREADY BE IN YOUR LIBRARY.

   Deliberately quiet. It is a warning, not a refusal - the same song
   legitimately arrives twice - so it reads as information with three
   answers under it, not as an error the way .scan-error does. */
.scan-duplicate-lead {
  margin: 0 0 0.75rem;
  color: var(--text-main);
  font-size: 0.95rem;
  line-height: 1.5;
}
.scan-duplicate-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.scan-duplicate-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid rgba(var(--w),.1);
  border-radius: 6px;
  background: rgba(var(--w),.03);
}
.scan-duplicate-name { color: var(--text-main); font-weight: 600; }
.scan-duplicate-note {
  font-size: 0.75rem;
  color: var(--text-muted);
  text-transform: lowercase;
}

/* Loading */
.scan-loading { display: flex; flex-direction: column; align-items: center; gap: 1.25rem; padding: 2rem 0; }
.scan-spinner {
  width: 2.5rem; height: 2.5rem;
  border: 3px solid rgba(var(--w),.1);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.75s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.scan-loading-text { color: var(--text-muted); font-size: 0.9rem; }

/* Error */
.scan-error-msg { color: #e07070; font-size: 0.9rem; margin-bottom: 0.75rem; }
/* The second sentence - what is still here, what another attempt would
   cost. Not red: it is not the failure, it is the way out of it. */
/* ==========================================================
   MORE THAN ONE SONG ON A PAGE (CP8.4c)
   ----------------------------------------------------------
   Hidden for the ordinary one-song scan. It is a switcher, not a list:
   the form below always shows exactly one candidate, and which one is
   the only thing this bar decides.
   ========================================================== */
.scan-candidates {
  margin: 0 0 1rem;
  padding: 0.75rem;
  border: 1px solid rgba(var(--w),.12);
  border-radius: 10px;
  background: rgba(var(--w),.03);
}
.scan-candidates-lead {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-main);
}
.scan-candidates-hint { font-size: 0.8rem; color: var(--text-muted); }
.scan-candidate-list { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.6rem; }
.scan-candidate {
  padding: 0.4rem 0.8rem;
  border: 1px solid rgba(var(--w),.16);
  border-radius: 999px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.82rem;
  cursor: pointer;
}
.scan-candidate.is-active {
  border-color: var(--accent);
  color: var(--text-main);
  background: rgba(var(--w),.06);
}
.scan-candidate.is-saved { opacity: 0.7; }
.scan-candidate-mark { color: var(--accent); }
.scan-candidate-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.scan-candidate-actions + .scan-candidate-actions { margin-top: 0.5rem; }
.scan-btn-all { margin-left: 0.25rem; }

.scan-error-note {
  margin: 0 0 1rem;
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--text-muted);
}

/* ==========================================================
   GUITAR TAB, READ-ONLY (song page)
   ----------------------------------------------------------
   Same notation as the editor, without the buttons: this is the
   student's page and it reads music rather than correcting it. A <pre>
   because conventional tab IS fixed-width text, and because that makes
   it selectable and copyable in one gesture.
   ========================================================== */
.song-tab-view { margin: 1.5rem 0; }
.song-tab-title {
  margin: 0 0 0.5rem;
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.song-tab-tuning, .song-tab-system-label {
  margin: 0 0 0.4rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}
/* One block per DISPLAY ROW, stacked. The staff is cut to fit by
   song-tab.js from a width this page measures, so the rows are already
   narrow enough - overflow-x is the safety valve for a width nothing
   could have fitted, not the layout. */
.song-tab-staves { margin-bottom: 0.75rem; }
.song-tab-staff {
  display: block;
  overflow-x: auto;
  overflow-y: hidden;
  margin: 0 0 0.5rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid rgba(var(--w),.1);
  border-radius: 8px;
  background: rgba(var(--w),.02);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text-main);
  white-space: pre;
}
@media (max-width: 380px) {
  .song-tab-staff { font-size: 0.85rem; padding: 0.45rem; }
}

/* ==========================================================
   GUITAR TAB EDITOR (CP8.4)
   ----------------------------------------------------------
   The staff is real characters in a monospace grid, drawn from the same
   layout the text renderer uses - so alignment is the font's job and
   not a stack of measured boxes that can drift a pixel per column.

   IT WRAPS, IT DOES NOT SCROLL. Tab does not compress - a fret number
   squeezed until it is unreadable has lost the one thing it was for -
   so the type keeps its size and the MUSIC wraps instead, into display
   rows of four measures, or two, or one, exactly as printed tablature
   has always been set. A staff dragged sideways loses your place in the
   music; a staff stacked down the page does not.

   .tab-staff-scroll therefore stays, and should never be seen: it is
   the safety valve for a width nothing could have fitted, not the
   layout. What decides where the rows break is song-tab.js, from a
   budget in characters that song-tab-fit.js measures here.
   ========================================================== */
.scan-tab { margin: 1.25rem 0 0; }
.scan-tab-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.scan-tab-history { display: flex; gap: 0.4rem; }
.scan-tab-history .scan-btn-secondary { margin: 0; padding: 0.35rem 0.7rem; font-size: 0.75rem; }
.scan-tab-history .scan-btn-secondary[disabled] { opacity: 0.4; cursor: default; }

.tab-editor { margin-top: 0.5rem; }

.tab-editor-tuning {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.tab-tuning-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.12em;
  color: var(--text-main);
}
.tab-tuning-note { font-style: italic; }

.tab-system { margin-bottom: 1rem; }
.tab-system-head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 0.25rem;
}
.tab-system-name { font-size: 0.75rem; color: var(--text-muted); }

.tab-staff-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  /* Room between one display row and the next, so a wrapped system
     reads as a stack of staves rather than one tall block. */
  padding-bottom: 0.5rem;
  scrollbar-width: thin;
}
.tab-staff {
  display: inline-block;
  min-width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid rgba(var(--w),.1);
  border-radius: 8px;
  background: rgba(var(--w),.02);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* 0.9rem is the floor: below it a two-digit fret stops being legible
     at arm's length, which is how a chart is actually read. */
  font-size: 0.9rem;
  line-height: 1.5;
  white-space: pre;
}
.tab-row { display: block; }
.tab-row-label { color: var(--text-muted); }
.tab-row--spans { color: var(--accent); }
.tab-cell { color: rgba(var(--w),.3); }
.tab-cell--bar { color: rgba(var(--w),.45); }
.tab-dash { color: rgba(var(--w),.3); }
.tab-span-label, .tab-span-run { color: var(--accent); }

/* A fret number and an empty position are both buttons, and both have
   to look like part of the staff rather than like form controls. */
.tab-note,
.tab-slot {
  font: inherit;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  border-radius: 3px;
}
.tab-note { color: var(--text-main); }
.tab-slot { color: rgba(var(--w),.3); }
/* AN EMPTY POSITION IN A GRIDDED BAR - the staff line running through a
   place where nothing is played.

   It is drawn as the staff and NOT as a symbol, on purpose. A rest is
   something a writer puts on a page; this is a position with nothing on
   it, and the difference is the whole point of the grid model
   (song-tab.js, gridSlots()). Anything printed here - a dot, a tick, a
   rest glyph - would be this project claiming the writer wrote a
   silence. So it looks exactly like the staff until it is hovered or
   focused, when it shows it is a place a note can go. */
.tab-slot--grid:hover { background: rgba(216,168,79,.10); }
.tab-note:hover { color: var(--accent); }
.tab-slot:hover { color: var(--text-secondary); background: rgba(var(--w),.06); }

/* SELECTED is an outline plus a background, never colour alone, and the
   buttons carry aria-pressed as well. */
.tab-note.is-selected,
.tab-slot.is-selected {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  background: rgba(216,168,79,.14);
  color: var(--text-main);
}
.tab-note:focus-visible,
.tab-slot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.tab-editor-panel {
  margin-top: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.tab-panel-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.tab-panel-title {
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-right: 0.25rem;
}
.tab-panel-desc { font-size: 0.8rem; color: var(--text-secondary); }
/* THE REVIEW NOTICE - one line, above the staff, said once.
   Informational: a handwritten page with no barlines is a valid
   canonical state, not a failed reading, so it is a quiet accent rule
   and never the error colour. */
.tab-editor-notice {
  margin: 0 0 0.6rem;
  padding: 0.5rem 0.65rem;
  border-left: 2px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: rgba(var(--w),.03);
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-secondary);
}
/* Nothing unresolved left - the same shape, stated as done. */
.tab-editor-notice--done { border-left-color: rgba(var(--w), .25); }
/* A sentence beside the buttons, for the facts a staff cannot show:
   that a run has no barlines, or that a barline is a boundary and not a
   beat. It takes the whole row so the buttons keep their line. */
.tab-panel-note {
  flex-basis: 100%;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted);
}
/* A display row that opens inside a bar the row above began. Marked so
   a wrapped bar does not read as a new one. */
.tab-staff[data-continues="true"] { border-left-style: dashed; }
.tab-editor-hint, .tab-editor-empty {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-muted);
}
.tab-editor-status {
  margin: 0.4rem 0 0;
  min-height: 1em;
  font-size: 0.75rem;
  color: var(--text-muted);
}

.tab-field, .tab-tuning-field {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.tab-input {
  font: inherit;
  font-size: 0.8rem;
  padding: 0.3rem 0.4rem;
  color: var(--text-main);
  background: rgba(var(--w),.04);
  border: 1px solid rgba(var(--w),.14);
  border-radius: 6px;
}
.tab-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.tab-input--fret { width: 4.5rem; }
.tab-input--note { width: 3rem; text-align: center; }
/* Two selects that must never look like one control: WHERE in the bar
   an event sits, and how long the bar is. Sized to their content so the
   16-position one does not become a text field. */
.tab-input--slot { width: 4rem; }
.tab-input--meter { width: 7.5rem; }

.tab-btn {
  font: inherit;
  font-size: 0.75rem;
  padding: 0.35rem 0.65rem;
  color: var(--text-secondary);
  background: none;
  border: 1px solid rgba(var(--w),.14);
  border-radius: 6px;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.tab-btn:hover { color: var(--text-main); border-color: rgba(var(--w),.28); }
.tab-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tab-btn--primary { color: var(--accent); border-color: rgba(216,168,79,.4); }
.tab-btn--danger { color: #e07070; border-color: rgba(224,112,112,.3); }
.tab-btn--quiet { color: var(--text-muted); border-color: transparent; }
/* A pressed chip is bordered AND filled AND carries aria-pressed - the
   state is never only its colour. */
.tab-chip.is-on {
  color: var(--accent);
  border-color: rgba(216,168,79,.45);
  background: rgba(216,168,79,.12);
}
/* A GRID THAT WOULD MOVE MUSIC. Dimmed and dashed rather than
   disabled: it is still focusable and still clickable, and clicking it
   puts the reason in the status line - "position 2 of 16 has no
   position in a grid of 8" - which is what tells an admin which note to
   move. A control that simply refuses to respond teaches nobody
   anything. The `title` carries the same sentence for a pointer. */
.tab-chip.is-blocked {
  color: var(--text-muted);
  border-style: dashed;
  border-color: rgba(var(--w),.18);
}
.tab-chip.is-blocked:hover { color: var(--text-secondary); }

.tab-editor-issues {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.tab-editor-issue { font-size: 0.75rem; color: var(--text-muted); }
.tab-editor-issue--error { color: #e07070; }

/* The staff never shrinks; the phone scrolls it. The CONTROLS do adapt,
   because a wrapped row of buttons is still readable and a wrapped
   stave is not. */
@media (max-width: 600px) {
  .tab-panel-row { gap: 0.3rem; }
  .tab-btn { padding: 0.3rem 0.5rem; }
  .scan-tab-head { align-items: flex-start; }
}
@media (max-width: 380px) {
  .tab-staff { font-size: 0.85rem; padding: 0.4rem; }
  .tab-field, .tab-tuning-field { font-size: 0.7rem; }
}

/* Scanned song in list */
.lesson-tag--scanned { background: rgba(216,168,79,.15); color: var(--accent); border: 1px solid rgba(216,168,79,.25); }
.scan-thumb-icon { font-size: 1.5rem; display: flex; align-items: center; justify-content: center; height: 100%; }

/* Scanned song structure + sections in detail */
.scanned-structure {
  font-size: 0.9rem;
  color: var(--text-muted);
  margin: 0.5rem 0 1.5rem;
}
.scanned-chart {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin: 1.5rem 0;
}
.scanned-section {
  background: var(--bg-section);
  border: 1px solid rgba(var(--w),.07);
  border-radius: var(--radius-small);
  padding: 1rem 1.25rem;
}
.scanned-section h3 {
  margin: 0 0 0.4rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.scanned-bars {
  margin: 0;
  font-family: monospace;
  font-size: 0.9rem;
  line-height: 1.7;
  color: var(--text-main);
  white-space: pre-wrap;
  word-break: break-word;
}
.scanned-notes {
  font-size: 0.85rem;
  color: var(--text-muted);
}
.scanned-actions { margin-top: 1rem; }

/* ==========================================================
   THEORY ï¿½ Secondary Dominants  (v16.95)
   ========================================================== */

.secdom-list { display: flex; flex-direction: column; gap: 0.65rem; margin-top: 0.75rem; }

.secdom-card {
  background: var(--bg-section);
  border: 1px solid rgba(var(--w),.07);
  border-radius: var(--radius-small);
  padding: 0.75rem 1rem;
}
.secdom-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 0.35rem;
}
.secdom-symbol {
  font-size: 0.78rem;
  color: var(--text-muted);
  min-width: 5rem;
}
.secdom-chord {
  font-weight: 700;
  font-size: 1rem;
  color: var(--accent);
}
.secdom-arrow { color: var(--text-muted); }
.secdom-target { font-weight: 600; }
.secdom-roman  { font-size: 0.8rem; color: var(--text-muted); font-weight: 400; }
.secdom-desc   { font-size: 0.82rem; color: var(--text-muted); line-height: 1.55; }

/* ==========================================================
   THEORY ï¿½ Shell Voicings
   ========================================================== */

.shell-grid { display: flex; flex-direction: column; gap: 0.85rem; margin-top: 0.25rem; }

.shell-form-group { display: flex; flex-direction: column; gap: 0.4rem; }

#shellGrid .chord-caged-item svg { max-width: 130px; }

.shell-form-title {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.shell-svg-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1.1rem;
}

.shell-svg-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
}

.shell-svg-label {
  font-size: 0.7rem;
  color: var(--text-muted);
  text-align: center;
}

.shell-svg { display: block; }

/* ==========================================================
   THEORY ï¿½ Drop Voicings
   ========================================================== */

.drop-cards { display: flex; flex-direction: column; gap: 0.9rem; margin-top: 0.25rem; }

.drop-card {
  background: var(--bg-section);
  border: 1px solid rgba(var(--w),.07);
  border-radius: var(--radius-small);
  padding: 0.85rem 1rem;
}

.drop-card-header {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding-left: 0.6rem;
  margin-bottom: 0.5rem;
}

.drop-card-name { font-weight: 700; font-size: 0.95rem; }
.drop-card-sub  { font-size: 0.78rem; color: var(--text-muted); }
.drop-card-desc { font-size: 0.83rem; color: var(--text-muted); line-height: 1.55; margin-bottom: 0.65rem; }

.drop-card-meta { display: flex; flex-direction: column; gap: 0.3rem; }
.drop-meta-row  { display: flex; gap: 0.6rem; align-items: baseline; font-size: 0.8rem; }
.drop-meta-label {
  font-size: 0.7rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  min-width: 4rem;
  flex-shrink: 0;
}
.drop-card-meta code {
  font-family: monospace;
  font-size: 0.8rem;
  color: var(--text-main);
  background: rgba(var(--w),.05);
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
}

/* ==========================================================
   THEORY ï¿½ Song Structure
   ========================================================== */

.song-struct-timeline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.75rem;
  overflow: hidden;
}

/* Forces a flex-wrap line break at a specific point (struct.rowBreakAt in
   theory.js) regardless of how much room the row still has — a
   full-width, zero-height item pushes everything after it onto the next
   line. */
.struct-row-break {
  flex-basis: 100%;
  width: 0;
  height: 0;
  margin: 0;
  padding: 0;
}

/* Desktop only: when theory.js (initSongStructure/fitTimelineOneLine)
   finds the row is a little too wide for all blocks at natural size, it
   sets --st-scale and adds this class instead of letting flex-wrap drop
   the last block(s) onto an almost-empty second line. Padding/font
   shrink together so the row stays proportional, not squashed. */
.song-struct-timeline--compact {
  flex-wrap: nowrap;
}
.song-struct-timeline--compact .struct-block {
  padding: calc(0.55rem * var(--st-scale, 1)) calc(0.9rem * var(--st-scale, 1));
}
.song-struct-timeline--compact .struct-block-label {
  font-size: calc(0.85rem * var(--st-scale, 1));
}
.song-struct-timeline--compact .struct-block-bars {
  font-size: calc(0.65rem * var(--st-scale, 1));
}

/* 12-bar/8-bar blues forms: always exactly 4 blocks per row (a real
   chart's 3-line-of-4-bars layout), not however many happen to fit the
   container's width like the flex-wrap default above. */
.song-struct-timeline--rows-of-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

/* Mobile: flex-wrap's per-item natural width (padding around each
   label) left uneven, left-aligned rows once labels like "Chorus"/
   "Bridge" wrapped — a grid gives every block equal width and fills
   the row edge to edge instead. Excludes --rows-of-4 (blues forms),
   which already sets its own fixed 4-column grid above. */
@media (max-width: 768px) {
  .song-struct-timeline:not(.song-struct-timeline--rows-of-4) {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Narrow phones: 3 columns starts cramping longer labels ("Chorus",
   "Bridge") — drop to 2. */
@media (max-width: 480px) {
  .song-struct-timeline:not(.song-struct-timeline--rows-of-4) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.struct-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.55rem 0.9rem;
  border-radius: var(--radius-small);
  border: 1px solid rgba(var(--w),0.1);
  background: rgba(var(--w),0.05);
  cursor: pointer;
  opacity: 0.82;
  transition: opacity .15s, transform .1s, background .15s;
}
.struct-block:hover  { opacity: 1; background: rgba(var(--w),0.08); }
.struct-block.active { opacity: 1; transform: translateY(-2px); outline: 2px solid rgba(216,168,79,0.7); box-shadow: 0 4px 12px rgba(0,0,0,.3); }

/* Blues turnaround (V turn) â€” accented so the repeating I/IV/V blocks
   aren't all visually identical; this is the bar that pulls back to 1. */
.struct-block--turn { background: rgba(216,168,79,0.16); border-color: rgba(216,168,79,0.35); }
.struct-block--turn .struct-block-label { color: var(--accent); }

.struct-block-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-main);
  line-height: 1.2;
}
.struct-block.active .struct-block-label { color: var(--accent); }

.struct-block-bars {
  font-size: 0.65rem;
  font-weight: 500;
  color: var(--text-muted);
  line-height: 1;
  margin-top: 3px;
}

.song-struct-detail { min-height: 2.5rem; }
.song-struct-detail p {
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--text-muted);
  margin: 0;
}
.struct-section-name {
  display: inline-block;
  font-weight: 700;
  font-size: 0.88rem;
  margin-bottom: 0.3rem;
}

.struct-chord-example {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid rgba(var(--w),0.06);
  font-size: 0.82rem;
  color: var(--text-muted);
}

.struct-chord-example strong {
  color: var(--accent);
  font-weight: 700;
  /* Monospace + white-space:pre — each bar is padded to a fixed
     character width in JS (theory.js's formatBar()) so every bar takes
     up equal visual room whether it holds one chord or two, the way a
     real lead sheet spaces things out. A proportional font would make
     that padding meaningless since space width isn't fixed there. */
  font-family: "Courier New", monospace;
  white-space: pre;
}

/* ==========================================================
   ADVANCED PROGRESSIONS
   ========================================================== */

.adv-prog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.85rem;
  margin-top: 0.75rem;
}

#advProgList {
  max-height: 140px;
  overflow-y: auto;
  padding-right: 0.3rem;
}

.shell-form-sep {
  width: 1px;
  align-self: stretch;
  background: rgba(var(--w),0.08);
  margin: 0 0.75rem;
  flex-shrink: 0;
}

.adv-prog-section {
  margin-bottom: 1.1rem;
}

.adv-prog-section-label {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.5;
  margin-bottom: 0.5rem;
}

.adv-prog-card {
  background: rgba(var(--w),0.04);
  border: 1px solid rgba(var(--w),0.08);
  border-radius: 10px;
  padding: 0.9rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.adv-prog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.15rem;
}

.adv-prog-label {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--text-main);
}

.adv-prog-formula {
  font-family: monospace;
  font-size: 0.88rem;
  color: var(--accent, #c9a84c);
  background: rgba(201,168,76,0.08);
  border-radius: 5px;
  padding: 0.25rem 0.5rem;
  display: inline-block;
}

.adv-prog-desc {
  font-size: 0.8rem;
  color: var(--text-muted);
  line-height: 1.5;
  margin-top: 0.2rem;
}


/* â”€â”€ Scale info tooltip â”€â”€ */
.sc-type-item {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.sc-info-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.78rem;
  padding: 0 0.15rem;
  opacity: 0.5;
  transition: opacity 0.15s, color 0.15s;
  line-height: 1;
  flex-shrink: 0;
}

.sc-info-btn:hover,
.sc-info-btn.sc-info-active {
  opacity: 1;
  color: var(--accent);
}

.sc-tooltip {
  display: none;
  position: fixed;
  z-index: 300;
  max-width: 280px;
  padding: 0.65rem 0.85rem;
  background: #2a2a2a;
  border: 1px solid rgba(var(--w),0.12);
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.6);
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--text-main);
  pointer-events: none;
}

.sc-tooltip strong {
  display: block;
  margin-bottom: 0.2rem;
  font-size: 0.85rem;
  color: var(--accent);
}

.sc-tip-aka {
  color: var(--text-muted);
  font-size: 0.74rem;
  margin-bottom: 0.3rem;
  font-style: italic;
}

.sc-tip-desc {
  color: var(--text);
}

/* â”€â”€ Fretboard layout consistency â”€â”€
   .tool-nav is really a page-level tool navigation strip (sticky, with a
   rule under it and 1.5rem of air below — see its base rule further up).
   Diatonic/Pentatonic and Arpeggio only borrow the class as a wrapper
   around their position stepper, where that divider and the big gap read
   as an unexplained break between the controls and the diagram. Both are
   listed here so the two steppers can't drift apart again; the values are
   unchanged from what Diatonic/Pentatonic already had. */
#tool-scales .tool-nav,
#tool-arpeggios .tool-nav {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
  align-items: center;
}

/* Chords tab: link out to the advanced chord page */
#tool-chords .tool-nav {
  border-bottom: none;
  margin-top: 1rem;
  margin-bottom: 0;
  padding-bottom: 0;
}

/* Position stepper sits inline in the tool-nav, next to the Advanced link */
.sv-cats--inline {
  margin-bottom: 0;
  align-items: center;
}

.fbx-pos-range {
  font-size: 0.72rem;
  color: var(--text-muted);
  white-space: nowrap;
}

/* The Advanced/Expert island view only: its .sv-info sits inside a flex
   column that collapses no margins, so it needs none. #cagedIntInfoBar is
   excluded because it is not in this view at all — the Intermediate tool
   has its caption in a diagram header now, and the header's own
   `.fb-diagram-head > .sv-info { margin: 0 }` is what zeroes it there. */
#fb-panel-caged .sv-info:not(#cagedIntInfoBar) { margin-top: 0; }


/* ==========================================================
   MOBILE CONTROL ROWS — one scrollable line (portal-wide pattern)
   ----------------------------------------------------------
   A pill/tab/chip row that no longer fits a phone's width stays on ONE
   line and scrolls horizontally, instead of wrapping to a second (and
   third) line that pushes the diagram below the fold. Same
   single-scrollable-row look the page already uses for .fb-tabs and
   .tool-nav — hidden scrollbar, touch momentum — so nothing new appears
   visually. The matching motion (after a selection, reveal the active
   item plus the next one, with the smallest scroll that does it) is
   portal.js's PortalRows helper.

   Rows opt in with the data-scroll-row attribute, which is all a future
   row anywhere in the portal needs — no per-tool CSS.

   On the Fretboard page the rule covers Intermediate, Advanced and
   Expert. It was Intermediate-only, on the reasoning that the levels
   above it wrapped their longer rows instead — but they never did: a
   #fbxCats holding "Harmonic minor modes" and "Melodic minor modes", or
   a #fbxTypes holding seven mode names, laid itself out on one 940px
   line inside a 375px viewport, and `.platform-content`'s
   `overflow-x: clip` then made everything past the fold unreachable
   rather than merely ugly. Scrolling that line is the behaviour the rest
   of the portal already has, so the three levels that share these
   elements now share it too.

   BEGINNER AND STARTER ARE DELIBERATELY STILL OUT. Their rows hold three
   short chips and always fit, so the rule would change nothing except
   the frozen levels' computed styles ([data-fb-level] is set by
   fretboard.js's applyFretboardLevelAttr()). Everywhere else the
   attribute is enough on its own.

   Desktop/tablet are untouched at every level: this lives inside the
   phone breakpoint only, and a row that fits shows no scrollbar because
   there is nothing to scroll.

   Neck diagrams are deliberately NOT part of this: a full neck rotates
   vertical in portrait and fits the viewport in landscape, a compact
   position window fits as-is, and neither ever scrolls horizontally. */
@media (max-width: 700px) {
  body:is([data-fb-level="intermediate"], [data-fb-level="advanced"], [data-fb-level="expert"]) [data-scroll-row],
  body:not(.fretboard-page) [data-scroll-row] {
    flex-wrap: nowrap;
    overflow-x: auto;
    /* A flex item will not shrink below its content unless told to, which
       is what let these rows push past the viewport instead of scrolling
       inside it. */
    min-width: 0;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Breathing room so the first/last pill — and its focus ring — is
       never clipped against the scroll container's edge, and so a chip
       reached by keyboard focus lands clear of that edge. */
    padding-inline: 2px;
    scroll-padding-inline: 0.5rem;
  }
  body:is([data-fb-level="intermediate"], [data-fb-level="advanced"], [data-fb-level="expert"]) [data-scroll-row]::-webkit-scrollbar,
  body:not(.fretboard-page) [data-scroll-row]::-webkit-scrollbar {
    display: none;
  }
  /* Direct children, not the buttons: #fbxTypes/#arpItems wrap each button
     in a .sc-type-item, and the position steppers start with a label span
     — all of them must keep their natural width. */
  body:is([data-fb-level="intermediate"], [data-fb-level="advanced"], [data-fb-level="expert"]) [data-scroll-row] > *,
  body:not(.fretboard-page) [data-scroll-row] > * {
    flex-shrink: 0;
  }
  /* The rows sit inside .fb-controls / .fb-control-line, which are flex
     containers of their own: without this the LINE keeps the row's full
     content width and the scroller never has anything narrower to scroll
     inside. */
  body.fretboard-page .fb-controls,
  body.fretboard-page .fb-control-line {
    min-width: 0;
  }
  body.fretboard-page .fb-control-line > * {
    min-width: 0;
  }
}

/* ==========================================================
   INTERMEDIATE CAGED (#cagedIntTool)
   ----------------------------------------------------------
   Same visual hierarchy/spacing as Intermediate Arpeggio and Pentatonic:
   QUALITY row → ROOT dropdown → LAYER stepper → INFO → 15-fret neck.
   Every row reuses that tool's own class (.sv-scales-row,
   .chord-voicing-select, .sv-cats--inline, .sv-info, .sv-board-wrap), so
   the two tabs are visually interchangeable and the neck inherits the
   shared full-neck responsive + left-handed + portrait-rotation behavior
   automatically.

   Deliberately NO CAGED CSS for the neck, the control rows or the info
   block. #cagedIntMap gets its width, max-width, scaling and orientation
   from the same .sv-board-wrap rules #fbxMap uses, and its vertical
   rhythm from the shared FRETBOARD CONTROL SYSTEM tokens at the end of
   this file — which is what keeps this tab interchangeable with Scales
   instead of merely similar to it. It used to carry three margins of its
   own here (Root 0/1rem, info 0.25rem/0.9rem, layers 0) and those are
   exactly the differences that made one tab sit 16px off another. */

/* The four #fb-panel-caged .dens-stepper overrides that lived here are
   gone with .dens-stepper itself — they existed to make the density
   stepper look like the Scales category row, which it now simply IS. */

/* Fretboard Practice — shared neck wrapper for all three widgets
   (Finding the Note / Note Naming / String Note Challenge). */
.practice-neck-wrap {
  margin: 0.75rem 0;
}
.practice-neck-wrap svg {
  width: 100%;
  height: auto;
  display: block;
}

/* String Note Challenge — one horizontal string (practiceStringBase()),
   not the full six-string neck, so this card stays short even at full
   card width. */
.practice-string-wrap {
  margin: 0.75rem 0;
  max-width: 720px;
}
.practice-string-wrap svg {
  width: 100%;
  height: auto;
  display: block;
}
.practice-neck-dot {
  cursor: pointer;
  transition: fill 0.1s;
}
.practice-neck-dot:hover {
  stroke: var(--accent);
  stroke-width: 1.5;
}

/* Scale Practice's note caller — same "row of beat markers, one lights up
   at a time" language as .mth-beat-dots above, but each step needs a note
   name inside it so these are small pills instead of plain dots. */
.scalep-degrees {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  justify-content: center;
  margin: 0.75rem 0;
}
.scalep-degree {
  min-width: 28px;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-small);
  background: rgba(var(--w),0.06);
  border: 1.5px solid rgba(var(--w),0.14);
  font-size: 0.8rem;
  font-weight: 700;
  text-align: center;
  color: var(--text-muted);
  transition: background 0.08s, border-color 0.08s, color 0.08s;
}
.scalep-degree.active {
  background: rgba(216,168,79,0.16);
  border-color: var(--accent);
  color: var(--accent);
}

/* Strumming Patterns — a row of down/up arrows, current beat highlighted
   in sync with the scheduler, same visual language as .scalep-degree. */
/* EIGHT SLOTS THAT ALWAYS FIT, AT ANY CARD WIDTH. One slot per eighth
   note, never wrapped and never scrolled — the pattern has to read as
   one continuous bar. So the row shrinks the slots and the gaps
   together instead of pushing the last one past the card's edge: 34px
   is the size a slot WANTS, and flex-shrink takes it down from there.
   The phone rule further down now only tightens the gap. */
.strum-arrows {
  display: flex;
  gap: clamp(0.15rem, 1.4%, 0.5rem);
  justify-content: center;
  margin: 0.9rem 0;
  min-width: 0;
}
.strum-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  flex: 0 1 34px;
  min-width: 0;
}
.strum-count {
  width: 100%;
  max-width: 34px;
  min-width: 0;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
  text-align: center;
}
.strum-arrow {
  width: 100%;
  max-width: 34px;
  min-width: 0;
  height: 34px;
  border-radius: var(--radius-small);
  background: rgba(var(--w),0.06);
  border: 1.5px solid rgba(var(--w),0.14);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-muted);
  transition: background 0.08s, border-color 0.08s, color 0.08s;
}
.strum-arrow--rest { opacity: 0.35; }

/* Starter's bars: four beat slots instead of eight eighths, so each slot is
   wider, a hold (the chord keeps ringing) reads as a softer dash, and the
   note or rest a slot begins sits underneath it in the same font the Theory
   note-value widget uses — arrows and lesson show one rhythm. */
.strum-arrows--beats .strum-step { flex: 0 1 64px; }
.strum-arrows--beats .strum-count,
.strum-arrows--beats .strum-arrow { max-width: 64px; }
.strum-arrow--hold { opacity: 0.55; }
.strum-symbol {
  min-height: 2.1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Bravura Rhythm";
  font-size: 1.6rem;
  line-height: 1;
  color: var(--text-main);
}

/* Play the TAB lights the fret it is on, inside the portal's own TAB
   block rather than a display of its own. Same accent as the pattern
   steppers' active step above, because it is the same idea: this is the
   one you play now. */
.reading-tab-example .tab-step {
  color: var(--accent);
  font-weight: 700;
}
.strum-arrow.active {
  background: rgba(216,168,79,0.16);
  border-color: var(--accent);
  color: var(--accent);
}

@media (max-width: 480px) {
  /* The slots already shrink to fit (see .strum-step's flex above); on a
     phone they also stop growing back to 34px, so eight of them plus the
     count row keep a phone-sized proportion rather than filling the card
     edge to edge. */
  .strum-count,
  .strum-arrow {
    max-width: 26px;
  }
  .strum-arrow {
    height: 26px;
    font-size: 0.9rem;
  }
}

/* Rhythm Reading Trainer — a row of note-value glyphs (quarter/eighth
   note or rest), same slot-row language as .strum-arrows/.scalep-degree
   but purely for reading, no active-step animation (nothing plays). */
.rhythmp-glyphs {
  display: flex;
  gap: clamp(0.15rem, 1.2%, 0.4rem);
  justify-content: center;
  margin: 0.9rem 0;
  font-size: 1.4rem;
  min-width: 0;
}
/* Same rule as .strum-step: the bar is one row of slots whatever the
   card is worth, so the slot is a width it PREFERS, not one it insists
   on. min-width:0 is what lets flex-shrink past the glyph's own
   min-content width; the glyph stays centred in whatever is left. */
.rhythmp-glyph {
  flex: 0 1 30px;
  width: auto;
  min-width: 0;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-main);
}
.rhythmp-glyph--rest { color: rgba(var(--w),0.5); font-size: 1rem; }
.rhythmp-glyph--held {
  color: var(--accent);
  font-size: 1.05rem;
  opacity: 0.72;
}

/* ==========================================================
   READING — COMPACT SHARED CARD SYSTEM
   ----------------------------------------------------------
   Reading uses the same three-part architecture as Tools: a fixed
   title/info header, a focused exercise stage, and one action rail at
   the bottom. The prompt zone has one shared height, so TAB, diagram,
   rhythm notation and the answer controls all start on matching lines.
   ========================================================== */
.practice-page #practice-panel-reading .tool-card--reading {
  overflow: visible;
  height: 420px;
  min-height: 420px;
  padding: 0;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.1);
  border-radius: 14px;
}

.practice-page #practice-panel-reading .reading-card-header {
  flex: 0 0 auto;
  padding: 1.1rem 1.45rem 0.9rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
}

.practice-page #practice-panel-reading .reading-card-header .tool-title {
  margin: 0;
}

.practice-page #practice-panel-reading .tool-info-popover .drill-sub {
  min-height: 0;
  max-width: none;
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.55;
}

.practice-page #practice-panel-reading .reading-card-stage {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: 1rem 1.45rem 0.95rem;
}

.practice-page #practice-panel-reading .reading-card-prompt {
  display: flex;
  flex: 0 0 9.25rem;
  align-items: center;
  justify-content: center;
  height: 9.25rem;
  min-height: 9.25rem;
  margin: 0 0 0.9rem;
}

.practice-page #practice-panel-reading .reading-card-prompt > :is(.reading-tab-example, .cswitch-display, .rhythmp-glyphs) {
  min-height: 0;
  margin: 0;
}

.practice-page #practice-panel-reading #tabTrainerTab.reading-tab-example {
  display: block;
  width: max-content;
  color: var(--text-main);
  font-size: 1.24rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.025em;
}

.practice-page #practice-panel-reading #tool-diagram-trainer .cswitch-display {
  justify-content: center;
  margin: 0;
}

.practice-page #practice-panel-reading #tool-diagram-trainer .cswitch-diagram {
  width: 116px;
}

.practice-page #practice-panel-reading #diagramTrainerDiagram svg {
  display: block;
}

.practice-page #practice-panel-reading #diagramTrainerDiagram svg line {
  stroke-width: 1.2;
}

.practice-page #practice-panel-reading #diagramTrainerDiagram svg circle {
  filter: drop-shadow(0 0 3px rgba(216, 168, 79, 0.22));
}

.practice-page #practice-panel-reading #rhythmReadGlyphs.rhythmp-glyphs {
  align-items: center;
  min-height: 0;
  margin: 0;
  font-size: 1.55rem;
}

.practice-page #practice-panel-reading #rhythmReadGlyphs .rhythmp-glyph {
  flex-basis: 33px;
  height: 44px;
}

.practice-page #practice-panel-reading .reading-card-answers {
  margin-top: 0;
}

.practice-page #practice-panel-reading .reading-card-answers .ear-options {
  margin: 0;
}

.practice-page #practice-panel-reading .reading-card-answers--chord {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.practice-page #practice-panel-reading #tool-diagram-trainer .metro-option-group {
  gap: 0.32rem;
  margin: 0;
}

.practice-page #practice-panel-reading #tool-diagram-trainer .metro-opt-row {
  overflow: visible;
  padding: 0;
}

.practice-page #practice-panel-reading #diagramAnswerRootBtns {
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.35rem;
}

.practice-page #practice-panel-reading #diagramAnswerRootBtns .metro-opt-btn,
.practice-page #practice-panel-reading #diagramAnswerRootBtns .metro-opt-btn:nth-child(n) {
  grid-column: auto;
  min-width: 0;
  min-height: 2.25rem;
  padding: 0.3rem 0;
}

.practice-page #practice-panel-reading #diagramAnswerQualityBtns {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.35rem;
}

.practice-page #practice-panel-reading #diagramAnswerQualityBtns .metro-opt-btn {
  min-height: 2.25rem;
  padding-inline: 0.15rem;
  font-size: 0.82rem;
  font-weight: 700;
}

.practice-page #practice-panel-reading #diagramAnswerQualityBtns .metro-opt-btn:disabled {
  cursor: not-allowed;
  opacity: 0.42;
}

.practice-page #practice-panel-reading #tabTrainerOptions .ear-option {
  min-width: 0;
  min-height: 2.25rem;
  padding: 0.3rem 0;
  font-size: 0.95rem;
}

.practice-page #practice-panel-reading #tabTrainerOptions.ear-options {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.35rem;
}

/* The column count follows the rhythmic GRID being read, not the widget:
   eight eighths of a bar and eight sixteenths of two beats both fit the
   default, but a triplet bar is twelve slots and 6/8 is six. --rhythm-slots
   is written by practice.js only when a pattern needs a grid other than
   eight; unset, this renders exactly as it always has. */
.practice-page #practice-panel-reading #rhythmReadOptions.rhythm-answer-strip,
.practice-page #practice-panel-rhythm #rhythmtOptions.rhythm-answer-strip {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 0.35rem;
  width: 100%;
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot {
  min-width: 0;
  min-height: 2.25rem;
  padding: 0.3rem 0;
  color: var(--text-main);
  background: rgba(var(--w), 0.035);
  border: 1px solid rgba(var(--w), 0.1);
  border-radius: var(--radius-small);
  font-family: var(--font-main);
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot:nth-child(even) {
  color: var(--text-muted);
  font-weight: 600;
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot:hover:not(:disabled),
.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot:focus-visible {
  border-color: rgba(216, 168, 79, 0.55);
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot.is-selected {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.13);
  border-color: rgba(216, 168, 79, 0.72);
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot.correct {
  color: #8fe0a8;
  background: rgba(80, 200, 120, 0.18);
  border-color: rgba(80, 200, 120, 0.62);
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot.wrong {
  color: #f0a0a0;
  background: rgba(220, 80, 80, 0.16);
  border-color: rgba(220, 80, 80, 0.58);
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot.missed {
  color: #8fe0a8;
  background: rgba(80, 200, 120, 0.06);
  border-color: rgba(80, 200, 120, 0.58);
  border-style: dashed;
}

.practice-page :is(#practice-panel-reading, #practice-panel-rhythm) .rhythm-answer-slot:disabled {
  cursor: default;
  opacity: 1;
}

.practice-page #practice-panel-reading #rhythmReadNext:disabled,
.practice-page #practice-panel-rhythm #rhythmtPlay:disabled {
  cursor: not-allowed;
  opacity: 0.42;
}

.practice-page #practice-panel-rhythm #rhythmtOptions .rhythm-answer-slot:disabled:not(.correct):not(.wrong):not(.missed) {
  opacity: 0.42;
}

.practice-page #practice-panel-reading #rhythmReadNext .rhythm-action-icon--next,
.practice-page #practice-panel-reading #rhythmReadNext[data-action="next"] .rhythm-action-icon--check {
  display: none;
}

.practice-page #practice-panel-reading #rhythmReadNext[data-action="next"] .rhythm-action-icon--next {
  display: block;
}

.practice-page #practice-panel-reading .reading-card-footer {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(150px, 1.05fr);
  align-items: center;
  gap: 0.8rem;
  padding: 0.85rem 1.45rem 0.95rem;
  border-top: 1px solid rgba(var(--w), 0.08);
}

.practice-page #practice-panel-reading .reading-card-footer .ear-status {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0.15rem;
  min-width: 0;
  min-height: 46px;
  margin: 0;
  padding: 0;
}

.practice-page #practice-panel-reading .reading-card-footer .ear-feedback {
  overflow: hidden;
  max-width: 100%;
  font-size: 0.82rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.practice-page #practice-panel-reading .reading-card-footer .ear-controls {
  display: block;
  margin: 0;
}

.practice-page #practice-panel-reading .reading-card-footer .tool-btn-primary {
  width: 100%;
  min-height: 46px;
  border-radius: 8px;
}

@media (max-width: 520px) {
  .practice-page #practice-panel-reading .reading-card-header,
  .practice-page #practice-panel-reading .reading-card-stage,
  .practice-page #practice-panel-reading .reading-card-footer {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  .practice-page #practice-panel-reading .reading-card-prompt {
    min-height: 9.25rem;
  }

  .practice-page #practice-panel-reading .reading-card-footer {
    grid-template-columns: minmax(0, 1fr) minmax(138px, 1.1fr);
  }

  .practice-page #practice-panel-reading .reading-card-footer .btn-label {
    display: inline;
  }

  .practice-page #practice-panel-reading #diagramAnswerQualityBtns .metro-opt-btn {
    font-size: 0.65rem;
  }
}

/* ==========================================================
   RHYTHM — COMPACT SHARED CARD SYSTEM
   ----------------------------------------------------------
   The three rhythm tools use the same header / exercise / action-rail
   architecture as Tools and Reading. Permanent instructions live in the
   info popover; settings that are not needed while playing stay in the
   footer menu. This keeps every card at the same compact 420px measure.
   ========================================================== */
.practice-page #practice-panel-rhythm .tool-card--rhythm {
  overflow: visible;
  height: 420px;
  min-height: 420px;
  padding: 0;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.1);
  border-radius: 14px;
}

.practice-page #practice-panel-rhythm .rhythm-card-header {
  flex: 0 0 auto;
  padding: 1.1rem 1.45rem 0.9rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
}

.practice-page #practice-panel-rhythm .rhythm-card-header .tool-title {
  margin: 0;
}

.practice-page #practice-panel-rhythm .tool-info-popover .drill-sub {
  min-height: 0;
  max-width: none;
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.55;
}

.practice-page #practice-panel-rhythm .rhythm-card-stage {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  padding: 1rem 1.45rem 0.95rem;
}

.practice-page #practice-panel-rhythm .rhythm-card-footer {
  display: grid;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.65rem;
  padding: 0.85rem 1.45rem 0.95rem;
  border-top: 1px solid rgba(var(--w), 0.08);
}

.practice-page #practice-panel-rhythm .rhythm-card-footer :is(.tool-btn-primary, .tool-btn-secondary, .rhythm-settings-trigger) {
  width: 100%;
  min-height: 46px;
  border-radius: 8px;
}

/* Recognition */
.practice-page #practice-panel-rhythm .rhythm-card-stage--recognition {
  gap: 0.7rem;
}

.practice-page #practice-panel-rhythm .rhythm-recognition-choice {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  justify-content: center;
  gap: 0.85rem;
  min-width: 0;
}

.practice-page #practice-panel-rhythm .rhythm-answer-select {
  width: 100%;
  min-width: 0;
  min-height: 46px;
  border-radius: 8px;
  font-size: 0.95rem;
}

.practice-page #practice-panel-rhythm .rhythm-answer-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.practice-page #practice-panel-rhythm .rhythm-answer-select:disabled {
  opacity: 0.5;
  cursor: default;
}

.practice-page #practice-panel-rhythm .rhythm-answer-select.is-correct {
  color: #8fe0a8;
  border-color: rgba(80, 200, 120, 0.62);
  opacity: 1;
}

.practice-page #practice-panel-rhythm .rhythm-answer-select.is-wrong {
  color: #f0a0a0;
  border-color: rgba(220, 80, 80, 0.58);
  opacity: 1;
}

.practice-page #practice-panel-rhythm .rhythm-stage-question {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.practice-page #practice-panel-rhythm #rhythmtOptions.ear-options {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.42rem;
  margin: 0;
}

.practice-page #practice-panel-rhythm #rhythmtOptions .ear-option {
  min-width: 0;
  min-height: 2.25rem;
  padding: 0.45rem 0.18rem;
  font-size: 0.69rem;
  line-height: 1.15;
  white-space: nowrap;
}

.practice-page #practice-panel-rhythm .rhythm-card-status {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 0.15rem;
  min-height: 2.5rem;
  margin: auto 0 0;
  padding: 0;
}

.practice-page #practice-panel-rhythm .rhythm-card-status .ear-feedback {
  overflow: hidden;
  max-width: 100%;
  font-size: 0.82rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.practice-page #practice-panel-rhythm .rhythm-recognition-footer {
  grid-template-columns: minmax(0, 1fr) minmax(145px, 1.08fr);
}

.practice-page #practice-panel-rhythm .rhythm-transport-secondary {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.42rem;
}

.practice-page #practice-panel-rhythm .rhythm-transport-secondary .tool-btn-secondary {
  min-width: 0;
  padding-inline: 0.45rem;
}

/* Practice Timing and Strumming share one tempo baseline. */
.practice-page #practice-panel-rhythm :is(.rhythm-card-stage--timing, .rhythm-card-stage--strum) {
  gap: 0.85rem;
  padding-top: 1.15rem;
}

.practice-page #practice-panel-rhythm :is(.rhythm-timing-visual, .rhythm-strum-visual) {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
}

.practice-page #practice-panel-rhythm .rhythm-timing-visual {
  gap: 0.4rem;
}

.practice-page #practice-panel-rhythm .clap-count-in span {
  min-height: 28px;
}

.practice-page #practice-panel-rhythm .clap-timing-labels {
  margin-bottom: 0.3rem;
}

.practice-page #practice-panel-rhythm .rhythm-tempo-block {
  flex: 0 0 auto;
}

.practice-page #practice-panel-rhythm .rhythm-tempo-block .metro-bpm-row {
  margin: 0;
}

.practice-page .rhythm-tempo-block .metro-step-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.practice-page #practice-panel-rhythm #tool-clap-tap .clap-count-in {
  width: min(210px, 100%);
  margin: 0 auto;
}

.practice-page #practice-panel-rhythm #tool-clap-tap .clap-timing-meter {
  width: 100%;
  margin: 0;
}

.practice-page #practice-panel-rhythm #tool-clap-tap .clap-timing-history {
  margin-top: 0.25rem;
}

.practice-page #practice-panel-rhythm .rhythm-timing-footer {
  grid-template-columns: 72px minmax(112px, 1.15fr) minmax(100px, 1fr);
}

.practice-page #practice-panel-rhythm .rhythm-timing-footer :is(.tool-btn-primary, .tool-btn-secondary) {
  padding-inline: 0.55rem;
  font-size: 0.8rem;
  white-space: nowrap;
}

.practice-page #practice-panel-rhythm .rhythm-footer-status {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0.1rem;
  min-width: 0;
  min-height: 46px;
  margin: 0;
  padding: 0;
}

.practice-page #practice-panel-rhythm .rhythm-footer-status :is(.ear-feedback, .ear-score) {
  overflow: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.practice-page #practice-panel-rhythm #tool-strum-patterns .strum-arrows {
  width: 100%;
  margin: 0;
}

.practice-page #practice-panel-rhythm #tool-strum-patterns .strum-count-in {
  margin: 0.35rem auto 0;
}

.practice-page #practice-panel-rhythm .strum-step {
  gap: 0.15rem;
}

.practice-page #practice-panel-rhythm .strum-arrow {
  height: 30px;
}

.practice-page #practice-panel-rhythm .rhythm-strum-footer {
  grid-template-columns: minmax(0, 0.95fr) minmax(145px, 1.2fr);
}

/* Strumming pattern is a secondary choice: keep it available without
   permanently competing with the animated arrows and tempo control. */
.practice-page #practice-panel-rhythm .rhythm-settings-menu {
  position: relative;
  min-width: 0;
}

.practice-page #practice-panel-rhythm .rhythm-settings-menu > summary {
  list-style: none;
}

.practice-page #practice-panel-rhythm .rhythm-settings-menu > summary::-webkit-details-marker {
  display: none;
}

.practice-page #practice-panel-rhythm .rhythm-settings-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.42rem;
  padding: 0.55rem 0.8rem;
  color: var(--text-main);
  background: rgba(var(--w), 0.04);
  border: 1px solid rgba(var(--w), 0.1);
  font-family: var(--font-main);
  font-size: 0.875rem;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.practice-page #practice-panel-rhythm .rhythm-settings-trigger:hover,
.practice-page #practice-panel-rhythm .rhythm-settings-menu[open] .rhythm-settings-trigger {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.07);
  border-color: rgba(216, 168, 79, 0.42);
}

.practice-page #practice-panel-rhythm .rhythm-settings-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.practice-page #practice-panel-rhythm .rhythm-settings-chevron {
  width: 14px;
  height: 14px;
  margin-left: 0.1rem;
  transition: transform 0.15s ease;
}

.practice-page #practice-panel-rhythm .rhythm-settings-menu[open] .rhythm-settings-chevron {
  transform: rotate(180deg);
}

.practice-page #practice-panel-rhythm .rhythm-settings-popover {
  position: absolute;
  z-index: 45;
  bottom: calc(100% + 0.6rem);
  left: 0;
  display: grid;
  grid-template-columns: auto minmax(130px, 1fr);
  align-items: center;
  gap: 0.75rem;
  width: min(310px, calc(100vw - 2rem));
  padding: 0.8rem;
  background: var(--bg-card);
  border: 1px solid rgba(var(--w), 0.12);
  border-radius: 9px;
  box-shadow: var(--shadow-soft);
}

.practice-page #practice-panel-rhythm .rhythm-settings-popover .metro-option-label {
  margin: 0;
  white-space: nowrap;
}

.practice-page #practice-panel-rhythm .rhythm-settings-popover .metro-select {
  width: 100%;
  min-width: 0;
}

.practice-page #practice-panel-rhythm #tool-strum-patterns:has(.rhythm-settings-menu[open]) {
  z-index: 45;
}

@media (max-width: 520px) {
  .practice-page #practice-panel-rhythm .rhythm-card-header,
  .practice-page #practice-panel-rhythm .rhythm-card-stage,
  .practice-page #practice-panel-rhythm .rhythm-card-footer {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  .practice-page #practice-panel-rhythm .rhythm-recognition-footer {
    grid-template-columns: minmax(0, 1fr) minmax(138px, 1.1fr);
  }

  .practice-page #practice-panel-rhythm .rhythm-timing-footer {
    grid-template-columns: 58px minmax(0, 1fr) minmax(0, 1fr);
  }

  .practice-page #practice-panel-rhythm .rhythm-strum-footer {
    grid-template-columns: minmax(0, 1fr) minmax(138px, 1.15fr);
  }

  .practice-page #practice-panel-rhythm .rhythm-card-footer > .tool-btn-primary .btn-label,
  .practice-page #practice-panel-rhythm .rhythm-settings-trigger .btn-label {
    display: inline;
  }

  .practice-page #practice-panel-rhythm .rhythm-card-footer :is(.tool-btn-primary, .tool-btn-secondary, .rhythm-settings-trigger) {
    height: auto;
  }

}

/* Chords shares the compact card frame; the progression stays on the
   working surface, while timing and exercise configuration live in Settings. */
.practice-page #practice-panel-chords .practice-3col > .tool-card--chords {
  height: 420px;
  min-height: 420px;
  padding: 0;
  overflow: visible;
  background: var(--bg-section);
  border-color: rgba(var(--w), 0.1);
  border-radius: 14px;
}

.practice-page #practice-panel-chords .chords-card-header {
  flex: 0 0 auto;
  padding: 1.1rem 1.45rem 0.9rem;
  border-bottom: 1px solid rgba(var(--w), 0.08);
}

.practice-page #practice-panel-chords .tool-info-popover .drill-sub {
  margin: 0;
  min-height: 0;
  font-size: 0.8rem;
  line-height: 1.55;
}

.practice-page #practice-panel-chords .chords-card-stage {
  flex: 1 1 auto;
  min-height: 0;
  padding: 1rem 1.45rem 0.95rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.practice-page #practice-panel-chords .chords-card-prompt {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 9.25rem;
  min-height: 9.25rem;
  margin: 0;
  gap: 1.15rem;
}

.practice-page #practice-panel-chords .cswitch-diagram {
  width: 116px;
}

.practice-page #practice-panel-chords .cswitch-diagram svg {
  display: block;
  max-height: 148px;
}

.practice-page #practice-panel-chords .cswitch-names {
  min-width: 0;
  min-height: 112px;
  flex: 1;
  max-width: 150px;
}

.practice-page #practice-panel-chords .cswitch-current {
  font-family: var(--font-main);
  font-size: 2.65rem;
  letter-spacing: -0.035em;
}

.practice-page #practice-panel-chords .chordp-tip {
  margin: 0.55rem 0 0;
  font-size: 0.72rem;
  line-height: 1.4;
}

.practice-page #practice-panel-chords .cswitch-count-in {
  width: 100%;
  margin: 0.8rem 0 0;
  gap: 0.3rem;
}

.practice-page #practice-panel-chords .cswitch-count-in span {
  min-height: 28px;
  font-size: 0.7rem;
}

.practice-page #practice-panel-chords .chords-selection-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 36px;
  gap: 0.6rem;
  color: var(--text-muted);
  font-size: 0.8rem;
}

.practice-page #practice-panel-chords .chords-progression {
  min-height: 72px;
}

.practice-page #practice-panel-chords .cswitch-slots {
  position: relative;
  gap: 0.45rem;
  align-items: center;
}

.practice-page #practice-panel-chords .cswitch-slot-btn {
  min-width: 42px;
  min-height: 36px;
  font-size: 0.9rem;
  padding: 0.35rem 0.6rem;
}

.practice-page #practice-panel-chords .cswitch-add-btn {
  min-height: 32px;
  padding: 0.3rem 0.45rem;
  font-size: 0.72rem;
}

.practice-page #practice-panel-chords .cswitch-slot-popover {
  width: 260px;
  min-width: 0;
  max-width: calc(100vw - 4rem);
  top: auto;
  bottom: calc(100% + 0.6rem);
  left: 0;
}

.practice-page #practice-panel-chords .chords-card-footer {
  display: grid;
  flex: 0 0 auto;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.2fr);
  align-items: center;
  gap: 0.65rem;
  padding: 0.85rem 1.45rem 0.95rem;
  border-top: 1px solid rgba(var(--w), 0.08);
}

.practice-page #practice-panel-chords .chords-card-footer :is(.tool-btn-primary, .chords-settings-trigger) {
  width: 100%;
  height: 46px;
  min-height: 46px;
  border-radius: 8px;
}

.chords-settings-menu {
  position: relative;
  min-width: 0;
}

.chords-settings-trigger {
  list-style: none;
  padding: 0.55rem 0.65rem;
  color: var(--text-main);
  background: rgba(var(--w), 0.04);
  border: 1px solid rgba(var(--w), 0.1);
  font-family: var(--font-main);
  font-size: 0.875rem;
  cursor: pointer;
}

.chords-settings-trigger::-webkit-details-marker { display: none; }
.chords-settings-chevron { margin-left: 0.15rem; line-height: 1; }
.chords-settings-menu[open] .chords-settings-chevron { transform: rotate(180deg); }
.chords-settings-trigger:hover,
.chords-settings-menu[open] .chords-settings-trigger {
  color: var(--accent);
  border-color: rgba(216, 168, 79, 0.42);
}
.chords-settings-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.chords-settings-popover {
  position: absolute;
  z-index: 45;
  bottom: calc(100% + 0.6rem);
  left: 0;
  width: min(310px, calc(100vw - 4rem));
  padding: 0.9rem;
  display: grid;
  gap: 1rem;
  background: var(--bg-card);
  border: 1px solid rgba(var(--w), 0.12);
  border-radius: 9px;
  box-shadow: var(--shadow-soft);
}

.chords-setting-field {
  display: grid;
  gap: 0.45rem;
  color: var(--text-muted);
  font-size: 0.75rem;
}

.chords-setting-field .metro-select { width: 100%; min-width: 0; }
.tool-card--chords:has(.chords-settings-menu[open], .cswitch-slot-popover) { z-index: 45; }

@media (max-width: 760px) {
  .practice-page #practice-panel-chords .practice-3col { grid-template-columns: 1fr; }
}

@media (max-width: 520px) {
  .practice-page #practice-panel-chords :is(.chords-card-header, .chords-card-stage, .chords-card-footer) {
    padding-left: 1rem;
    padding-right: 1rem;
  }
  .practice-page #practice-panel-chords .chords-card-footer .btn-label { display: inline; }
  .practice-page #practice-panel-chords .cswitch-current { font-size: 2.35rem; }
}

/* Give every count its own equal-width slot. Quarter-note answers use four
   slots and subdivisions use eight, so "1 2 3 4" remains visibly separated
   without making the longer "1 & 2 & 3 & 4 &" answers overflow. */
.rhythmp-count-row {
  display: grid;
  width: 100%;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  align-items: center;
}
.rhythmp-count-row--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.rhythmp-count {
  text-align: center;
}

/* Chord Recognition — feedback panel shown after each answer: the
   chord's name above a guitar diagram (no piano keyboard — this is a
   guitar-fretboard-first exercise), then the note-name spelling, then
   replay/next controls. */
.cr-feedback-detail {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid rgba(var(--w),0.07);
}
.cr-feedback-visuals {
  display: flex;
  justify-content: center;
}
.cr-feedback-diagram-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
}
.cr-feedback-chordname {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-main);
}
.cr-feedback-diagram svg { width: 100%; max-width: 120px; display: block; }
/* Scale Recognition's feedback piano (practicePianoSVG) — same sibling
   panel as .cr-feedback-diagram above but was missing this rule
   entirely, so on a narrow phone it had nothing keeping it inside the
   card's width. */
.cr-feedback-piano svg { width: 100%; max-width: 260px; display: block; }
#earPiano.cr-feedback-piano svg { max-width: 520px; }
.cr-feedback-notes {
  text-align: center;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--accent);
  margin: 0.7rem 0;
}
.cr-question { margin-top: 0.6rem; }

/* ==========================================================
   CHORDS categorierij
   ========================================================== */

/* Chords categorierij â†’ exact zoals Scales categorierij (.fbx-step-btn basis).
   padding/font-size/font-weight come from the shared .sv-cat-btn /
   .tab-secondary rule (~line 3153) â€” only the transparent background/
   border is Chords-specific (default .sv-cat-btn has a faint fill). */
#chordCats .sv-cat-btn {
  background: transparent;
  border: 1px solid transparent;
}
/* Color-only hover, matching Scales' .fbx-step-btn:hover exactly (no
   background tint on hover there — only on .active). */
#chordCats .sv-cat-btn:hover {
  color: var(--text-main);
}
/* Same LEVEL 2 colors as the shared .sv-cat-btn.active rule above â€” repeated
   here because the #chordCats id selector otherwise outranks it on
   background/border (the id-scoped rule above sets both to transparent). */
#chordCats .sv-cat-btn.active {
  background: rgba(216, 168, 79, 0.08);
  border-color: rgba(216, 168, 79, 0.28);
}

/* Control-group spacing comes from the shared .fb-controls group these
   rows now sit in, not from three id-level margins that outranked it. */

/* Fundamentals playback uses the shared piano actions, including touch targets. */
.theory-piano-actions .foundation-play { min-height: 44px; }
.foundation-play[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); background: rgba(216,168,79,0.1); }

/* Starter Fundamentals follows the shared Theory split: concise copy left,
   the smallest useful instrument view right. */
.starter-piano-widget,.starter-fretboard-widget { align-self:start; }
.starter-piano-widget .alpha-piano-wrap { width:100%; }
.starter-piano-widget .starter-interaction-feedback { margin:0.65rem 0 0; }
.starter-fretboard-widget [role="tabpanel"] { width:100%; }
.starter-guitar-photo { width:100%; overflow:hidden; }
.starter-guitar-photo svg { display:block; width:100%; height:auto; }
.starter-photo-fret { font:500 16px Inter,sans-serif; fill:var(--text-muted); text-anchor:middle; }
.starter-guitar-photo.is-vertical svg { max-width:22rem; max-height:min(72svh,42rem); margin:0 auto; }
.starter-guitar-photo.is-vertical .starter-photo-fret { font-size:20px; text-anchor:start; }
/* Physical demonstrations follow the learner's playing hand, just like the
   diagrams beside them. Mirror geometry only: captions and fret numbers stay
   upright. Music reading (TAB, staves, piano and time) is never mirrored. */
html[data-handed="left"] :is(#starterPosturePhoto,.posture-hand-grid .lesson-photo-img,.starter-real-neck,.starter-guitar-photo svg,.starter-controls-photo,.ga-stage) { transform:scaleX(-1); }
html[data-handed="left"] :is(.starter-guitar-photo svg,.starter-controls-photo) text { transform:scaleX(-1); transform-box:fill-box; transform-origin:center; }
html[data-handed="left"] .ga-tag { transform:translateY(-50%) scaleX(-1); }
html[data-handed="left"] .ga-tag--right { text-align:right; padding-left:0; padding-right:.4rem; }
html[data-handed="left"] .ga-tag--left { text-align:left; padding-right:0; padding-left:.4rem; }
.starter-first-chords {
  display:grid;
  grid-template-columns:repeat(6,minmax(0,1fr));
  align-items:start;
  gap:0.5rem var(--starter-chord-gap);
  max-width:calc(3 * var(--starter-play-chord-size) + 2 * var(--starter-chord-gap));
  margin-inline:auto;
}
.starter-chord-study .chord-caged-item,
.starter-first-chords .chord-caged-item {
  flex:0 1 var(--starter-chord-size);
  min-width:0;
  width:min(100%,var(--starter-chord-size));
  max-width:var(--starter-chord-size);
  justify-self:center;
}
/* Reference grids keep identical geometry and aligned nuts. Increase only
   the external teaching labels, not the entire illustration. */
#starterChordGuide svg text {
  font-size:16px;
  font-weight:500;
  fill:var(--text-muted);
}
.starter-first-chords .chord-caged-item { max-width:var(--starter-play-chord-size); }
.starter-first-chords .chord-caged-item:nth-child(-n+3) { grid-column:span 2; }
.starter-first-chords .chord-caged-item:nth-child(4) { grid-column:2 / span 2; }
.starter-first-chords .chord-caged-item:nth-child(5) { grid-column:4 / span 2; }
.starter-first-chords .chord-caged-item:nth-child(n+4) { justify-self:center; }
.platform-page [role="tabpanel"] a { color:var(--accent); }
.platform-page .theory-overview-practice-links a { color:var(--text-muted); }
.platform-page .theory-overview-practice-links a:is(:hover,:focus-visible) { color:var(--accent); }
#tool-starter-ready .theory-lesson-title { font-size:clamp(1.9rem,2.2vw,2.2rem); line-height:1.25; }
#tool-starter-ready .theory-readiness-card > p { font-size:1.05rem; line-height:1.7; }
#tool-starter-ready .theory-readiness-list { font-size:1.02rem; line-height:1.75; }
#tool-starter-ready .theory-readiness-list { gap:0.8rem; margin-block:1.3rem 1.6rem; }
#tool-starter-ready .theory-readiness-list li { font-size:inherit; line-height:inherit; }
#tool-starter-ready .theory-readiness-check { font-size:1rem; line-height:1.6; }
#tool-starter-ready .theory-overview-level-link { font-size:0.95rem; }
.starter-readiness-card { max-width:72ch; padding:clamp(1.4rem,2.6vw,2rem); }
.starter-readiness-card .theory-readiness-list { grid-template-columns:repeat(2,minmax(0,1fr)); column-gap:clamp(1.5rem,3vw,2.5rem); }
@media(max-width:900px) {
  .starter-first-chords { grid-template-columns:repeat(2,minmax(0,1fr)); }
  .starter-first-chords .chord-caged-item:nth-child(n) { grid-column:span 1; width:100%; }
  .starter-first-chords .chord-caged-item:last-child { grid-column:1 / -1; width:min(calc((100% - var(--starter-chord-gap)) / 2),var(--starter-chord-size)); }
}
@media(max-width:420px) {
  .starter-first-chords { grid-template-columns:1fr; }
  .starter-first-chords .chord-caged-item:last-child { width:min(100%,var(--starter-chord-size)); }
}
@media(max-width:760px) {
  .starter-readiness-card .theory-readiness-list { grid-template-columns:minmax(0,1fr); }
}

/* The global tabbed-page rail is declared earlier on .fb-tabs. Theory adds
   Glossary beside that scrolling list, so the neutral line belongs to their
   shared parent here and can continue behind neither child nor gap. */
.tabbed-page .theory-topic-navigation {
  border-bottom:1px solid rgba(var(--w),0.09);
}
.tabbed-page .theory-topic-navigation .fb-tabs {
  margin-bottom:0;
  border-bottom:0;
}
.tabbed-page .theory-topic-navigation .theory-glossary-action {
  margin-bottom:0;
  padding-block:0 0.6rem;
}

/* ==========================================================
   FRETBOARD PAGE — 3-level tab indentation hierarchy
   L1 (#fbTabs Notes/Chords/Scales) is the shared left content
   axis. L2 (#chordCats/#fbxCats — Open/Power/Barre, Diatonic/
   Pentatonic) and L3 (#chordTypes/#fbxTypes — Major/Minor/
   Dominant 7, C Major/A Minor) each step in 12px further.

   The three levels don't share one padding, so a flat 0/12/24
   margin on the CONTAINER box isn't enough: .fb-tab's own
   left padding (1.4rem/22.4px) is much bigger than .sv-cat-btn's
   pill padding (0.75rem/12px) or .scale-btn's (0.5rem/8px), so
   an L2 pill's visible background+border would land to the left
   of L1's own text even with a "bigger" margin. The 34px/46px
   below instead step from where L1's tab TEXT actually starts
   (22.4px in) — 22.4 + 12 and 22.4 + 24 — so each level's own
   visible edge (pill box for L2/L3, text for L1) reads as a
   real 12px step past the one above it. Scoped to fretboard.
   html's own containers only — Theory/Practice reuse
   .sv-cats/.sv-scales-row for unrelated pickers and must not
   shift. Mobile (below, ≤700px) doesn't need this compensation:
   L1/L2/L3 buttons all share the same 0.9rem left padding there,
   so a flat 0/8/16 margin already lines up correctly. */
#fbTabs {
  margin-inline-start: 0;
}
#chordCats,
#fbxCats,
#arpCats {
  margin-inline-start: 0;
}
.chord-finger-legend--below {
  text-align: left;
  margin: 0 0 1rem;
}
/* Reading Music's chord-diagram legend sits in a narrow widget column, so
   it wrapped mid-phrase ("O = open / string"). Keeping each half unbreakable
   lets the two sit side by side whenever they fit and break only at the
   separator when they don't. */
#tool-reading-chord-diagram .chord-finger-legend--below > span {
  white-space: nowrap;
}
@media (max-width: 700px) {
  /* Too narrow for both halves on one line — break there deliberately and
     drop the now-dangling separator. */
  #tool-reading-chord-diagram .chord-finger-legend--below .fretboard-legend-second {
    display: block;
  }
  #tool-reading-chord-diagram .chord-finger-legend--below .fretboard-legend-separator {
    display: none;
  }
}
#fb-panel-chords .drill-sub {
  margin-bottom: 0.4rem;
}

/* Compact chord collections (Jappe, 30 September). The canonical SVG and
   its labels stay intact. A readable floor determines wrapping, while a
   shared cap stops a short row becoming oversized. Only broad collections
   use five columns; three inversions remain a three-column comparison.
   Theory and the separate CAGED tab retain their own composition. */
#fb-panel-chords {
  --chord-diagram-min: 14rem;
  --chord-diagram-max: 250px;
}
#fb-panel-chords #chordDiagram {
  container: chord-collection / inline-size;
}
#fb-panel-chords .chord-caged-row {
  --chord-gap: 1rem;
  --chord-columns: 3;
  column-gap: var(--chord-gap);
  row-gap: var(--fb-gap-neck);
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(100%, max(var(--chord-diagram-min), (100% - 2 * var(--chord-gap)) / 3)), 1fr)
  );
}

/* Center a shorter final row; size follows item count, never chord names
   or learning level. No fixed five-track grid on narrow screens. */
#fb-panel-chords .chord-caged-row:has(> :nth-child(4)) {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
#fb-panel-chords .chord-caged-row:has(> :nth-child(4)) > .chord-caged-item {
  /* The rounding allowance prevents an extra wrap at exact-fit widths. */
  flex: 0 0 min(100%, max(var(--chord-diagram-min), (100% - (var(--chord-columns) - 1) * var(--chord-gap)) / var(--chord-columns) - 1px));
}
/* Five 14rem diagrams plus four 1rem gutters, based on the content width
   (also responds to a collapsed sidebar), not a device breakpoint. */
@container chord-collection (min-width: 74rem) {
  #fb-panel-chords .chord-caged-row:has(> :nth-child(4)) {
    --chord-columns: 5;
  }
}
#fb-panel-chords .chord-caged-row .chord-caged-item svg {
  max-width: var(--chord-diagram-max);
}

#fb-panel-chords .chord-caged-item {
  min-width: 0;
  padding-inline: 0;
  gap: calc(var(--fb-gap-row) / 2);
}
#fb-panel-chords .chord-grid-label {
  font-size: var(--type-item);
  line-height: 1.4;
}
#fb-panel-chords .chord-caged-item svg {
  width: 100%;
}

/* Approved Starter comparison variant: preserve E/Em and A/Am as pairs
   on narrow collections. Other levels keep their 14rem reading floor. */
@container chord-collection (max-width: 44rem) {
  .fretboard-page[data-fb-level="starter"] #fb-panel-chords .chord-caged-row {
    --chord-columns: 2;
    --chord-diagram-min: 0px;
    row-gap: var(--fb-gap-group);
  }
}
@container chord-collection (max-width: 18rem) {
  .fretboard-page[data-fb-level="starter"] #fb-panel-chords .chord-caged-row {
    --chord-columns: 1;
  }
}

/* Chords has a caption-only diagram header. Align its compact tones and
   formula with the right edge, matching the metadata side of positioned
   fretboard diagrams without inventing an empty left-hand control. */
.fretboard-page #fb-panel-chords .fb-diagram-head {
  justify-content: flex-end;
}
.fretboard-page #fb-panel-chords #chordInfoBar {
  text-align: right;
}
.fretboard-page #fb-panel-chords #chordInfoBar .caged-formula-row {
  align-self: stretch;
  justify-content: flex-end;
  width: 100%;
}
.fretboard-page #fb-panel-chords #chordInfoBar .caged-formula-row > div {
  justify-content: flex-end;
  align-items: flex-end;
}

body.fretboard-page[data-fb-level="intermediate"] #fb-panel-chords .fb-diagram-head {
  justify-content: flex-start;
  align-items: center;
}
body.fretboard-page[data-fb-level="intermediate"] #chordInfoBar {
  flex: 0 1 auto;
  text-align: left;
}
body.fretboard-page[data-fb-level="intermediate"] #fb-panel-chords #chordInfoBar .caged-formula-row,
body.fretboard-page[data-fb-level="intermediate"] #fb-panel-chords #chordInfoBar .caged-formula-row > div {
  justify-content: flex-start;
  align-items: flex-start;
}
body.fretboard-page[data-fb-level="intermediate"] #chordInfoBar .caged-formula-row > div {
  flex-direction: column;
}
body.fretboard-page[data-fb-level="intermediate"] #chordInfoBar .sv-formula + .sv-formula::before {
  content: none;
}
body.fretboard-page:is([data-fb-level="beginner"], [data-fb-level="intermediate"]) .chord-formula-cell {
  display: inline-block;
  width: 2.5em;
  text-align: center;
}
.fretboard-page[data-fb-level="beginner"] .chord-card-formula {
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1.6;
  text-align: center;
  margin: 0;
}
.fretboard-page[data-fb-level="intermediate"] #cagedIntInfoBar {
  max-width: 100%;
  overflow-x: auto;
}
.fretboard-page[data-fb-level="intermediate"] #cagedIntInfoBar .sv-formula {
  white-space: nowrap;
}

.fretboard-page[data-fb-level="intermediate"] .chord-triad-overview {
  /* Contain off-screen accessible labels within this horizontal scroller. */
  position: relative;
  width: 100%;
  overflow-x: auto;
}
.fretboard-page[data-fb-level="intermediate"] .chord-triad-heading,
.fretboard-page[data-fb-level="intermediate"] .chord-triad-group {
  display: grid;
  grid-template-columns: repeat(3, minmax(14rem, 1fr));
  gap: var(--chord-gap, 1rem);
  min-width: 44rem;
  align-items: start;
}
.fretboard-page[data-fb-level="intermediate"] .chord-triad-group {
  padding-bottom: 1.5rem;
}
.fretboard-page[data-fb-level="intermediate"] .chord-triad-heading {
  text-align: center;
  padding-bottom: 0.75rem;
}
.fretboard-page[data-fb-level="intermediate"] .chord-triad-formula {
  margin-top: 0.3rem;
  color: var(--text-muted);
  font-size: 0.8rem;
  line-height: 1.5;
}
.fretboard-page[data-fb-level="intermediate"] .chord-triad-group .chord-caged-item svg {
  width: 100%;
  max-width: var(--chord-diagram-max);
  height: auto;
}
.fretboard-page #fb-panel-chords .chord-card-actions {
  margin-top: 0;
}
.fretboard-page #fb-panel-chords .chord-caged-item svg {
  width: 100%;
  max-width: var(--chord-diagram-max);
  height: auto;
  flex-shrink: 0;
}

/* THE GAP YIELDS FIRST. Narrowing the gutter buys width for the
   diagrams without dropping a column, so it is the cheaper move and it
   happens first; the column count only gives way further down, when the
   floor above finally binds. Setting --chord-gap rather than column-gap
   is what keeps the two in step - both track formulas divide by this
   same number, so a gutter change cannot silently shift the width at
   which a column is dropped. */
@media (max-width: 900px) {
  #fb-panel-chords .chord-caged-row {
    --chord-gap: 0.6rem;
  }
}

@media (max-width: 600px) {
  #fb-panel-chords .chord-caged-row {
    --chord-gap: 0.35rem;
  }
}
#chordTypes,
#fbxTypes,
#arpItems {
  margin-inline-start: 0;
}

.chord-nav-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}
/* On Fretboard this row is one of the shared .fb-controls groups, so the
   group's alignment and rhythm win — see the CONTROL GROUP block at the
   end of this file. Theory keeps the rule above untouched. */
.fretboard-page .chord-nav-row.fb-controls {
  align-items: flex-end;
  gap: var(--fb-gap-row) var(--fb-gap-group);
  margin-bottom: var(--fb-gap-neck);
}
.chord-nav-row #chordCats,
.chord-nav-row #chordTypes {
  margin-bottom: 0;
}
/* Left-aligned like every other control row on the page. This used to
   push the Type row to the far right of the column, which read as a
   toolbar and put Chords' second control on an edge no other tab shares. */
.chord-nav-row #chordTypes {
  justify-content: flex-start;
}
.chord-nav-row .chord-voicing-select {
  margin-top: 0;
}

/* CHORDS: L2 UNDER L1. #chordCats is the harmonic family row (Major /
   Minor / Dominant / Diminished / Suspended / Augmented / Power) and
   #chordTypes is that family's own suffix row plus the compact Shape
   dropdown. They are two LEVELS of one choice, not siblings, so the
   second takes a full-width row underneath the first:

       ROOT
       [ C ]   [ Major ] [ Minor ] [ Dominant ] ...
       [ 6 ] [ 6/9 ] [ add9 ] [ maj7 ] [ maj9 ] ...   Shape [ Open ]

   The same break #chordIntFamily, #cagedIntLayers, #intervalsFamily and
   Diatonic's #fbxTypes already use.

   :not(.chord-nav-row--stacked) because BEGINNER is a COLUMN, where
   flex-basis is the cross-size and 100% would set a height instead of a
   width. Beginner already reads L2-under-L1 from that column, so it
   needs nothing here and must not be touched by this. */
.fretboard-page .chord-nav-row.fb-controls:not(.chord-nav-row--stacked) > #chordTypes {
  flex-basis: 100%;
}

/* Beginner Chords: family row (Open/Power/Barre, #chordCats) stacked
   directly above its own quality row (Major/Minor/Dominant 7, #chordTypes),
   both left-aligned. Intermediate Chords uses its dedicated hierarchy:
   family first, then Root + qualities on the second row. Advanced/Expert's
   QUALITY_GROUPS tabs +
   compact "Shape" dropdown keep the original side-by-side .chord-nav-row
   layout above — this modifier (toggled only for Beginner, see
   renderChordBuilder() in theory.js) overrides just the row axis and
   #chordTypes' right-alignment, same override the existing ≤700px rule
   below already applies unconditionally to every level; this just extends
   it to Beginner at every width, not only on narrow screens. */
/* .fretboard-page .chord-nav-row.fb-controls (two classes) otherwise
   outranks the plain .chord-nav-row--stacked class below and its
   align-items: flex-end wins, pushing Beginner's Family/Type buttons to
   the right edge of the column instead of stacking left. Matching that
   selector here, not !important, keeps this an ordinary specificity
   fight the cascade can still be reasoned about. */
.fretboard-page .chord-nav-row.fb-controls.chord-nav-row--stacked {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--fb-gap-row);
}
/* Nothing to un-right-align any more: the base row is left-aligned like
   every other control row on the page. */

/* AN EMPTY CONTROL ROW IS REMOVED, NOT EMPTIED — the same rule the
   diagram header already follows, and for the same reason: a flex
   container whose children are both zero-height still renders its own
   `gap` between them.

   BOTH children must be empty for the whole row to go — a level that
   drops one row and keeps the other still needs the container. */
.fretboard-page .chord-nav-row:has(> #chordCats:empty):has(> #chordTypes:empty) {
  display: none;
}
/* ONE empty child still must not cost a gap on its own. Starter has no
   family tabs (its one chord family draws no tab row — see
   basicFamilyIds() in theory.js) but, since the Minor/Major split, DOES
   have quality tabs, so #chordCats sits empty beside a non-empty
   #chordTypes — exactly the single-empty-child case the rule above does
   not reach. Individually removing whichever side is empty keeps the
   stacked row's own `gap` (chord-nav-row--stacked above) from opening a
   blank strip above the tabs that are actually showing. */
.fretboard-page .chord-nav-row > #chordCats:empty,
.fretboard-page .chord-nav-row > #chordTypes:empty {
  display: none;
}

/* Intermediate Chords > Triads. The Strings row is a control between the control group and the
   diagrams, so it takes no margin: the group above already ends on
   --fb-gap-neck and the diagram below begins on it. */
.fretboard-page #chordTriadStringsRow {
  margin: 0;
}
@media (max-width: 700px) {
  /* Nothing tab-specific left to tighten here: the whole page's vertical
     rhythm is four tokens, and the mobile block below adjusts those. */
}

/* Intermediate Intervals carried its own two margins here (info 1.25rem,
   neck 1.5rem, both tightened again on mobile) from when its info block
   sat above the neck. It has none now: the shared .sv-board-wrap and
   .sv-info rules give it the identical rhythm every other tab has. */

.chord-voicing-select {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 0.65rem;
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 600;
}
.chord-voicing-select select {
  appearance: auto;
  color-scheme: dark;
  color: var(--text-main);
  background: rgba(var(--w),0.05);
  border: 1px solid rgba(var(--w),0.12);
  border-radius: 8px;
  padding: 0.38rem 1.8rem 0.38rem 0.65rem;
  font: inherit;
}
.chord-voicing-select select:hover,
.chord-voicing-select select:focus-visible {
  border-color: rgba(216,168,79,0.78);
  box-shadow: 0 0 0 2px rgba(216,168,79,0.12);
  outline: none;
}
.chord-voicing-select option {
  color: #f0f0f0;
  background: #202020;
  opacity: 1;
}
.chord-voicing-select option:checked,
.chord-voicing-select option:hover {
  color: #f6d98f;
  background: #342a19;
}
.chord-voicing-select option:disabled {
  color: #777;
}

/* Exact Fretboard playback marker. The fill remains untouched so roots and
   blue notes keep their own colours; only a ring/glow and slight scale are
   added while that exact SVG position is sounding. */
.sv-dot {
  transform-box: fill-box;
  transform-origin: center;
}
.sv-dot.is-sequence-active {
  transform: scale(1.09);
  filter: drop-shadow(0 0 5px rgba(255,226,153,0.95));
}
.sv-dot.is-sequence-active > circle {
  stroke: rgba(255,245,214,0.98);
  stroke-width: 3px;
}
/* Intermediate Notation: the shared frets are visually the bridge between
   two neighbouring position ranges, without introducing another neck size. */
.reading-overlap-map {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  gap: 0.4rem;
  width: 100%;
}
.reading-overlap-position,
.reading-overlap-shared {
  display: grid;
  place-items: center;
  gap: 0.2rem;
  min-height: 76px;
  padding: 0.55rem;
  border: 1px solid rgba(var(--w),0.14);
  border-radius: var(--radius-small);
  background: rgba(var(--w),0.045);
  color: var(--text-muted);
  font-size: 0.72rem;
}
.reading-overlap-shared {
  border-color: rgba(216,168,79,0.65);
  background: rgba(216,168,79,0.12);
  color: var(--accent);
}
.reading-overlap-map b {
  color: var(--text-main);
  font-size: 0.9rem;
}
.theory-neck-context {
  width: 100%;
}
body[data-fb-level="intermediate"] .iv-chips {
  flex-wrap: wrap;
  overflow: visible;
}
/* The circle is the main visual here; chords sit beneath it, not in a
   competing narrow column. Its diameter follows the available lesson space. */
body[data-fb-level="intermediate"] .cof-wrapper {
  flex-direction: column;
  align-items: center;
}
body[data-fb-level="intermediate"] .cof-svg {
  max-width: 440px;
}
body[data-fb-level="intermediate"] .cof-chords-wrap {
  width: 100%;
}
body[data-fb-level="intermediate"] #tool-reading-movable-diagrams .theory-widget-row {
  flex-direction: column;
  align-items: center;
}
body[data-fb-level="intermediate"] #readingMovableChord {
  width: 180px;
  max-width: 100%;
}
body[data-fb-level="intermediate"] #readingOverlapScale {
  width: 100%;
}
body[data-fb-level="intermediate"] #tool-songform .song-struct-timeline {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 7rem), 1fr));
}
body[data-fb-level="intermediate"] #tool-songform .song-struct-timeline--rows-of-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
body[data-fb-level="intermediate"] #tool-songform .struct-block {
  min-width: 0;
  min-height: 64px;
  padding: 0.6rem 0.35rem;
}
body[data-fb-level="intermediate"] #tool-songform .struct-block-label {
  font-size: 0.85rem;
}
.theory-key-reference {
  width: 100%;
  padding-block: 1rem;
}
.theory-key-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
}
.theory-key-pair > div {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.theory-key-pair span {
  color: var(--text-muted);
  font-size: 0.85rem;
}
.theory-key-pair strong {
  font-size: 1.6rem;
  color: var(--text-main);
}
.theory-key-signature {
  margin: 1.5rem 0 0;
  line-height: 1.8;
  overflow-wrap: anywhere;
}
.theory-technique-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: 1.25rem;
  width: 100%;
}
.theory-technique-grid figure {
  margin: 0;
  padding-block: 0.75rem;
  border-top: 1px solid rgba(var(--w), 0.12);
}
.theory-technique-grid figcaption {
  font-size: 0.85rem;
  color: var(--text-muted);
}
.theory-technique-grid pre {
  margin: 0.5rem 0 0;
  font: 1.05rem/1.6 "Courier New", monospace;
  color: var(--text-main);
}
.rhy-recap {
  margin-top: 1rem;
  border-top: 1px solid rgba(var(--w), 0.12);
}
.rhy-recap > summary {
  padding: 1rem 0.25rem;
  min-height: 44px;
  color: var(--text-main);
  cursor: pointer;
  font-size: 0.85rem;
}
.rhy-recap > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.theory-reference {
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.theory-reference summary {
  cursor: pointer;
  width: fit-content;
  min-height: 44px;
  align-content: center;
}
.theory-reference p { margin-top: 0.65rem; }
.theory-reference summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* Shared Theory choice rows: view tabs and listening choices look alike,
   but retain their tab/pressed semantics and existing audio behaviour.
   Photo selectors, page navigation, switches and notation-row playback
   intentionally remain separate component families. */
.theory-widget-tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 0 1.5rem;
  width: 100%;
  max-width: 100%;
  margin: 0 0 0.75rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  overflow: visible;
}
/* One tab design in the whole portal (Jappe, 13 October 2026): the chosen tab is gold text,
   without a frame or an underline of its own; the visual sits in its own framed panel below. */
.theory-widget-tabs[hidden] { display: none; }
.theory-widget-tabs button,
.theory-lesson-widget .theory-widget-tabs button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 44px;
  min-height: 44px;
  max-width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  margin-bottom: 0;
  background: transparent;
  color: var(--text-muted);
  font: 600 var(--type-small)/1.4 var(--font-main);
  letter-spacing: 0.01em;
  white-space: normal;
  cursor: pointer;
}
.theory-widget-tabs button:is(.active, [aria-selected="true"], [aria-pressed="true"]) {
  color: var(--accent);
  background: transparent;
}
.theory-widget-tabs button:hover:not(.active):not([aria-selected="true"]):not([aria-pressed="true"]) {
  color: var(--text-main);
  background: transparent;
}
.theory-widget-tabs button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -3px;
}
/* Approved design D: one quiet surface connects choices and their visual.
   Nested mode families keep one outer frame, not a stack of bordered cards. */
.theory-widget-panel {
  min-width: 0;
  padding: 1.25rem clamp(8px, 2vw, 16px);
  border: 1px solid rgba(var(--w), 0.14);
  border-radius: var(--radius-card);
  background: var(--bg-section);
}
.theory-widget-panel .theory-widget-panel {
  padding-inline: 0;
  padding-bottom: 0;
  border: 0;
  border-radius: 0;
}
/* Notation needs the horizontal space for labels plus a complete bar. */
.theory-widget-panel:has(> .rhy-tree) { padding-inline: 8px; }
.theory-widget-panel > :first-child { margin-top: 0; }
.theory-widget-panel > :last-child { margin-bottom: 0; }
.feel-comparison .theory-widget-tabs { flex-wrap: wrap; margin-bottom: 0; }
.feel-comparison .theory-widget-tabs button { white-space: normal; }
/* Matching columns preserve diatonic/pentatonic comparison; the individual
   choices use the same shared styling as the other widget rows. */
#scaleTypeChips.theory-widget-tabs { width: 100%; gap: 0; }
.theory-example-result {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.45rem 0.75rem;
  margin: 1rem 0 0;
  color: var(--text-muted);
  font-size: 0.85rem;
}
.theory-example-result strong { color: var(--text-main); font-weight: 500; }
#ivChips .iv-group { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.theory-lesson-widget .iv-chip { min-height: 32px; padding: 0.3rem 0.65rem; }
@media (pointer: coarse), (max-width: 600px) {
  .theory-lesson-widget .iv-chip,
  .theory-view-tabs button { min-height: 44px; min-width: 44px; }
}
/* Result lines use their badges and labels for hierarchy. A separate grey
   panel made the same kind of feedback look unrelated across lessons. */
.theory-lesson-widget .iv-info {
  margin-top: 0.75rem;
  min-height: 0;
  padding: 0.85rem 0;
  border-top: 0;
  border-radius: 0;
  background: none;
}
body[data-fb-level="intermediate"] #ivInfo .iv-info-text { gap: 0.5rem; }
body[data-fb-level="intermediate"] #ivInfo .iv-char { display: none; }
body[data-fb-level="intermediate"] #ivInfo .iv-notes {
  margin-left: 0.35rem;
  color: var(--text-main);
  font-size: 0.85rem;
  font-weight: 500;
}
.theory-note-strip {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.4rem;
  width: 100%;
  padding: 0;
  margin: 0 0 1.25rem;
  list-style: none;
}
.theory-note-strip li {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 0.6rem;
  min-height: 92px;
  border: 1px solid rgba(var(--w), 0.14);
  border-radius: 6px;
  background: rgba(var(--w), 0.025);
}
.theory-note-strip strong { font-size: clamp(0.85rem, 1.25vw, 1.1rem); font-weight: 600; }
.theory-note-strip span { font-size: 0.65rem; color: var(--text-muted); }
.theory-note-strip .is-tonic {
  border-color: var(--root-accent);
  box-shadow: inset 0 -2px var(--root-accent);
}
.theory-note-strip .is-tonic strong { color: var(--root-accent); }
.theory-note-strip .is-tonic span { color: var(--text-main); }

/* The chord/arpeggio pair that used to live under the TAB example was
   folded into the example itself (one block, three labelled sections),
   so .reading-tab-compare and its label class went with it. The section
   labels now sit inside the <pre> and use .tab-step for their accent. */

/* Chords L3 quality tabs (#chordTypes — Major/Minor/Dominant 7) render
   both a full and a compact label per button (theory.js renderOpenGrid/
   renderBarreGrid); CSS toggles which one shows instead of swapping text
   or duplicating buttons, so the same control/value keeps representing
   the same chord quality at every width. Desktop/tablet show the full
   label; the existing mobile breakpoint below switches to the compact
   one. Scoped to #chordTypes only — Scales' C Major/A Minor (#fbxTypes)
   don't use this full/compact split and keep their plain text label. */
#chordTypes .cq-compact {
  display: none;
}

/* ==========================================================
   FRETBOARD PAGE — MOBILE
   Scoped to fretboard.html's own containers (#fbTabs, #fbGlobalBar,
   #fb-panel-*) only — Theory/Learn/Practice reuse .scale-btn/.sv-cat-btn
   for their own pickers and must not be touched by this. Desktop/tablet
   layout is untouched (no rules outside the media query below).
   ========================================================== */
@media (max-width: 700px) {
  /* CAGED's own pickers keep their pre-existing bigger, bolder mobile
     tap targets untouched (a deliberate CAGED-specific choice, not part
     of the Notes/Chords/Scales hierarchy this task covers). */
  #fb-panel-caged .scale-btn,
  #fb-panel-caged .sv-cat-btn,
  #fb-panel-caged .fbx-step-btn {
    padding: 0.55rem 0.9rem;
    font-size: 0.85rem;
    min-height: 40px;
  }

  /* Root/quality/type pickers — bigger tap targets (~40px+) instead of
     the ~28px desktop pill, and room to wrap instead of cramming, but
     WITHOUT bumping the font past the LEVEL 3 canonical size (0.8125rem)
     — a bigger box with the same small text keeps this tier reading as
     subordinate to LEVEL 1/2 even on mobile. #fb-panel-chords's Major/
     Minor/Dominant 7 tabs (.scale-btn) are the same tertiary picker as
     Scales' C Major/A Minor tabs — included here so both get the same
     mobile bump instead of only one of them growing. Chords' and
     Scales' LEVEL 2 category row (Open/Power/Barre, Diatonic/
     Pentatonic) is scoped out below into its own shared rule. */
  #fbGlobalBar .scale-btn,
  #fb-panel-scales .scale-btn,
  #fb-panel-chords .scale-btn,
  #fb-panel-arpeggios .scale-btn {
    padding: 0.55rem 0.9rem;
    min-height: 40px;
  }

  /* LEVEL 3 (Major/Minor/Dominant 7, C Major/A Minor) must read clearly
     smaller than LEVEL 2's 0.8125rem (13px) — the shared padding/
     min-height bump above no longer relied on font-size to separate the
     tiers, so give L3 its own slightly smaller size here. Scoped past
     the rule above so it wins the cascade without touching #fbGlobalBar's
     root-note pills (a different, non-hierarchy picker). */
  #fb-panel-scales .scale-btn,
  #fb-panel-chords .scale-btn,
  #fb-panel-arpeggios .scale-btn {
    font-size: 0.78rem;
  }

  /* Chords L3 quality tabs switch to the compact label (Maj/Min/Dom7) at
     this breakpoint — "Dominant 7" is the one label wide enough to wrap
     onto its own line at narrow widths, which reads as a stray fourth
     tier. Same button/value, just the shorter of the two spans theory.js
     always renders (see the desktop rule ~line 11099). */
  #chordTypes .cq-full {
    display: none;
  }
  #chordTypes .cq-compact {
    display: inline;
  }

  #fbGlobalBar .sv-roots .scale-btn {
    padding: 0.45rem 0.7rem;
    font-size: 0.85rem;
    min-height: 40px;
  }

  /* LEVEL 2 category row — Chords' Open/Power/Barre (.sv-cat-btn) and
     Scales' Diatonic/Pentatonic (.fbx-step-btn, #fbxCats) are two
     different classes for the exact same tier, both already sharing the
     canonical 0.8125rem/0.32rem 0.75rem pill (see ~line 3153) — one
     shared rule here just adds a bit more mobile tap-target room, so
     they can't drift again. Also reaches #fbxPositions (1/2/3/4/5/open/
     all), which shares .fbx-step-btn — same lighter tier, same fix.
     Font-size is intentionally NOT bumped here — the canonical 13px
     already stays clearly below .fb-tab's 13.6px mobile size. */
  #fb-panel-chords .sv-cat-btn,
  #fb-panel-scales .fbx-step-btn,
  #fb-panel-arpeggios .fbx-step-btn {
    padding: 0.4rem 0.9rem;
    min-height: 35px;
  }

  /* Root row / quality row: allow wrapping so the label never forces
     the picker into a squeeze on narrow phones. */
  .fb-root-row,
  .fb-type-row {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }

  /* No mobile margin of their own: both rows sit inside the shared
     .fb-controls group, which carries the whole page's rhythm at every
     width. */

  /* Same L1/L2/L3 indentation hierarchy as desktop. L1/L2/L3 buttons
     all share the same 0.9rem (14.4px) left padding on mobile, but L1
     has NO visible box (plain text, no pill background) while L2/L3
     are filled pills — so an 8px/16px margin still put the L2 pill's
     visible background/border to the left of where L1's text actually
     starts (8 < 14.4). 26px clears L1's 14.4px text inset by a visible
     ~12px, then L3 steps another 12px past L2's own pill edge. */
  #fbTabs {
    margin-inline-start: 0;
  }
  #chordCats,
  #fbxCats,
  #arpCats {
    margin-inline-start: 0;
  }
  #chordTypes,
  #fbxTypes,
  #arpItems {
    margin-inline-start: 0;
  }

  /* One column on a phone. The gap is the shared row token, not a
     mobile-only number, so the stack reads at the same rhythm the page
     uses everywhere else. */
  .chord-nav-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--fb-gap-row);
  }

  /* Chord diagram: more of the available width now that the page is a
     single column — larger diagram reads easier at arm's length. */
  #fb-panel-chords .chord-diagram-wrap svg {
    max-width: 280px;
  }

  /* 1-per-row below tablet width so each chord diagram gets real size
     instead of shrinking to fit 2 across.

     The Fretboard's CAGED row is deliberately NOT in this rule any more.
     C-A-G-E-D is one series read across, so it never wraps and never
     shrinks; below the width where five fit, that row scrolls inside
     itself at a fixed shape width (see THE FIVE CAGED SHAPES above).
     Collapsing it to one column was the old behaviour and is exactly
     what turned "further up the neck" into "further down the page".

     The Chords tab is deliberately NOT in this rule either: it decides
     its own column count from a minimum diagram width rather than from a
     breakpoint (see #fb-panel-chords .chord-caged-row), and at 700px it
     still has room for several. Collapsing it to one here would throw
     that away and hand a phone one enormous diagram per screen. Other
     pages' .chord-caged-row still collapses. */
  .chord-caged-row:not(#fb-panel-chords .chord-caged-row) {
    grid-template-columns: 1fr !important;
  }
}

/* ==========================================================
   LEFT-HANDED — mirrors fretboard/chord diagrams for data-handed="left"
   (Settings > Handedness, applied to <html> by handedness.js). Beginner's
   diagrams (open-chord grid, Practice's Finding-the-Note/Note-Naming/
   String-Note-Challenge necks, Fretboard's Notes/Scales necks) all share
   the same handful of wrapper classes, so one rule covers them without
   touching any per-widget geometry code. .reading-chord-example joined them on
   2026-08-25: Theory's in-lesson chord diagrams (Reading Music's E/A/D,
   and Starter's Em) are the same cbBuildSVG() output as the Fretboard's
   grid and had simply never been listed, so a left-handed player got a
   mirrored Fretboard and an unmirrored lesson.

   Theory's own neck diagrams (Fundamentals/Melody/Reading/Harmony/
   Chords/Arpeggios necks, plus the CAGED island necks) draw straight
   into plain id'd/classed containers with no shared wrapper class of
   their own, so they're listed explicitly by id/class instead. The
   dominant family (#dominantNeck, #dominantPentaNeck,
   #theoryArpDominantNeck) and #cagedShapes joined them on 2026-09-07
   (Theory v1, PR 1, defect A2): their major/minor counterparts were
   listed and they were not, so a left-handed learner got a mirrored
   major diagram beside an unmirrored dominant one. The Time Signatures
   example now uses semantic HTML and needs no handedness rule.

   The trick: flip the whole SVG (strings/frets/dots swap sides), then
   flip every <text> a second time so labels land back upright instead
   of mirrored — two flips cancel for the glyphs, one flip remains for
   the layout. transform-box:fill-box makes each text element mirror
   around its own box instead of the SVG's (0,0) origin. ========================================================== */
[data-handed="left"] .chord-diagram-wrap svg,
[data-handed="left"] .reading-chord-example svg,
[data-handed="left"] .theory-grips svg,
[data-handed="left"] .practice-neck-wrap svg,
[data-handed="left"] .sv-board-wrap svg,
[data-handed="left"] .caged-island-neck-wrap svg,
[data-handed="left"] :is(#starterOpenStringNeck, #scaleNeck,
    #readingScaleNeck, #majorNeck, #majorPentaNeck,
    #minorNeck, #minorPentaNeck, #dominantNeck, #dominantPentaNeck, #bluesNeck,
    #chordNeckMajor, #chordNeckMinor, #chordNeckBarre, #chordNeckBarreMinor,
    #theoryArpMajorNeck, #theoryArpMinorNeck, #theoryArpDominantNeck,
    #chromaticNeck, #ivExactNeck, #intIntervalsNeck, #majorScaleNeck, #intermediateFretboardNavNeck,
    #intermediateArpeggioDom7, #intermediateArpeggioMaj7,
    #intermediateArpeggioMin7, #cagedShapes) svg {
  transform: scaleX(-1);
}
[data-handed="left"] .chord-diagram-wrap svg text,
[data-handed="left"] .reading-chord-example svg text,
[data-handed="left"] .theory-grips svg text,
[data-handed="left"] .practice-neck-wrap svg text,
[data-handed="left"] .sv-board-wrap svg text,
[data-handed="left"] .caged-island-neck-wrap svg text,
[data-handed="left"] :is(#starterOpenStringNeck, #scaleNeck,
    #readingScaleNeck, #majorNeck, #majorPentaNeck,
    #minorNeck, #minorPentaNeck, #dominantNeck, #dominantPentaNeck, #bluesNeck,
    #chordNeckMajor, #chordNeckMinor, #chordNeckBarre, #chordNeckBarreMinor,
    #theoryArpMajorNeck, #theoryArpMinorNeck, #theoryArpDominantNeck,
    #chromaticNeck, #ivExactNeck, #intIntervalsNeck, #majorScaleNeck, #intermediateFretboardNavNeck,
    #intermediateArpeggioDom7, #intermediateArpeggioMaj7,
    #intermediateArpeggioMin7, #cagedShapes) svg text {
  transform: scaleX(-1);
  transform-box: fill-box;
  transform-origin: center;
}

/* Mobile horizontal necks: the ≤700px "stay horizontal" block further up
   resets transform to none on these wraps, which beats the general
   [data-handed="left"] .sv-board-wrap rule above and would otherwise
   leave the neck unmirrored while its <text> labels (matched by that
   lower-specificity general rule) flipped on their own — normal-looking
   neck, backwards labels. :not(.fb-vertical) keeps this clear of the
   rotated portrait layout, which has its own mirror in the ORIENTATION
   SYSTEM block. */
@media (max-width: 700px) {
  [data-handed="left"] #fb-panel-scales .sv-board-wrap:not(.fb-vertical) svg,
  [data-handed="left"] #fb-panel-arpeggios .sv-board-wrap:not(.fb-vertical) svg,
  [data-handed="left"] #fb-panel-caged .sv-board-wrap:not(.fb-vertical) svg,
  [data-handed="left"] #fb-panel-scales .sv-board-wrap:not(.fb-vertical) svg text,
  [data-handed="left"] #fb-panel-arpeggios .sv-board-wrap:not(.fb-vertical) svg text,
  [data-handed="left"] #fb-panel-caged .sv-board-wrap:not(.fb-vertical) svg text {
    transform: scaleX(-1);
  }
  [data-handed="left"] #notesMap:not(.fb-vertical) svg,
  [data-handed="left"] #notesMap:not(.fb-vertical) svg text {
    transform: scaleX(-1);
  }
}


/* ==========================================================
   SIDEBAR — nav labels in the tapered width range
   ----------------------------------------------------------
   At the full 260px every nav label fits on one line with room to spare.
   At the tapered 220px longer translated labels can approach the
   available width and wrap to two lines.

   Rather than widening the sidebar back (which is the whole point of the
   taper) or truncating the label (which is content), the label simply
   gets the room back from its own chrome: less horizontal padding on the
   nav item and a slightly smaller label, in the tapered range only. Above
   1280px nothing here applies and the sidebar is exactly what it was.

   Scoped to .platform-nav .nav-link, so the account block, the logo and
   the icon rail keep the sidebar's shared edge padding and stay aligned
   with each other. The collapsed rail (1rem, higher specificity) and the
   <=768px takeover (!important) both still win.
   ========================================================== */
@media (min-width: 769px) and (max-width: 1280px) {
  .platform-sidebar {
    padding-left: 1.15rem;
    padding-right: 1.15rem;
  }

  /* The nav's own 2.2rem right padding is the real constraint here — it
     reserves room for the collapse toggle at full width, which the
     tapered width cannot spare. */
  .platform-nav {
    padding-right: 0.75rem;
  }

  .platform-nav .nav-link {
    padding-left: 0.625rem;
    padding-right: 0.625rem;
    font-size: var(--type-body);
  }

  /* THE FOOTER ROWS, same range and same reasoning as the nav labels
     above. At 220px "Intermediate" is the longest thing the learning row
     has to state, and its own line is what makes that possible; what is
     left to do here is give the row's chrome back a few millimetres so
     the level never reaches for the ellipsis at all. */
  html:not(.sidebar-collapsed) .sidebar-menu-trigger {
    gap: 0.28rem;
    padding-left: 0.2rem;
    /* Asymmetric on purpose: the right edge holds a 16px chevron that
       already reads as an inset, and the last two millimetres here are
       what keep "Intermediate" whole at 220px. */
    padding-right: 0.2rem;
  }

  /* A little smaller at 220px, so the longest combination keeps room to
     spare rather than only just fitting. */
  html:not(.sidebar-collapsed) .account-summary {
    font-size: var(--type-xs);
  }

  html:not(.sidebar-collapsed) .account-trigger .account-avatar-wrap {
    width: 26px;
    grid-area: avatar;
  }

  html:not(.sidebar-collapsed) .account-trigger .sidebar-menu-chevron {
    width: 14px;
    height: 14px;
    grid-area: chevron;
    margin-left: 0;
  }

  html:not(.sidebar-collapsed) .account-trigger {
    column-gap: 0.28rem;
  }
}

/* Starter now uses the same .theory-lesson-split, .theory-lesson-text and
   .theory-lesson-widget rules as Beginner. Only content-intrinsic sizing stays
   here: tall photographs keep a sensible height and both Starter instruments
   use equal drawing areas. */
.starter-lesson .guitar-anatomy { justify-content: center; }
.starter-lesson .ga-stage { max-width: var(--starter-photo); }
.starter-lesson .lesson-photo { width: min(100%, 360px); margin-inline: auto; }
/* The three 1280px cut-outs have almost identical person heights. Keep the
   selected posture large without changing the photograph's proportions. */
.starter-lesson .lesson-photo--posture { width: min(100%, 480px); }
.starter-lesson .lesson-photo-img--posture {
  width: 100%;
  max-width: 480px;
  aspect-ratio: 1;
  object-fit: contain;
}

/* Approved D portrait variant: the person precedes the slightly smaller
   instrument, with a shared baseline and a single right-hand label column.
   The two frames overlap only transparent margins, never visible subjects. */
@media (min-width: 1401px) {
  .theory-lesson-split:is(.starter-guitar-split, .posture-hand-row),
  .sidebar-collapsed .theory-lesson-split:is(.starter-guitar-split, .posture-hand-row) {
    grid-template-columns: minmax(520px, 0.95fr) minmax(0, 1.05fr);
  }
}
/* Narrow laptops (1280-1400px, e.g. 1366x768; Jappe, 14 October 2026): Starter's compact widgets keep
   text beside widget instead of stacking below an empty half. Wider screens keep the rules above;
   higher levels keep the shared 1400px collapse, sized for their full-neck widgets. */
@media (min-width: 1280px) and (max-width: 1400px) {
  .starter-lesson .theory-lesson-split,
  .sidebar-collapsed .starter-lesson .theory-lesson-split {
    grid-template-columns: minmax(280px, 45fr) minmax(0, 55fr);
  }
  .starter-lesson .theory-lesson-split:is(.starter-guitar-split, .posture-hand-row),
  .sidebar-collapsed .starter-lesson .theory-lesson-split:is(.starter-guitar-split, .posture-hand-row) {
    grid-template-columns: minmax(360px, 0.95fr) minmax(0, 1.05fr);
  }
}
.starter-guitar-split .starter-guitar-visual {
  --guitar-label-space: 80px;
  min-width: 0;
  max-width: none;
  container-type: inline-size;
}
.starter-guitar-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr);
  align-items: end;
  width: 100%;
  max-width: 700px;
  margin-inline: auto;
  /* Room for the longest existing translated callout, outside the photo. */
  padding-right: var(--guitar-label-space);
}

html[data-handed="left"] .starter-guitar-pair { direction:rtl; padding-right:0; padding-left:var(--guitar-label-space); }
html[data-handed="left"] .starter-guitar-pair > .guitar-anatomy { direction:ltr; justify-content:flex-start; }
.starter-guitar-pair > .guitar-anatomy {
  min-width: 0;
  width: 100%;
  justify-content: flex-end;
}
.starter-guitar-pair .ga-stage {
  flex-shrink: 0;
  width: 128.5714%;
  max-width: none;
}
.starter-lesson .starter-guitar-pair > .lesson-photo--posture {
  width: 100%;
  max-width: none;
  aspect-ratio: 2 / 3;
  margin: 0;
}
.starter-guitar-pair .lesson-photo-img--posture {
  width: 100%;
  height: 100%;
  max-width: none;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  /* Only transparent columns are trimmed from all three original cut-outs. */
  object-position: 64% center;
}
/* Stack based on the widget's space, including sidebar changes, rather than
   shrinking a full seated portrait and its labels into a phone-sized pair. */
@container (max-width: 460px) {
  .starter-guitar-pair {
    grid-template-columns: minmax(0, 1fr);
    gap: 2rem;
    max-width: 420px;
    padding-right: 0;
  }
  .starter-guitar-pair > .guitar-anatomy {
    width: calc(90% - 64px);
    justify-self: start;
    margin-left: 12px;
  }
  .starter-guitar-pair .ga-stage {
    width: 100%;
  }
  html[data-handed="left"] .starter-guitar-pair { padding-left:0; }
  html[data-handed="left"] .starter-guitar-pair > .guitar-anatomy { justify-self:right; margin-left:0; margin-right:12px; }
}
.starter-posture-copy { margin-top: 0; }
/* Electric-only detail: a quiet native disclosure keeps extra controls out
   of the overview. Numbered photo markers map directly to the short list. */
.starter-guitar-controls { width: 100%; max-width: 700px; margin: 1rem auto 0; }
.starter-guitar-controls[hidden] { display: none; }
.starter-guitar-controls summary { min-height: 44px; padding-block: 0.75rem; cursor: pointer; font-weight: 600; }
/* Match the anatomy stage in the 1 : .7 pair (stage = 9/17 of its inner
   width). Only the disclosure moves; its expanded explanation keeps the
   full visual column. Mirroring follows the guitar, not the portrait. */
.starter-guitar-controls summary {
  width: calc((100% - var(--guitar-label-space)) * 9 / 17);
  margin-left: calc((100% - var(--guitar-label-space)) * 8 / 17);
  text-align: center;
}
html[data-handed="left"] .starter-guitar-controls summary { margin-left: var(--guitar-label-space); }
.starter-guitar-controls summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* Approved compact-desktop lesson family (13 October): all Starter lessons
   keep their type sizes and share the same heading and paragraph spacing.
   Budget against the browser viewport, not the monitor resolution. The photo
   pair scales together; long lessons and expanded Controls scroll naturally. */
@media (min-width: 1401px) and (max-height: 1050px) {
  .starter-lesson > .theory-lesson-header { margin-bottom: 0.5rem; }
  .starter-lesson .theory-lesson-title { margin-bottom: 0.5rem; }
  .starter-lesson .alpha-desc { margin-bottom: 0.625rem; }
  .starter-lesson { padding-bottom: 0; }
  .platform-page:has([role="tabpanel"]:not([hidden]) > .starter-lesson:not([hidden])) { padding-bottom: 1.5rem; }
  #tool-starter-your-guitar :is(.starter-guitar-pair, .starter-guitar-controls) {
    max-width: min(700px, 68dvh);
  }
}
.starter-controls-detail { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.5rem; align-items: center; padding-block: 0.5rem 1rem; }
.starter-controls-photo { display: block; width: 100%; height: auto; }
.starter-controls-markers path { stroke: #111; stroke-width: 2; fill: none; }
.starter-controls-markers circle { fill: var(--accent); stroke: #111; stroke-width: 2; }
.starter-controls-markers text { fill: #111; stroke: none; font: 600 18px var(--font-main); text-anchor: middle; dominant-baseline: central; }
.starter-controls-list { margin: 0; padding-left: 1.5rem; font-size: 0.875rem; line-height: 1.5; }
.starter-controls-list li { padding-left: 0.25rem; margin-bottom: 0.75rem; }
.starter-controls-list li:last-child { margin-bottom: 0; }
.starter-controls-list li::marker { color: var(--accent); font-weight: 600; }
.starter-controls-list strong, .starter-controls-list span { display: block; }
.starter-controls-list strong { overflow-wrap: anywhere; }
@container (max-width: 460px) {
  .starter-guitar-controls summary { width: calc(90% - 64px); margin-left: 12px; }
  html[data-handed="left"] .starter-guitar-controls summary { margin-left: auto; margin-right: 12px; }
  .starter-controls-detail { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
  .starter-controls-photo { max-width: 360px; margin-inline: auto; }
}


/* One continuous text column avoids artificial paragraph gaps caused by
   separate photo rows. All four uncropped details remain visible alongside it. */
.starter-hands-intro {
  max-width: 520px;
  text-align: left;
}
.posture-hand-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2rem;
  width: min(100%, 580px);
  margin-inline: auto;
}
.posture-hand-row { margin-top: 0; }
.posture-hand-row > .theory-lesson-text > :last-child { margin-bottom: 0; }
.posture-hand-row > .theory-lesson-widget {
  width: 100%;
  container-type: inline-size;
}
.starter-lesson .posture-hand-grid .lesson-photo {
  width: 100%;
  max-width: 280px;
  margin: 0;
}
.posture-hand-grid .lesson-photo-img {
  display: block;
  width: min(100%, 280px);
  height: auto;
  aspect-ratio: 1;
  object-fit: contain;
  margin-inline: auto;
}
.posture-hand-grid .theory-widget-caption { text-align: left; }
/* Four square details need less room than a fretboard widget. Keep every
   photograph uncropped, using a smaller 2x2 grid on short desktop viewports.
   Heading/tab positions and normal document scrolling are unchanged. */
@media (min-width: 1401px) and (max-height: 1050px) {
  .posture-hand-grid { width: min(100%, 456px); gap: 1rem; }
  #tool-starter-your-hands .alpha-desc:last-child { margin-bottom: 0; }
}
@container (max-width: 460px) {
  .posture-hand-grid { grid-template-columns: minmax(0, 1fr); justify-items: center; }
}


/* Starter and Beginner overviews use the same editorial learning-path
   layout. The short three-step progression explains the logic; the numbered
   rows remain the actual navigation. */
body:has(#theory-panel-starter-overview:not([hidden])) #theoryMobileContents,
body:has(#theory-panel-starter-summary:not([hidden])) #theoryMobileContents,
body:has(#theory-panel-overview:not([hidden])) #theoryMobileContents,
body:has(#theory-panel-summary:not([hidden])) #theoryMobileContents { display: none; }

.starter-overview-layout,
.theory-level-summary,
#theory-panel-overview {
  container-type: inline-size;
}
#theory-panel-starter-overview > .theory-lesson-section,
.theory-level-summary > .theory-lesson-section,
#theory-panel-overview > .theory-level-overview {
  min-width: 0;
  margin: 0;
  padding: 0.5rem 0 0;
  border: 0;
}
/* Beginner keeps the complete route together and points back to Starter in
   one compact prerequisite line. The full readiness check belongs to the end
   of Starter, where it closes that level. */
.theory-level-overview > .theory-overview-intro {
  max-width: 68ch;
  margin: 0 0 1.75rem;
  font-size: 1rem;
  line-height: 1.82;
}
.theory-overview-legacy-foundations { display: none; }
.theory-overview-contents {
  max-width: none;
  margin: 0;
}
.theory-overview-contents .theory-overview-row {
  grid-template-columns: 2.25rem minmax(10rem, 0.55fr) minmax(18rem, 1.45fr);
  gap: 1rem;
  min-height: 4.35rem;
}
.theory-overview-practice {
  margin-top: 1.4rem;
  padding-top: 1rem;
  border-top: 1px solid rgba(var(--w), 0.1);
}
.theory-overview-practice-title {
  margin: 0 0 0.65rem;
  color: var(--text-main);
  font-size: 0.82rem;
  font-weight: 700;
}
.theory-overview-practice-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem 1.35rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.theory-overview-practice-links a {
  color: var(--text-muted);
  font-size: 0.82rem;
  line-height: 1.5;
  text-decoration: underline;
  text-decoration-color: rgba(var(--w), 0.25);
  text-underline-offset: 0.2em;
}
.theory-overview-practice-links a:is(:hover, :focus-visible) { color: var(--accent); }
#theory-panel-starter-summary #tool-starter-ready {
  max-width: 72ch;
  margin-inline: 0;
}
.beginner-prerequisite {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.8rem 1.5rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid rgba(var(--w), 0.1);
}
.beginner-prerequisite p {
  max-width: 68ch;
  margin: 0;
  color: var(--text-muted);
  font-size: 0.82rem;
  line-height: 1.55;
}
.theory-overview-level-link.level-btn {
  display: inline-block;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 700;
  text-decoration: none;
}
.theory-overview-level-link.level-btn:is(:hover,:focus-visible) {
  border-color: transparent;
  background: none;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.theory-readiness-card,
.theory-summary-practice {
  min-width: 0;
  padding: 1.25rem 1.3rem 1.3rem;
  border: 1px solid rgba(var(--w), 0.12);
  border-radius: 12px;
  background: rgba(var(--w), 0.025);
}
.theory-readiness-card .theory-lesson-title {
  margin: 0 0 0.45rem;
  font-size: 1.35rem;
  line-height: 1.35;
}
.theory-readiness-card > p {
  margin: 0 0 1rem;
  color: var(--text-muted);
  font-size: 0.82rem;
  line-height: 1.55;
}
.theory-readiness-list {
  display: grid;
  gap: 0.72rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.theory-readiness-list li {
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr);
  gap: 0.68rem;
  align-items: start;
  color: var(--text-main);
  font-size: 0.82rem;
  line-height: 1.48;
}
.theory-readiness-check {
  color: var(--accent);
  font-size: 0.9rem;
  font-weight: 800;
  line-height: 1.35;
  text-align: center;
}
.theory-summary-content .theory-overview-level-link { margin-top: 1.05rem; }
/* One semantic system for short feedback below interactive Theory widgets.
   Position remains component-specific; colour and type do not. Static
   captions use the same size in muted text, while live feedback is gold. */
.theory-lesson-section :is(
  .starter-interaction-feedback,
  .theory-widget-feedback,
  .rhythm-concept-feedback,
  .feel-feedback,
  .rhy-feedback,
  .ts-demo-footer
) {
  color: var(--accent);
  font-family: Inter, sans-serif;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.5;
}
/* Shared piano playback: labelled keys stay neutral at rest. Only sounding
   tones receive colour and one consistent outline across all levels. */
.theory-piano-key.is-current rect {
  fill: var(--accent);
  stroke: var(--text-main);
  stroke-width: 2.5px;
  filter: drop-shadow(0 0 4px rgba(216,168,79,0.42));
}
.theory-piano-key.is-current-root rect { fill: var(--root-accent); }
.theory-piano-actions .melody-play,
.theory-piano-example-play {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
}
#harmonyIntroChips { margin: 1.25rem 0 0; }
.theory-piano-example {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px solid rgba(var(--w),0.1);
}
.theory-piano-example[hidden] { display: none; }
.theory-piano-example-copy {
  display: inline-flex;
  align-items: baseline;
  gap: 0.45rem;
  color: var(--text-muted);
  font-size: 0.82rem;
  line-height: 1.5;
}
.theory-piano-example-copy > :first-child { color: var(--accent); }
.theory-piano-example-play.is-playing,
.theory-piano-example-play[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(216,168,79,0.1);
}

/* Note Values & Rests keeps the shared 45/55 Theory split. Only the notation
   inside its existing tabbed widget scales up for clearer reading. */
#tool-rhythm .theory-lesson-text { display: block; }
#tool-rhythm .rhy-tree { margin-top: 0; }
#tool-rhythm .rhy-row {
  grid-template-columns: 168px minmax(0, 1fr);
  gap: 14px;
  padding: 8px 6px;
}
#tool-rhythm .rhy-name { font-size: 0.9rem; }
#tool-rhythm .rhy-score { height: 76px; }
#tool-rhythm .rhy-play-icon { width: 30px; height: 30px; flex-basis: 30px; }
#tool-rhythm .rhy-play-icon svg { width: 18px; height: 18px; }
.rhy-barline--measure { stroke: rgba(var(--w),0.48); stroke-width: 1.5; }
.theory-level-summary .theory-summary-content {
  display: grid;
  grid-template-columns: minmax(30rem, 1.1fr) minmax(22rem, 0.9fr);
  gap: clamp(2rem, 3vw, 3rem);
  align-items: start;
  margin-top: 2rem;
  padding-top: 1.35rem;
  border-top: 1px solid rgba(var(--w), 0.1);
}
.theory-summary-practice { margin: 0; }
.theory-summary-practice > .theory-widget-caption {
  margin: 0 0 0.85rem;
  text-align: left;
  color: var(--text-main);
  font-weight: 700;
}
.theory-level-summary .theory-role-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.theory-level-summary .theory-role-item {
  display: grid;
  gap: 0.32rem;
  min-width: 0;
  padding-top: 0.72rem;
  border-top: 1px solid rgba(var(--w), 0.1);
}
.theory-level-summary .theory-role-name {
  color: var(--text-main);
  font-size: 0.8rem;
  font-weight: 700;
}
.theory-level-summary .theory-role-desc {
  font-size: 0.8rem;
  line-height: 1.5;
}
.theory-level-summary .theory-role-desc a {
  color: var(--text-muted);
  text-decoration: underline;
  text-decoration-color: rgba(var(--w), 0.25);
  text-underline-offset: 0.2em;
}
.theory-level-summary .theory-role-desc a:is(:hover, :focus-visible) { color: var(--accent); }

@container (max-width: 940px) {
  .theory-level-summary .theory-summary-content {
    grid-template-columns: minmax(0, 1fr);
    gap: 2rem;
  }
}@container (max-width: 700px) {
  .theory-overview-contents .theory-overview-row {
    grid-template-columns: 2rem minmax(0, 1fr);
    gap: 0.65rem;
    min-height: 0;
  }
  .theory-overview-contents .theory-overview-desc { grid-column: 2; }
  .beginner-prerequisite { align-items: flex-start; flex-direction: column; }
}

@media (max-width: 600px) {
  .theory-piano-example { align-items: stretch; flex-direction: column; }
  .theory-piano-example-play { width: 100%; }
  #tool-rhythm .rhy-row { grid-template-columns: minmax(0, 1fr); gap: 2px; padding-block: 5px; }
}
.orientation-note-picker { display: none; }
@media (max-width: 700px) {
  .orientation-note-picker {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(44px, 1fr));
    gap: 0.4rem;
    margin-top: 1rem;
  }
  .orientation-note-picker button {
    min-width: 44px;
    min-height: 44px;
    padding: 0.3rem;
    border: 1px solid rgba(var(--w), 0.18);
    border-radius: var(--radius-small);
    background: transparent;
    color: var(--text-main);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
  }
  .orientation-note-picker button[aria-pressed="true"] {
    color: var(--accent);
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
  }
  .orientation-note-picker button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }
  #alphaPiano .piano-note-name { font-size: 19px; }
  .starter-lesson .lesson-photo--posture { width: min(100%, 340px); }
}
@media (max-width: 700px) and (orientation: portrait) {
  /* Full 15-fret neck, original geometry. A bounded width keeps the
     portrait diagram readable without turning it into a very long poster. */
  .starter-neck-stage #starterOpenStringNeck { max-width: 230px; margin-inline: auto; }
}


/* ==========================================================
   PORTAL PAGE + TAB LAYOUT  —  the shared tabbed-page pattern
   ----------------------------------------------------------
   The approved Fretboard composition, promoted to the portal's standard
   for any page built as:

       PAGE TITLE
       gold accent rule

       [ main tabs ...................................... ]
       ------------------------------------------------------

       [ secondary controls, if the tab has any ]
       [ page content ]

   OPT-IN, never automatic: a page gets it by carrying `tabbed-page` on
   <body>. Today that is Fretboard, Theory, Practice and Settings — every
   page built from a page title plus a Level 1 tab row.

   TWO TAB COMPONENTS, ONE TREATMENT. Fretboard/Theory/Practice use
   `.fb-tabs > .fb-tab`; Settings uses `.platform-tabs > button`. They are
   the same thing wearing different markup — both are Level 1 navigation
   with a gold active tab and an underline — so every rule below addresses
   both rather than being written twice. Neither component's markup, tab
   set, labels or behaviour changes; only how it is drawn.

   Pages without that structure are untouched: Dashboard, Learn (a card
   list, no tab row), Songs and the Song pages (their own composition),
   and Privacy.

   Everything here is layout: spacing, alignment, weight and the rail. No
   content, no labels, no tabs added or removed, no information
   architecture, no state. `.platform-page h1`, `.fb-tab` and
   `.platform-tabs button` keep their portal-wide definitions further up
   this file — these rules only refine them for pages that opted in, so
   nothing else in the portal moves.
   ========================================================== */

/* ---- TITLE BLOCK ----------------------------------------------------
   Nothing. Deliberately.

   A tabbed page's h1 is the SAME h1 as every other page's, and it gets
   the portal's own `.platform-page h1::after` — the short gold gradient,
   72px, fading to transparent — with no override of any kind here. An
   earlier pass suppressed it on these four pages on the grounds that a
   gold active-tab underline directly below was competing with it. The
   right resolution was the opposite one: the underline went (see the tab
   rules below), the h1 accent stayed. A page does not get a second h1
   design because it happens to have tabs.

   If you are looking for the gold rule, it is at `.platform-page h1` /
   `::after` near the top of this file, and that is the only place it is
   defined for the whole portal. */

/* ---- MAIN TAB ROW ---------------------------------------------------
   Two things make seven or eight labels read as one component:

   1. A real baseline rail. The portal-wide rule asks for `var(--border)`,
      which is not defined anywhere in this file, so the declaration is
      invalid at computed-value time and border-bottom-style falls back to
      `none` — these rows have been running with no rail at all, which is
      most of why the labels read as loose items. Set explicitly here
      rather than by defining --border, which would silently change every
      other `var(--border)` user in the portal.
   2. Gap-based spacing instead of per-tab horizontal padding, so the
      first label's left edge lands on the page's text margin — the same x
      as the title, the gold rule under it, and the content below.

   The rail is neutral and full-width: it belongs to the ROW, never to the
   active tab, and it does not change as tabs are switched. Gold in this
   component means exactly one thing — which tab you are on — and it says
   it with text colour alone.

   Not pills: this is Level 1 navigation. */
.tabbed-page :is(.fb-tabs, .platform-tabs) {
  gap: 0 1.75rem;
  border-bottom: 1px solid rgba(var(--w), 0.09);
  margin-top: 0;
  margin-bottom: var(--page-tabs-gap);
}

.tabbed-page :is(.fb-tabs .fb-tab, .platform-tabs button) {
  min-height: 44px;
  min-width: 44px;
  padding: 0 0 0.6rem;
  font-size: var(--type-small);
  font-weight: 600;
  letter-spacing: 0.005em;
  border-bottom-width: 2px;
  margin-bottom: -1px;
  color: rgba(var(--w), 0.46);
}

.tabbed-page :is(.fb-tabs .fb-tab, .platform-tabs button):hover {
  color: var(--text-main);
}

/* Active is gold TEXT and nothing else.

   The portal-wide `.fb-tab.active` and `.platform-tabs button.active`
   rules both set border-bottom-color to the accent; this resets it to
   transparent rather than removing the border, so the box keeps the exact
   height it had and nothing reflows when tabs are switched. There is no
   ::after standing in for it either — the accent under a tabbed page's
   title is the h1's, and only the h1's.

   Weight also stays uniform across states, for the same no-reflow reason:
   the colour carries the state on its own against a row of muted labels. */
.tabbed-page :is(.fb-tabs .fb-tab, .platform-tabs button).active {
  color: var(--accent);
  border-bottom-color: transparent;
}

/* Narrow widths keep the portal's existing one-row scroll behaviour (see
   the .fretboard-page :is(#fbTabs, …) block above). Only the scroll
   padding needs restoring, since the tabs no longer carry their own. */
@media (max-width: 700px) {
  .tabbed-page :is(.fb-tabs, .platform-tabs) {
    gap: 0 1.25rem;
    scroll-padding-inline: 0;
  }
  .tabbed-page :is(.fb-tabs .fb-tab, .platform-tabs button) {
    padding: 0 0 0.5rem;
  }
}

/* The same control height applies to every Fretboard tab on touch screens. */
@media (max-width: 900px), (pointer: coarse) {
  body.fretboard-page { --fb-control-h: 44px; }
}

/* ==========================================================
   FRETBOARD — full-neck width budget
   ----------------------------------------------------------
   Every neck-bearing Fretboard tab now draws the full 15-fret neck on
   desktop, which makes it the widest object in the portal. The page's own
   padding steps 4rem → 2rem only at 900px, so between 901px and 1400px
   the neck was paying 128px of padding while the sidebar was still
   expanded: a 1024px window gave it 636px, LESS than a 768px tablet gives
   it, because there the sidebar has already collapsed. The neck got
   smaller as the window got bigger.

   Moving the 2rem step up to 1400px for this page removes the inversion.
   Together with the tapering --sidebar-w above, 1024px now yields ~740px
   of neck against a tablet's 713px, and the curve is monotonic from 768
   up to 1440. Padding and sidebar width, not dot or type size: the neck's
   internal proportions were never the problem.

   Left/right only — the vertical rhythm must not move with width. That is
   why this moves --page-gutter and not --page-pad-y: the axis shifts, the
   rhythm does not, and everything keyed to the axis (title, tabs, controls,
   neck) shifts together instead of the neck alone breaking ranks.
   ========================================================== */
@media (min-width: 901px) and (max-width: 1400px) {
  .fretboard-page {
    --page-gutter: 2rem;
  }
}

/* ==========================================================
   FRETBOARD CONTROL SYSTEM
   ----------------------------------------------------------
   One control hierarchy for all seven tabs. Before this, three different
   visual patterns were doing the same semantic job — `.sv-cat-btn` inside
   a segmented `.sv-cats--pill` container (Notes, the global Quality row,
   Chords' triad strings), bordered `.scale-btn` pills (scale types,
   CAGED/Arpeggio qualities), and borderless `.fbx-step-btn` chips
   (categories, positions, CAGED layers) — so Naturals/Sharps/Flats looked
   like a different kind of control from Major/Minor one tab over.

   The levels, and what makes each one legible as its own tier:

     1  PRIMARY NAV      text only, gold active, neutral rail
                         (.tabbed-page .fb-tabs, above — untouched here)
     2  MODE / TYPE      bordered pill, gold tint when active
     3  VIEW / SUBMODE   text only, gold when active, NO border
     P  PARAMETER        uppercase label + native select
     ·  POSITION         uppercase label + compact round steppers
     ·  ACTION           outlined, its own colour, never gold
     ·  PLAYBACK         icon buttons (untouched)

   Level 2 and Level 3 are separated by the border, not by size: a tier
   that only differed in padding would read as the same control at a
   different zoom. Level 3 has no box at all, so it reads as a switch
   inside the thing Level 2 selected.

   SCOPED TO .fretboard-page THROUGHOUT. `.scale-btn` and `.fbx-step-btn`
   are portal-wide classes that Theory and Practice also use; none of
   these rules may reach them. The tests assert this.

   This is styling only. No markup was restructured, no class renamed in
   JS, no handler touched — every rule below addresses the rows the tools
   already build.
   ========================================================== */

/* Vertical rhythm. One value per KIND of gap, so the same relationship
   gets the same space on every tab and switching tabs changes the content
   inside a stable template rather than the template itself.

   Four kinds, and the hierarchy between them is the point — one uniform
   margin everywhere would flatten the page just as badly as per-tab
   margins fragment it:

     row      tightly related controls, inside one group
     group    one control group to the next
     neck     the neck's own breathing room, above AND below it
     section  the interface to the writing about it

   --fb-gap-neck is deliberately the largest of the three control gaps
   and is used on BOTH sides of the diagram, which is what makes the neck
   read as the page's subject rather than as one more row. */
.fretboard-page {
  --fb-gap-row: 0.8rem;      /* between two control rows in one group  */
  --fb-gap-group: 1.5rem;    /* between control groups                 */
  --fb-gap-neck: 2rem;       /* controls ↔ neck, and neck ↔ its footer */
  --fb-gap-section: 2.8rem;  /* playback → the musical information     */

  /* ONE HEIGHT FOR EVERY CONTROL THAT CAN SHARE A LINE.
     Before this the same row held four different heights — a Level 2
     pill computed to 30.42px out of its padding, the Root select to
     30.00, "Add blue note" to 26.59 and a position chip to 27.56 — so
     the row had no single baseline to sit on and Root read as a few
     pixels out wherever it appeared.

     Height is stated once, here, and every control takes it. Padding can
     then be tuned for horizontal rhythm without ever moving a baseline
     again, and vertical centring is done by each control's own flex box
     rather than by matching paddings against font metrics. */
  --fb-control-h: 30px;
}

/* ---- LEVEL 2 — MODE / TYPE ------------------------------------------
   Loose pills, deliberately with no shared outer container: a segmented
   track implies the options are a single either/or widget, which is true
   for Naturals/Sharps/Flats but not for a scale-type row that changes
   length with the level. One pattern that fits both beats two patterns.

   Every Level 2 row on the page is listed by id rather than by class,
   because the same three classes also build Level 3 and the position
   steppers — the id is what actually says "this row picks a mode".

   Buttons are matched as DESCENDANTS, not children: the scale/arpeggio
   type rows wrap each option in a .sc-type-item alongside a small info
   button, so a `> button` selector silently missed exactly the rows with
   the most options. .sc-info-btn is excluded — it is a disclosure, not a
   mode. */
.fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
    #intervalsFamily,
    #globalQuality, #intervalsPicker) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  row-gap: var(--fb-gap-row);
  /* The segmented container's own frame, cleared for the three rows that
     were built as one: they are loose pills now like every other row.

     NO `overflow: visible` HERE. It used to sit in this reset, and being
     later in the file at equal specificity it silently beat BOTH mobile
     scroll rules — so any one of these rows that did not fit a phone laid
     itself out on one line wider than the viewport and was then clipped
     by `.platform-content`'s `overflow-x: clip`, with everything past the
     fold unreachable. It went unnoticed only because the rows short
     enough to fit never needed to scroll. There is no frame left to clear
     on this axis: the three properties above already removed it. */
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
  margin: 0 0 var(--fb-gap-row);
}

.fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
    #intervalsFamily,
    #globalQuality, #intervalsPicker) button:not(.sc-info-btn):not(.sv-blues-btn) {
  flex: 0 0 auto;
  /* The height comes from the token, not from the padding — so the
     padding here is purely the horizontal rhythm, and an option with a
     descender or a ♭ in it can never make its pill taller than its
     neighbour's. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--fb-control-h);
  padding: 0 0.8rem;
  font-family: var(--font-main);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.005em;
  line-height: 1.35;
  white-space: nowrap;
  cursor: pointer;
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: var(--radius-small);
  background: rgba(var(--w), 0.035);
  color: rgba(var(--w), 0.62);
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}

.fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
    #intervalsFamily,
    #globalQuality, #intervalsPicker) button:not(.sc-info-btn):not(.sv-blues-btn):hover:not(:disabled) {
  background: rgba(var(--w), 0.07);
  color: var(--text-main);
}

/* Active: gold text on a flat gold tint with a gold hairline. No glow, no
   gradient, no shadow — the tint alone is enough at this size. */
.fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
    #intervalsFamily,
    #globalQuality, #intervalsPicker) button:not(.sc-info-btn):not(.sv-blues-btn).active {
  background: rgba(216, 168, 79, 0.12);
  border-color: rgba(216, 168, 79, 0.3);
  color: var(--accent);
}

/* ---- NOTES: NOTE SELECTOR + SPELLING, STILL ONE ROW ------------------
   The Notes tab asks two independent questions — WHICH notes are on the
   neck (All / Naturals / one pitch) and HOW accidentals are named (Sharps /
   Flats). Note reuses the shared select/combobox; spelling is a radio group.
   Each retains its own accessible name and keyboard contract.

   Visually it stays ONE Level 2 row: #notesSharpToggle is still the row
   (it keeps every Level 2 declaration above, and the button rules match
   as descendants, so the pills are unchanged), and these two rules only
   say where the seam between the groups falls. A wider gap than the
   0.35rem between sibling pills is the whole separation — no divider, no
   caption, no second tier.

   Spelling is DISABLED, not hidden, while Naturals is shown: nothing on
   that neck is an accidental, so the choice has no visible effect, but it
   still holds the reader's Sharps/Flats preference for when All returns.
   Hiding it would make the row jump; disabling it says "not applicable
   here" and keeps the layout still. No dimming rule is written here — the
   shared Level 2 `:disabled` rule below already reaches these buttons as
   descendants of #notesSharpToggle and gives them the portal's one
   disabled treatment (opacity 0.38, not-allowed). */
.fretboard-page #notesSharpToggle {
  gap: 0.35rem 1.35rem;
}

.fretboard-page .fb-notes-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}


/* ---- LEVEL 3 — VIEW / SUBMODE ---------------------------------------
   CAGED's Shape/Root/Arpeggio/Pentatonic/Diatonic layers and Chords'
   nested quality/string rows switch a view Level 2 already chose, so they
   carry no box: text, muted, gold when active. */
.fretboard-page :is(#cagedIntLayers, #chordIntType, #chordTypes, #chordTriadStrings) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem 0.4rem;
  row-gap: var(--fb-gap-row);
  margin: 0 0 var(--fb-gap-row);
}

.fretboard-page :is(#cagedIntLayers, #chordIntType, #chordTypes, #chordTriadStrings) button:not(.sc-info-btn):not(.sv-blues-btn) {
  flex: 0 0 auto;
  /* Same height as Level 2, deliberately. The block above says these two
     tiers are separated by the border and not by size — but they were
     also 2.4px apart, because each derived its own height from its own
     padding. On CAGED, where a Level 2 quality row and a Level 3 layer
     row share one .fb-controls line with Root, that difference was
     visible as exactly the kind of step this pass removes. The tier still
     reads as lighter: no box, muted text, and a tighter horizontal
     padding — all the things that are not height. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--fb-control-h);
  padding: 0 0.5rem;
  font-family: var(--font-main);
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.35;
  white-space: nowrap;
  cursor: pointer;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-small);
  color: rgba(var(--w), 0.45);
  transition: background 0.14s ease, color 0.14s ease;
}

.fretboard-page :is(#cagedIntLayers, #chordIntType, #chordTypes, #chordTriadStrings) button:not(.sc-info-btn):not(.sv-blues-btn):hover:not(:disabled) {
  color: var(--text-main);
}

/* Active carries a whisper of tint — enough to hold the eye in a row of
   plain words, far short of Level 2's bordered pill. */
.fretboard-page :is(#cagedIntLayers, #chordIntType, #chordTypes, #chordTriadStrings) button:not(.sc-info-btn):not(.sv-blues-btn).active {
  color: var(--accent);
  background: rgba(216, 168, 79, 0.08);
}

/* ---- POSITION -------------------------------------------------------
   Its own control, not a mode row: compact round steppers, identical in
   Arpeggio, Pentatonic, Diatonic and Intervals.

   ONE LABEL SOURCE. The row carries role="group" + aria-label, written by
   buildPositions()/buildArpPositions()/buildIvPositions() in fretboard.js. The positioned
   diagram composition below reuses that translated string visibly through
   attr(aria-label), so the visual and accessible names cannot drift and no
   duplicate label element exists. */
.fretboard-page :is(#fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) {
  display: flex;
  flex-wrap: wrap;
  overflow: visible;
  align-items: center;
  gap: 0.3rem;
  margin: 0;
}

.fretboard-page :is(#fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) button:not(.sc-info-btn):not(.sv-blues-btn) {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.9rem;
  min-height: var(--fb-control-h);
  padding: 0 0.6rem;
  font-family: var(--font-main);
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  background: rgba(var(--w), 0.035);
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: var(--radius-pill);
  color: rgba(var(--w), 0.6);
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}

.fretboard-page :is(#fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) button:not(.sc-info-btn):not(.sv-blues-btn):hover:not(:disabled) {
  background: rgba(var(--w), 0.07);
  color: var(--text-main);
}

/* A ring rather than a fill: the active position is a place on the neck,
   and a ring reads as "you are here" where a solid pill reads as a
   pressed button. */
.fretboard-page :is(#fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) button:not(.sc-info-btn):not(.sv-blues-btn).active {
  background: rgba(216, 168, 79, 0.1);
  border-color: rgba(216, 168, 79, 0.45);
  color: var(--accent);
}

/* ---- PARAMETER ROW (Root) -------------------------------------------
   A parameter, not a tab: label + form control, identical wherever it
   appears. The label matches the POSITION label's treatment, because both
   name a value rather than offering a choice. */
.fretboard-page .chord-voicing-select {
  /* Block-level flex with fit-content width, not inline-flex: the group
     still hugs its label and select, but its vertical margins now apply
     predictably instead of being partly absorbed by the line box it used
     to sit in — which made the gap above Root vary by tab (13px on CAGED
     against 21px on Chords) for no reason a reader could see.

     The label sits ABOVE the control, not beside it. That is what lets
     Root be the FIRST control on every tab and still share a line with
     the selector row that follows it: a caption over a select is a
     compact block, where a caption beside one is a wide strip that
     pushes everything else onto the next line. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: fit-content;
  gap: 0.3rem;
  margin: 0 0 var(--fb-gap-group);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgba(var(--w), 0.38);
}

.fretboard-page .chord-voicing-select select {
  /* height as well as min-height: a select sizes itself from its own
     font metrics and its option list, and only a stated height keeps it
     on the same baseline as the pills beside it in every browser. */
  height: var(--fb-control-h);
  min-height: var(--fb-control-h);
  padding: 0 0.5rem;
  font-family: var(--font-main);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-main);
  background: rgba(var(--w), 0.035);
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: var(--radius-small);
}

.fretboard-page .chord-voicing-select select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---- ROOT PICKER — the open state, drawn by us ------------------------
   The closed control above was already ours. The OPEN one was not: a
   native <select> menu is drawn by the operating system, so it arrived
   white, with system-blue selection and system metrics, in the middle of
   a dark gold-accented page. root-picker.js renders a listbox over the
   same <select> — the select stays the model and every tab's existing
   change handler is untouched — and these rules draw it.

   `.fb-rootpick-on` is set by that script, so the native control is only
   hidden once something has actually replaced it. With JS off, the page
   keeps the styled native select exactly as before.

   GOLD IS SPENT IN TWO PLACES AND NOWHERE ELSE: the focus ring, and a
   2px marker beside the selected root. Not the closed surface, not the
   panel border, and not the selected row's background — a fully gold row
   would read as a button rather than as "you are here". */

.fretboard-page.fb-rootpick-on .chord-voicing-select select.fb-rootpick-native {
  display: none;
}

.fretboard-page .fb-rootpick {
  position: relative;
  display: inline-flex;
}

.fretboard-page .fb-rootpick-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  /* Same box the select had: same height token, same radius, same
     surface — the control's footprint does not move. */
  height: var(--fb-control-h);
  /* Wide enough for the widest root name, so switching C → C♯/D♭ cannot
     reflow the row it sits in. */
  min-width: 4.5rem;
  padding: 0 0.5rem;
  background: rgba(var(--w), 0.035);
  border: 1px solid rgba(var(--w), 0.09);
  border-radius: var(--radius-small);
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

/* The root is the content; the chevron is punctuation. Centring the
   value across the whole button — chevron included — is what keeps a
   one-character root from sitting off to one side of its own box. */
.fretboard-page .fb-rootpick-value {
  flex: 1;
  text-align: center;
  /* Accidentals are technical text: tabular figures and a shared
     baseline keep C♯ and D♭ the same visual weight as C. */
  font-variant-numeric: tabular-nums;
}

.fretboard-page .fb-rootpick-chevron {
  display: inline-flex;
  flex: 0 0 auto;
  color: rgba(var(--w), 0.4);
  transition: color 0.15s ease, transform 0.15s ease;
}

.fretboard-page .fb-rootpick-btn:hover {
  background: rgba(var(--w), 0.06);
  border-color: rgba(var(--w), 0.2);
}

.fretboard-page .fb-rootpick-btn:hover .fb-rootpick-chevron {
  color: rgba(var(--w), 0.62);
}

.fretboard-page .fb-rootpick-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.fretboard-page .fb-rootpick.is-open .fb-rootpick-btn {
  background: rgba(var(--w), 0.07);
  border-color: rgba(var(--w), 0.26);
}

.fretboard-page .fb-rootpick.is-open .fb-rootpick-chevron {
  color: rgba(var(--w), 0.7);
  transform: rotate(180deg);
}

.fretboard-page .fb-rootpick-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ---- PARAMETER ROW (Root), the twelve-pill variant --------------------
   Advanced and Expert pick their root from #globalRoots instead of the
   dropdown — same parameter, a different widget, because at that level
   the twelve roots are worth having in view at once. It keeps its own
   look entirely: the naturals stay plain .scale-btn pills and the
   accidentals keep their stacked enharmonic pair (C♯ over D♭), recessed
   and smaller.

   Only the box is standardised. The row was running two heights at once
   — 25px for a natural, 27px for a stacked accidental, on two different
   tops — so the twelve pills that name the most important value on the
   page sat on a visibly ragged line. Height and centring come from the
   token now; nothing else here changes.

   Spacing joins the token set at the same time: the bar's two rows are
   one group (--fb-gap-row) and the bar as a whole is one group away from
   the tool below it (--fb-gap-group). 0.5rem and 1.5rem were the last
   two hand-set vertical numbers left on this page. */
.fretboard-page .sv-roots .scale-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--fb-control-h);
}

.fretboard-page .sv-roots .scale-btn.sv-acc {
  flex-direction: column;
  justify-content: center;
}

.fretboard-page .fb-root-bar {
  gap: var(--fb-gap-row);
  margin-bottom: var(--fb-gap-group);
}

/* ---- CONTEXTUAL ACTION ----------------------------------------------
   "Add blue note" is an action, not a mode and not a position. It keeps
   its blue — the colour is the note it adds, which is the whole point —
   but takes the shared geometry so it sits in the same rhythm, and it is
   never gold: gold on this page means "selected", and an action is not a
   selection. */
.fretboard-page .sv-blues-btn {
  /* Same box as the mode pills it sits beside — only the colour says it
     is an action rather than a selection. It was the shortest control on
     the line at 26.59px, which is what made the family row look ragged
     next to it. */
  min-height: var(--fb-control-h);
  padding: 0 0.7rem;
  font-size: 0.78rem;
  font-weight: 600;
  border-radius: var(--radius-small);
}

/* It sits INSIDE #fbxTypes, which is a Level 2 row on Diatonic — so the
   id-based rule above outranked its own class and briefly repainted it as
   a mode pill. Every level rule now excludes it by name, and its blue is
   restated here so the exclusion can never silently un-style it. */
.fretboard-page .sv-blues-btn,
.fretboard-page .sv-blues-btn:hover {
  background: rgba(91, 155, 213, 0.07);
  border: 1px solid rgba(91, 155, 213, 0.2);
  color: rgba(91, 155, 213, 0.8);
}

.fretboard-page .sv-blues-btn:hover {
  background: rgba(91, 155, 213, 0.12);
}

/* ---- CONTROL LINE ----------------------------------------------------
   Two control rows that belong on the same line. Used by Pentatonic,
   where the "Add blue note" action sits beside the Major/Minor/Dominant
   family row instead of under it:

       [ Major ] [ Minor ] [ Dominant ]    [ ○ Add blue note ]

   The gap between the two rows is deliberately wider than the gap between
   the pills inside a row, so the line reads as "selection group, then an
   action" rather than as four equal options. --fb-gap-row is the existing
   token nearest the 10-12px this needs; the pills' own gap is 0.35rem.

   One flex line, wrapping. When a narrow screen cannot hold both, the
   action drops to its own line naturally — no width query, no second
   markup path, and the rows keep their own styling either way. Diatonic
   puts its whole Type row in the same slot and simply fills the line. */
.fretboard-page .fb-control-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* Families and types need the same breathing room when they wrap. */
  gap: var(--fb-gap-row);
}

/* Inside the line the rows carry no bottom margin of their own — the line
   owns the spacing to whatever follows. An empty row (Diatonic hides
   #fbxCats, Pentatonic's #fbxTypes is empty until a blues scale is
   picked) must not leave a gap behind either.

   The rows are named by id for the same specificity reason the group's
   own reset states them (see CONTROL GROUP below), and this is where
   that reason was actually costing something: `.fb-control-line > *`
   loses to `.fretboard-page :is(#fbxCats, …)`, so #fbxCats and #fbxTypes
   kept a 12.8px bottom margin INSIDE the line. .fb-controls aligns
   MARGIN boxes, so the family row and the blue-note action floated
   12.8px above the Root select they were supposed to share a baseline
   with — the exact mismatch this pass exists to remove, and one no
   amount of height tuning would have fixed. */
.fretboard-page .fb-control-line > :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle, #intervalsFamily, #intervalsPicker,
    #cagedIntLayers, #chordIntType, #chordTypes,
    #fbGlobalBar),
.fretboard-page .fb-control-line > * {
  margin-top: 0;
  margin-bottom: 0;
}

.fretboard-page .fb-control-line:not(:has(> *:not(:empty))) {
  display: none;
}

.fretboard-page .fb-control-line {
  margin-bottom: var(--fb-gap-row);
}

/* ---- DIATONIC: ROOT + PRIMARY TABS, THEN THE SUB-TABS UNDER THEM ----
   The Diatonic tab has a real two-level picker: #fbxCats chooses the
   FAMILY (Major modes, Harmonic minor modes, Melodic minor modes,
   Symmetric, Bebop, Exotic — and at Expert the two harmonic-major
   mode systems), and #fbxTypes then chooses one of that family's seven
   modes. Seven mode buttons cannot share a line with the family row and
   a Root control, and on one line they read as siblings of the
   families, which they are not.

       ROOT
       [ C ▾ ]   [ Major modes ] [ Harmonic minor modes ] ...
       [ Ionian ] [ Dorian ] [ Phrygian ] [ Lydian ] ...

   So the sub-tabs take a full-width row of their own UNDER the first
   row — the same break #chordIntFamily, #cagedIntLayers and
   #cagedIntLayers already use, and for the same reason.

   `display: contents` rather than a markup move: #fbxCats and #fbxTypes
   share .fb-control-line so that Intermediate PENTATONIC can keep its
   blue-note action beside the family row (a contextual action next to
   the selection it acts on, which is deliberate and stays). Dissolving
   the line on THIS tab only makes both rows direct children of the
   group, so the group's own row-gap and flex-end alignment apply and
   Root lands beside the FAMILY row rather than beside the block of two.
   Nothing about Pentatonic, about Beginner, or about any scale data
   changes; only where the second row sits.

   Scoped by the tab the page is actually on (fretboard.js sets
   document.body.dataset.fbTab on every tab switch), never by level: the
   hierarchy is a property of the picker, not of who is reading it. */
body.fretboard-page[data-fb-tab="scales"] .fb-control-line {
  display: contents;
}
body.fretboard-page[data-fb-tab="scales"] .fb-controls > #fbxTypes {
  flex-basis: 100%;
}
/* An empty row must not reserve a line of its own — the
   .fb-control-line empty guard above cannot do it any more, because the
   line is no longer a box on this tab. */
body.fretboard-page[data-fb-tab="scales"] .fb-controls > #fbxTypes:empty,
body.fretboard-page[data-fb-tab="scales"] .fb-controls > #fbxCats:empty {
  display: none;
}

/* ---- CONTROL GROUP ---------------------------------------------------
   ROOT, then what to look at, on ONE line wherever the line holds them:

       ROOT
       [ C ▾ ]     [ Major ] [ Minor ] [ Dominant 7 ]

   Root comes first on every tab that has one. It is the note everything
   else on the page is measured from, so choosing it first is the order
   the music is in — and once it is first, it is first everywhere, which
   is what stops each tab from teaching its own reading order.

   Two groups on one line, not one merged control: the column gap between
   them is --fb-gap-group, the same gap those two would have had stacked,
   so they stay legibly separate. Bottoms are aligned rather than
   centres, because the Root caption sits above its select and it is the
   CONTROLS that should line up, not the block's midpoints.

   Never stretched: every child is content-width and the line is
   left-aligned, so the controls end where they end instead of spreading
   across the column. A row too long for the line simply wraps, which is
   the intentional second row rather than a squeeze. */
.fretboard-page .fb-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  /* flex-start, never space-between: the controls are a left-aligned
     cluster on the page's one content edge. Chords' own .chord-nav-row
     used to push its two rows to opposite ends of the column, which read
     as a toolbar rather than as a choice and put its Type row on a left
     edge no other tab shares. */
  justify-content: flex-start;
  column-gap: var(--fb-gap-group);
  row-gap: var(--fb-gap-row);
  margin: 0 0 var(--fb-gap-neck);
}

/* The group owns the vertical rhythm; its children own none of it. This
   is what removes the per-tab drift — every row inside every tab's
   control group now has exactly the same margins, which is none.

   The rows are listed by id, the same way every other rule in this block
   lists them, for a specificity reason and not a stylistic one: the
   Level 2 rule above matches them through :is(#…) and so carries an id's
   weight. A plain .fb-controls > * selector loses to it, and the row keeps a
   13px bottom margin nobody can see — except that align-items aligns
   MARGIN boxes, so Root ends up sitting 13px below the row beside it.
   That was visible as a small unexplained step in exactly the place this
   pass exists to make straight. */
.fretboard-page .fb-controls > :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle, #intervalsFamily, #intervalsPicker,
    #cagedIntLayers, #chordIntType, #chordTypes,
    #fbGlobalBar, .fb-control-line, .chord-voicing-select),
.fretboard-page .fb-controls > * {
  margin-top: 0;
  margin-bottom: 0;
}

/* A sub-row that belongs UNDER the line rather than beside it, on its own
   full-width line inside the SAME group — so it takes the group's own
   row-gap and needs no margin, no wrapper and no second .fb-controls.

   Chords' #chordIntFamily  Triads/Seventh chords/Color chords is the parent
                            choice and therefore owns the first row. Root and
                            Major/Minor/etc. remain together on the row below.
   CAGED's #cagedIntLayers  CAGED is the one tab with two DIFFERENT
                          KINDS of control, not two rows of one kind:

                            [ C ▾ ]  Major  Minor  Dominant 7
                            Shape  Root  Arpeggio  Pentatonic  Diatonic

                          Row 1 chooses the harmony; row 2 chooses which
                          view of it the neck draws. Running all five
                          layers onto the end of the quality row made one
                          undifferentiated eight-control strip in which
                          "Minor" and "Pentatonic" looked like siblings.
                          The two tiers already look different (Level 2
                          pill vs Level 3 plain word — see above); the
                          line break is what makes them READ as two
                          decisions. No heading is added for row 2: the
                          tier styling and the break already say it. */
.fretboard-page .fb-controls > :is(#chordIntFamily, #cagedIntLayers, #intervalsFamily) {
  flex-basis: 100%;
}

/* Chords' second row is Root + the selected family's qualities. Let the
   quality group shrink inside the remaining line width; its nested
   data-scroll-row already owns horizontal overflow on phones. Without
   min-width:0 the flex item's max-content width forces it onto row three. */
.fretboard-page #chordIntType {
  flex: 1 1 0;
  width: 0;
  min-width: 0;
}

/* ---- SHARED STATES ---------------------------------------------------
   One focus ring and one disabled treatment for every control above, so
   keyboard use and unavailable options read the same everywhere. */
.fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
    #intervalsFamily,
    #globalQuality, #intervalsPicker,
    #cagedIntLayers, #chordIntType, #chordTypes,
    #chordTriadStrings, #fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) button:not(.sc-info-btn):not(.sv-blues-btn):focus-visible,
.fretboard-page .sv-blues-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
    #chordIntFamily, #arpCats, #arpItems,
    #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
    #intervalsFamily,
    #globalQuality, #intervalsPicker,
    #cagedIntLayers, #chordIntType, #chordTypes,
    #chordTriadStrings, #fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) button:not(.sc-info-btn):not(.sv-blues-btn):disabled {
  opacity: 0.38;
  cursor: not-allowed;
}

/* ---- VERTICAL RHYTHM -------------------------------------------------
   The same relationship gets the same gap on every tab. Only the gaps are
   set here; what each tab puts in them is its own business.

   ONE ORDER, ON EVERY TAB THAT HAS THE PARTS FOR IT. The header started
   on #fb-panel-scales and has now been adopted by Arpeggio, Intervals,
   CAGED (Intermediate) and Chords as well — every tool on the page with
   a caption to show:

       controls     → head        --fb-gap-neck
       head         → diagram     --fb-gap-row
       diagram      → playback    --fb-gap-neck

   The neck keeps --fb-gap-neck on both sides, which is what makes it read
   as the page's subject; the header is bound to it with the tightest gap
   the system has, which is what makes the two read as one object.

   The original order — info under the neck, after playback — survives in
   exactly one place, and deliberately: the Advanced/Expert CAGED islands,
   where five diagrams carry five captions of their own and there is no
   single diagram for a single header to head.

   A tab without one of these collapses that step rather than reserving
   space for it: Notes has no position stepper and no info block, so its
   neck simply sits --fb-gap-neck under the Naturals/Sharps/Flats row and
   the panel ends there. */

/* The neck's own breathing room, stated on the neck so it does not
   depend on which control happens to come last — that differs per tab
   and per level. Both diagram containers, so Chords keeps the rhythm
   even though its primary visual is a chord grid rather than a neck. */
.fretboard-page .sv-board-wrap,
.fretboard-page .chord-diagram-wrap,
#notesMap {
  margin-top: var(--fb-gap-neck);
}

/* The position stepper reuses .tool-nav for its chips, and that base
   class is a STICKY page navigation bar with a bottom border and 1.5rem
   under it — none of which belongs to a control sitting beneath a
   diagram. Unset here rather than by giving the stepper another class,
   because the chips themselves are exactly right. */
.fretboard-page .tool-nav {
  position: static;
  z-index: auto;
  background: none;
  border-bottom: none;
  padding: 0;
  margin: var(--fb-gap-neck) 0 0;
}

.fretboard-page .fretboard-sequence-actions {
  margin: var(--fb-gap-row) 0 0;
}

/* The Beginner-Arpeggio footer rule that used to live here is gone with
   the tab that needed it. It gave playback the neck's gap on a tab whose
   stepper was empty and therefore hidden, leaving playback as the neck's
   direct next sibling with only a footer-internal gap to sit on. Now that
   the stepper has moved up into the diagram header on every tab that has
   one, playback ALWAYS follows the diagram directly and always takes the
   neck gap — unconditionally, from the rule under DIAGRAM HEADER, with
   no emptiness to detect. */

/* ---- DIAGRAM HEADER --------------------------------------------------
   The shared Fretboard composition, third piece. .fb-controls chooses the
   music; this chooses nothing — it states what that choice produced and
   offers the one control that navigates the diagram rather than changing
   it:

       POSITION  1 2 3 4 5 All                 C · E♭ · F · G · B♭
                                               1 · ♭3 · 4 · 5 · ♭7
       ──────────────────── the neck ────────────────────

   The base header remains content-led for caption-only tools. Position
   diagrams opt into the approved composition below: the view leads on the
   left and the result reads on the right. Both belong to the diagram, so
   both stay inside its header rather than becoming detached control rows.

   Bottoms are aligned, not centres or tops, for the same reason
   .fb-controls aligns bottoms: the stepper and caption share the neck's
   upper edge while the caption's own lines rise off it.

   A CLASS, NOT AN ID — which is what made the rollout one wrapper per
   tab and no new rule here. Every tool on the page with a caption carries
   it, and Intervals uses it for its position navigator.

   WHAT EACH TAB PUTS IN IT is its own business, and most put in half of
   it. Arpeggio, Pentatonic and Diatonic fill both halves because all three
   pair positions with a note/formula caption. Intervals has the position
   navigator alone; CAGED and Chords have the caption alone. A missing half
   is not reserved, because `space-between` on a single item leaves it at
   the start of the line and nothing is stretched to fill anything.

   Notes has neither a caption nor a stepper and therefore has no header,
   the same way it has no root picker. Nothing was invented for it so the
   set would look complete.

   THE ONE DELIBERATE EXCEPTION is Advanced/Expert CAGED. Its five islands
   each carry their own name and their own diagram, so there is no single
   subject for a single header to name, and giving it one would have meant
   either five headers or one that lies about which island it describes.
   A composition that fits five tools is not evidence it fits the sixth. */
.fretboard-page .fb-diagram-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  column-gap: var(--fb-gap-group);
  row-gap: var(--fb-gap-row);
  /* Top margin collapses with .fb-controls' identical bottom margin, so
     the controls sit --fb-gap-neck above the header exactly as they used
     to sit above the neck.

     The BOTTOM used to be --fb-gap-row, the tightest gap the system has,
     on the reasoning that header and neck are one object. That was right
     while the stepper announced itself with the word POSITION. Without it
     the stepper is a row of bare numerals — 1 2 3 4 5 — sitting 13px above
     a row of bare numerals that are the FRET NUMBERS, and the two read as
     one ragged strip of digits. The group gap separates them without
     detaching the header: it is still the smallest gap that is not the
     within-a-group one, and the neck still keeps the largest gap of all on
     both sides, which is what keeps it the subject. */
  margin: var(--fb-gap-neck) 0 var(--fb-gap-group);
}

/* The diagram's own top gap is already spoken for by the header above it.
   Both diagram containers, so Chords keeps the rhythm even though its
   primary visual is a chord grid rather than a neck — the same pairing
   the neck gap itself uses. */
.fretboard-page .fb-diagram-head + .sv-board-wrap,
.fretboard-page .fb-diagram-head + .chord-diagram-wrap {
  margin-top: 0;
}

/* WHERE THE LINE BREAKS, AND WHY THERE.
   No width query decides this — the content does. The caption asks for
   26rem before it will let the stepper share its line; below that the
   flex line cannot hold both comfortably and the stepper wraps to a full
   row of its own, still directly above the neck.

   26rem is measured, not chosen: the widest caption the two scale tabs
   produce is a seven-note scale with its degrees (≈300px of text under a
   name of ≈180px), and the stepper is ≈286px including its POSITION
   label. 416 + 21 + 286 = 723px of content width, which a 1024px window
   clears (≈740px) and a 768px tablet does not (≈713px) — so the split
   lands exactly where the two-column reading stops being comfortable
   rather than where a round number falls.

   `space-between` puts a lone wrapped item at the start of its line, so
   the stepper needs no second alignment rule for the stacked case. */
.fretboard-page .fb-diagram-head > .sv-info {
  flex: 1 1 26rem;
  min-width: 0;
  /* Inside the header the caption is not the page's closing section any
     more — it is the diagram's title. The section gap belongs to the
     tabs that still print it under playback. */
  margin: 0;
}

.fretboard-page .fb-diagram-head > .tool-nav {
  flex: 0 1 auto;
  max-width: 100%;
  margin: 0;
}

/* A tool with no positions to step through (Beginner's curated items)
   shows no stepper rather than an empty one — otherwise the header keeps
   a zero-width flex item that still earns a gap, and on a phone a blank
   row between the caption and the neck. Keyed on the stepper's own
   emptiness, the same way the footer rhythm always was, so it is the
   tool's data that decides and not a level class. Same idiom as
   .fb-control-line's own empty guard above. */
.fretboard-page .fb-diagram-head > .tool-nav:has(> :empty) {
  display: none;
}

/* AND A HEADER WITH NOTHING LEFT IN IT IS NOT A HEADER.
   Removing the last child is not enough: an empty FLEX container does not
   self-collapse the way an empty block does, so both of its margins
   survive and it leaves a blank strip of exactly --fb-gap-head between the
   controls and the diagram. Measured at 40px where the same relationship
   is 27px one tab over — which is precisely the kind of per-tab drift the
   spacing tokens exist to prevent.

   Two ways a header can empty out, and both really happen:

     Beginner Arpeggio  the stepper is the only child and JS never fills
                        it, so the guard above hides it and nothing is left
     Chords             the caption is the only child and renderInfoLine()
                        in theory.js hides the block outright for a family
                        with nothing to say

   Matched on the emptiness itself in the first case and on the inline
   display JS actually writes in the second — `el.style.display = 'none'`
   serialises with the space. Never on a level or a tab name: it is the
   tool's own data that decides whether it has a header, the same way it
   decides whether it has a stepper. */
.fretboard-page .fb-diagram-head:has(> .tool-nav:only-child > :empty),
.fretboard-page .fb-diagram-head:has(> .sv-info:only-child[style*="display: none"]) {
  display: none;
}

/* Playback is the neck's footer on every tab with a header — nothing
   sits between the two any more — so it takes the neck's own gap instead
   of the tighter gap that used to separate it from the stepper. Stated
   through the header's presence rather than per tab, which is what let
   Arpeggio inherit the right rhythm from its wrapper alone. */
.fretboard-page section:has(> .fb-diagram-head) > .fretboard-sequence-actions {
  margin-top: var(--fb-gap-neck);
}

/* Notes and the Starter neck reach the same place by the other route: they
   have no diagram header at all, so the rule above cannot see them, but
   playback still sits directly under the neck exactly as it does on every
   tab that does have one. Same gap, stated for the two sections that own
   a bare neck — measured, not assumed: without this they took the tighter
   --fb-gap-row (12.8px against the 32px everywhere else) and the control
   read as belonging to the neck's own block rather than following it. */
.fretboard-page :is(#tool-notes, #tool-starter-neck) > .fretboard-sequence-actions {
  margin-top: var(--fb-gap-neck);
}

/* Portrait Starter keeps all fifteen frets. Transport and the existing
   one-time rotation hint precede the neck rather than requiring a long scroll. */
@media (max-width: 700px) and (orientation: portrait) {
  .fretboard-page #tool-starter-neck > .fretboard-sequence-actions {
    margin: 0 0 var(--fb-gap-neck);
  }
  .fretboard-page #tool-starter-neck > .fb-rotate-hint {
    margin: 0 0 var(--fb-gap-group);
  }
}

/* PORTAL POSITION DIAGRAMS — the approved Intermediate Pentatonic
   hierarchy, shared by every equivalent diagram rather than owned by one
   tab or level. Arpeggio, Pentatonic, Diatonic and Intervals all carry the
   explicit --positioned modifier. The first three have a two-part subject:

       POSITION  1 2 3 4 5 All          C · E♭ · F · G · B♭
                                        1 · ♭3 · 4 · 5 · ♭7
       ─────────────────── the neck ───────────────────
                         playback

   Position leads on the left, notes/degrees form a right-aligned caption
   where the tool has one, and playback shares the neck's horizontal centre.
   Headers without a position (Chords) keep their
   natural caption-only composition. */
.fretboard-page .fb-diagram-head.fb-diagram-head--positioned {
  flex-wrap: nowrap;
  /* Extra modal context must not push Position below the note/formula pair. */
  align-items: flex-start;
}

.fretboard-page .fb-diagram-head--positioned > .tool-nav {
  order: 1;
  flex: 0 0 auto;
  /* The raised label and focus targets must stay visible at tablet widths too. */
  overflow: visible;
}

.fretboard-page .fb-diagram-head--positioned :is(#fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions)::before {
  content: attr(aria-label);
  flex: 0 0 100%;
  /* Raise the caption while the buttons stay aligned with the formula. */
  transform: translateY(calc(var(--fb-gap-row) * -0.5));
  color: rgba(var(--w), 0.38);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  line-height: 1.2;
  text-transform: uppercase;
}

.fretboard-page .fb-diagram-head--positioned > .sv-info {
  order: 2;
  flex: 0 1 auto;
  text-align: right;
}

.fretboard-page .fb-diagram-head--positioned > .sv-info .caged-formula-row > div {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: flex-end;
  row-gap: 0.2rem;
}

.fretboard-page .fb-diagram-head--positioned > .sv-info .sv-formula {
  flex-basis: auto;
  width: 100%;
}

.fretboard-page .fb-diagram-head--positioned > .sv-info .sv-formula + .sv-formula::before {
  content: none;
}

/* Intermediate Arpeggio, Pentatonic and Diatonic use the same positioned
   header. Keep the formula attached to the position controls instead of
   pushing it to the far edge of the page: both pieces describe the single
   diagram directly below them. Other levels retain their existing layout. */
body.fretboard-page[data-fb-level="intermediate"] #cagedIntInfoBar .fb-formula-cell,
body.fretboard-page:is([data-fb-level="beginner"], [data-fb-level="intermediate"]) .fb-diagram-head--positioned .fb-formula-cell {
  display: inline-block;
  width: 2.5em;
  text-align: center;
  white-space: nowrap;
}

.fretboard-page[data-fb-level="intermediate"] #cagedIntTool .fb-diagram-head {
  justify-content: flex-start;
}
.fretboard-page[data-fb-level="intermediate"] #cagedIntInfoBar {
  flex: 0 1 auto;
  text-align: left;
}
.fretboard-page[data-fb-level="intermediate"] #cagedIntInfoBar .caged-formula-row > div {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: flex-start;
  row-gap: 0.2rem;
}
.fretboard-page[data-fb-level="intermediate"] #cagedIntInfoBar .sv-formula {
  flex-basis: auto;
  width: 100%;
}
.fretboard-page[data-fb-level="intermediate"] #cagedIntInfoBar .sv-formula + .sv-formula::before {
  content: none;
}


body.fretboard-page:is([data-fb-level="beginner"], [data-fb-level="intermediate"]) .fb-diagram-head--positioned {
  justify-content: flex-start;
}

body.fretboard-page:is([data-fb-level="beginner"], [data-fb-level="intermediate"]) .fb-diagram-head--positioned > .sv-info {
  flex: 0 1 auto;
  text-align: left;
}

body.fretboard-page:is([data-fb-level="beginner"], [data-fb-level="intermediate"]) .fb-diagram-head--positioned > .sv-info .caged-formula-row > div {
  align-items: flex-start;
}

.fretboard-page .fretboard-sequence-actions {
  justify-content: center;
}

@media (max-width: 700px) {
  .fretboard-page .fb-diagram-head.fb-diagram-head--positioned {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }

  .fretboard-page .fb-diagram-head--positioned :is(#fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) {
    flex-wrap: wrap !important;
  }

  .fretboard-page .fb-diagram-head--positioned :is(#fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions)::before {
    /* Keep the same label/button separation in the stacked mobile layout. */
    transform: none;
    margin-bottom: calc(var(--fb-gap-row) * 0.5);
  }

  .fretboard-page .fb-diagram-head--positioned > .sv-info {
    width: 100%;
    text-align: left;
  }

  .fretboard-page .fb-diagram-head--positioned > .sv-info .caged-formula-row > div {
    align-items: flex-start;
  }
}

/* PHONES: one column, no flex.
   Below 700px the stepper is a horizontally scrolling row
   (data-scroll-row, see MOBILE CONTROL ROWS) and a flex item sized from
   its own content would push the page wider than the screen before it
   ever got the chance to scroll. A plain block stack gives it the full
   width to scroll inside, and keeps the vertical order the wrap already
   produces: caption, then POSITION, then the neck. */
@media (max-width: 700px) {
  .fretboard-page .fb-diagram-head {
    display: block;
  }
  /* The gap is carried by the stepper, not by the caption: where there is
     no stepper the caption must sit straight on the neck, with no blank
     row standing in for a control that is not there. */
  .fretboard-page .fb-diagram-head > .sv-info + .tool-nav {
    margin-top: var(--fb-gap-row);
  }
}

/* ---- THE INFORMATION BLOCK ------------------------------------------
   The caption itself, wherever it sits. One placement now, and one
   holdout:

     in the diagram header (Pentatonic, Diatonic, Arpeggio, Intervals,
       CAGED at Intermediate, Chords) — read before the dots, so the
       musical context is understood before it is interpreted
     under its own diagram (the Advanced/Expert CAGED islands) — where
       the caption belongs to one island out of five rather than to the
       page

   Secondary but never hidden either way — plain text on the page
   background with no card, no panel and no divider, separated by space
   alone. The base rule below still states the section gap, because that
   is what an info block costs when it is NOT in a header; in a header the
   header owns the spacing and this block carries none.

   The name is the only line at full strength. Notes and degrees are
   muted supporting detail and sit side by side where the line holds
   them, since they are read against each other. */
.fretboard-page .sv-info {
  margin: var(--fb-gap-section) 0 0;
  gap: 0;
}

.fretboard-page .sv-info .caged-formula-row > div {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 1.1rem;
  row-gap: 0.2rem;
}

.fretboard-page .sv-info .sv-name {
  flex-basis: 100%;
  line-height: 1.2;
}

/* Stacked by default: a seven- or eight-note scale plus its degrees is
   too long for one line on a phone. */
.fretboard-page .sv-info .sv-formula {
  flex-basis: 100%;
  margin-top: 0 !important;
}

/* Wide enough for both — the separator exists only in this branch, so it
   can never end up stranded at the start of a wrapped line. */
@media (min-width: 700px) {
  .fretboard-page .sv-info .sv-formula {
    flex-basis: auto;
  }
  .fretboard-page .sv-info .sv-formula + .sv-formula::before {
    content: "";
    display: inline-block;
    width: 1px;
    height: 0.8em;
    margin-right: 1.1rem;
    vertical-align: -0.05em;
    background: rgba(var(--w), 0.18);
  }
}

/* ---- MOBILE HIT TARGETS ----------------------------------------------
   The controls stay visually compact — a row of 44px-tall pills would
   dominate a phone screen — but every one of them gets a 44px EFFECTIVE
   hit area.

   The target grows by exactly half the row gap on each side. Adjacent
   targets then tile edge to edge and can never overlap, whichever way a
   row wraps, because the space they grow into is space nothing else
   occupies. The gap is raised to 0.7rem first so that half of it either
   side of a 33px control clears 44px:

       33px control + 2 × 5.6px = 44.2px

   --fb-hit-pad is derived from --fb-hit-gap rather than typed twice, so
   the two can never drift apart and reintroduce overlap. */
@media (max-width: 700px) {
  .fretboard-page {
    --fb-hit-gap: 0.7rem;
    --fb-hit-pad: calc(var(--fb-hit-gap) / 2);
  }

  /* .sv-type-row is a nested flex container inside the chord/scale type
     rows with a tighter gap of its own; without it the hit areas inside
     those rows overlapped by 6.4px while every other row tiled cleanly. */
  .fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
      #chordIntFamily, #arpCats, #arpItems,
      #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
      #intervalsFamily,
    #intervalsFamily,
      #globalQuality, #intervalsPicker,
      #cagedIntLayers, #chordIntType, #chordTypes,
      #chordTriadStrings, #fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions),
  .fretboard-page .sv-type-row {
    gap: var(--fb-hit-gap);
  }

  /* A floor on the painted control too, so a single-character option like
     position "1" is never a tiny sliver inside a large invisible target. */
  .fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
      #chordIntFamily, #arpCats, #arpItems,
      #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
      #intervalsFamily,
    #intervalsFamily,
      #globalQuality, #intervalsPicker,
      #cagedIntLayers, #chordIntType, #chordTypes,
      #chordTriadStrings, #fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) button:not(.sc-info-btn):not(.sv-blues-btn),
  .fretboard-page .sv-blues-btn {
    position: relative;
    min-height: 33px;
    min-width: 33px;
  }

  /* The Root select takes the same floor. It is the one control on these
     rows that is not a button, so it was the one control the 33px floor
     missed — and once every pill on the line is 33px, a 30px select is
     both a smaller target and a visible step in a row whose whole point
     is that it has none. */
  .fretboard-page .chord-voicing-select select,
  .fretboard-page .fb-rootpick-btn {
    height: 33px;
    min-height: 33px;
  }

  /* Rows are easier to hit than they are to read on a phone. */
  .fretboard-page .fb-rootpick-option {
    min-height: 34px;
  }

  /* The hit area itself. Inset by a negative half-gap on all four sides,
     so it reaches exactly to the midpoint between this control and its
     neighbour. Nothing is painted — this only extends what a finger can
     land on. */
  .fretboard-page :is(#fbxCats, #fbxTypes, #chordCats,
      #chordIntFamily, #arpCats, #arpItems,
      #cagedBegQualityRow, #cagedIntQualityRow, #notesSharpToggle,
      #intervalsFamily,
    #intervalsFamily,
      #globalQuality, #intervalsPicker,
      #cagedIntLayers, #chordIntType, #chordTypes,
      #chordTriadStrings, #fbxPositions, #arpPositions, #intervalsPositions, #cagedIntPositions) button:not(.sc-info-btn):not(.sv-blues-btn)::after,
  .fretboard-page .sv-blues-btn::after {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--fb-hit-pad));
  }
}


/* ---- NECK PLACEMENT AND STATES --------------------------------------
   The necks themselves are generated by fbNeckChromeSVG() in
   fretboard.js. This is only what CSS owns: the diagram's position in the
   column, and the states SVG attributes cannot express. */

/* ---- ROOT PICKER — the popover ---------------------------------------
   Deliberately OUTSIDE the control system above. A control that sits on
   a row is flat by rule — no shadow, no gradient, nothing that lifts it
   off the page. A floating panel is not one of those: it has to read as
   hovering above the neck, and every other popover in the portal (the
   language menu, the sidebar's level picker) says so with the same soft
   shadow. Keeping it in the control block would have made it look like
   an exception to that rule instead of a different kind of object.

   Its trigger, .fb-rootpick-btn, stays in the control system where it
   belongs — it is a control on a row, and it is flat. */

/* THE PANEL. Fixed, not absolute: these controls sit in rows that scroll
   sideways on a phone (PortalRows), and an absolutely positioned panel
   is clipped by the first ancestor that scrolls. Fixed takes it out of
   that chain entirely, which is also why the position is computed in JS
   rather than inherited. z-index 210 is the portal's popover tier — the
   same one the sidebar's level picker uses — deliberately below the
   mobile topbar (250) and the nav drawer, not an arbitrary large number. */
.fretboard-page .fb-rootpick-panel {
  position: fixed;
  z-index: 210;
  min-width: 4.5rem;
  max-width: min(16rem, calc(100vw - 16px));
  padding: 4px;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: #0a0a0a;
  border: 1px solid rgba(var(--w), 0.13);
  border-radius: 8px;
  /* Depth, not drama. */
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.45);
}

.fretboard-page .fb-rootpick-panel[hidden] {
  display: none;
}

.fretboard-page .fb-rootpick-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 28px;
  padding: 0.25rem 0.45rem;
  border-radius: 4px;
  color: rgba(var(--w), 0.72);
  font-family: var(--font-main);
  font-size: 0.8125rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

/* The marker column exists on every row, selected or not, so the labels
   sit on one left edge instead of shifting when the selection moves. */
.fretboard-page .fb-rootpick-mark {
  flex: 0 0 2px;
  width: 2px;
  height: 11px;
  border-radius: 1px;
  background: transparent;
}

.fretboard-page .fb-rootpick-option:hover,
.fretboard-page .fb-rootpick-option.is-active {
  background: rgba(var(--w), 0.07);
  color: var(--text-main);
}

.fretboard-page .fb-rootpick-option[aria-selected="true"] {
  background: rgba(var(--w), 0.045);
  color: var(--text-main);
  font-weight: 600;
}

.fretboard-page .fb-rootpick-option[aria-selected="true"] .fb-rootpick-mark {
  background: var(--accent);
}

.fretboard-page .fb-rootpick-option[aria-disabled="true"] {
  opacity: 0.4;
  cursor: default;
}

.fretboard-page .fb-rootpick-option[aria-disabled="true"]:hover {
  background: transparent;
  color: rgba(var(--w), 0.72);
}
/* #notesMap takes the shared --fb-gap-neck with the other diagrams; see
   the VERTICAL RHYTHM block above. */

/* Hover on a note marker: a single light ring, no scale, no glow, no
   shadow. The marker must not appear to lift off the neck — it is a label
   on a string, and the ring only confirms which one the pointer will
   play. Scoped to the Fretboard page's own necks. */
.fretboard-page .sv-board-wrap .sv-dot > circle {
  transition: stroke 0.12s ease;
  stroke: transparent;
  stroke-width: 2;
}

.fretboard-page .sv-board-wrap .sv-dot:hover > circle {
  stroke: rgba(255, 238, 201, 0.85);
}


/* ---- CAGED SHAPE RANGES ----------------------------------------------
   Where the five CAGED shapes actually sit on the neck, stated UNDER it.

   This replaces the old C3 / A4 / G5 / E1 / D2 markers, which sat in the
   strip between the fret numbers and the top string — one row of small
   glyphs directly under another row of small glyphs, competing with the
   fret numbers for the same 11 units of space and saying nothing about
   how far each shape reaches.

   A SECOND SVG INSIDE THE SAME WRAPPER, never a taller neck. FB_NECK's
   own comment records why: a CAGED-only vertical-padding parameter once
   made the CAGED viewBox 30 units taller, which on a portrait phone (the
   whole SVG is fitted to the screen width after rotating) silently
   rendered the CAGED neck ~14% smaller than the identical Scales neck.
   The strip therefore carries its own viewBox, of exactly the neck's own
   width — so `width: 100%` on both puts one internal unit at the same
   number of screen pixels in both, and a bracket edge lands on its fret
   to the pixel at every viewport, with no measuring and no JS.

   Living inside .sv-board-wrap is also what makes LEFT-HANDED free: the
   existing [data-handed="left"] .sv-board-wrap svg mirror flips this
   strip with the neck it annotates, and the matching svg text rule flips
   the labels back upright — the same two-flip trick every neck on the
   page already uses. The ranges cannot mirror out of step with the frets
   because they are mirrored by the same rule, in the same wrapper.

   Portrait keeps the same ranges along the low-E side of the neck,
   just as below the horizontal neck. This side mirrors with handedness.
   neck-orientation.js fits both SVGs at the same scale; only the caption
   shortens to C/A/G/E/D. No frets or note positions change. */
.fretboard-page .caged-range-compact {
  display: none;
}

.fretboard-page #cagedIntMap .caged-range-strip text {
  /* Compensate for a narrower SVG without enlarging the neck itself. */
  font-size: clamp(13px, calc(33px - 1.5625vw), 22px);
}

@media (max-width: 700px) {
  /* Keep the five layers together in the same swipeable row used by the
     other Fretboard controls. */
  .fretboard-page #cagedIntLayers {
    flex-wrap: nowrap;
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
  }
}

@media (max-width: 700px) and (orientation: portrait) {
  .fretboard-page #cagedIntMap.fb-vertical {
    position: relative;
  }

  .fretboard-page #cagedIntMap.fb-vertical > svg:first-child {
    position: relative;
    left: calc(var(--fb-vert-annotation-width) + 10px);
  }

  [data-handed="left"] .fretboard-page #cagedIntMap.fb-vertical > svg:first-child {
    left: 0;
  }

  .fretboard-page #cagedIntMap.fb-vertical .caged-dot > text {
    font-size: 9px;
  }

  .fretboard-page #cagedIntMap.fb-vertical .caged-range-strip {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
  }

  [data-handed="left"] .fretboard-page #cagedIntMap.fb-vertical .caged-range-strip {
    left: calc(100% - var(--fb-vert-annotation-width));
  }

  /* Shared neck transforms keep ranges aligned and text upright for
     either playing hand, including the order of the two range tracks. */
  .fretboard-page #cagedIntMap.fb-vertical .caged-range-strip text {
    font-size: 11px;
  }

  .fretboard-page #cagedIntMap.fb-vertical .caged-range-word {
    display: none;
  }

  .fretboard-page #cagedIntMap.fb-vertical .caged-range-compact {
    display: inline;
  }
}

/* ==========================================================
   SETTINGS PAGE
   ----------------------------------------------------------
   ONE SCOPE, ONE CONTROL SYSTEM. Everything below is written under
   `.settings-page`, the class settings.html carries on <body>. Nothing
   here leaks to another page, and — just as important — nothing here
   reaches the SIDEBAR, which sits inside the same <body> and contains
   its own .level-btn (the level picker popover), .lang-btn and
   .nav-gear. That is why every rule goes through `.set-*` markup, or
   through `.set-shell`, the wrapper around the page's own content. A
   bare `.settings-page .level-btn` would have restyled the popover.

   WHY THE CLASS IS ON <body> AND NOT ON <main>. The mobile tab row is
   sticky under the 56px .nav-mobile-topbar, and `body { overflow-x:
   hidden }` (the mobile drawer's belt-and-braces rule) makes <body> a
   scroll container, which silently kills `position: sticky` for
   everything inside it. `overflow-x: clip` clips identically without
   creating that scroll container — but only a selector that can reach
   <body> can say so, and only for this page.

   THE HEADING CONTRACT. Three levels, three different jobs:

     h1  .platform-page h1     the page          (portal-wide, untouched)
     h2  .set-group-title      a GROUP of settings — small, uppercase,
                               muted. General / Sound / Learning /
                               Diagrams / Account / Support.
     h3  .set-item-title       ONE setting — sentence case, larger,
                               near-white. Language, Handedness, Piano
                               sound, Level, Change password…

   Before this, a group heading and a setting heading were both <h2>
   and both drew as `.platform-page h2`, so "Sound" and "Piano sound"
   looked identical and the page read as one flat list of 12 things.
   `.platform-page h2` itself is NOT touched — other pages keep it.

   GOLD MEANS ONE THING: selection, focus, and the primary action. Not
   group headings (they went muted), not the avatar, not borders at
   rest. Selection is never colour alone either — an active control
   also carries a filled mark or a border weight change.
   ========================================================== */

.settings-page {
  /* 1000px is the reading width; the shell is PINNED to the portal's
     content axis and never centres itself in the leftover space. */
  --set-shell-max: 1000px;

  --set-item-gap: 2.1rem;
  --set-group-gap: 2.9rem;

  /* Every control in Settings is at least this tall — buttons, choices,
     inputs and radio rows alike. */
  --set-control-h: 44px;
  --set-radius: 10px;

  --set-line: rgba(var(--w), 0.09);
  --set-line-strong: rgba(var(--w), 0.16);
  --set-surface: rgba(var(--w), 0.025);
  --set-choice-bg: rgba(var(--w), 0.03);

  /* Inputs keep the portal's solid field colour rather than a
     translucent one, so the -webkit-autofill inset shadow further up
     this file still matches what is under it. */
  --set-field-bg: #181818;

  --set-gold-soft: rgba(216, 168, 79, 0.12);
  --set-gold-line: rgba(216, 168, 79, 0.55);
  --set-danger-text: #d98b8b;
  --set-danger-line: rgba(196, 90, 90, 0.38);
}

/* ---- SHELL ---------------------------------------------------------- */

.settings-page .set-shell {
  max-width: var(--set-shell-max);
  margin-inline: 0;
}

.settings-page .set-head {
  margin-bottom: 1.7rem;
}

.settings-page .set-head h1 {
  margin-bottom: 0;
}

/* ---- TABS ----------------------------------------------------------- */
/* Drawing is inherited from the TABBED PAGE block above — gold text for
   the active tab, a neutral rail under the row. Only the focus ring and
   the mobile sticky behaviour are added here. */

.settings-page .set-tabs {
  margin-bottom: 2.1rem;
}

.settings-page .set-tabs button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
  color: var(--text-main);
}

/* ---- GROUPS AND ITEMS ------------------------------------------------ */

.settings-page .settings-panel {
  margin-top: 0;
}

.settings-page .settings-panel[hidden] {
  display: none;
}

.settings-page .set-group {
  margin: 0 0 var(--set-group-gap);
}

.settings-page .set-group:last-child {
  margin-bottom: 0;
}

/* The rule between two groups is the only divider on the page. Sections
   are separated by space, not by boxes. */
.settings-page .set-group + .set-group {
  padding-top: var(--set-group-gap);
  border-top: 1px solid var(--set-line);
}

.settings-page .set-group-title {
  margin: 0 0 1.5rem;
  padding: 0;
  font-family: var(--font-main);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(var(--w), 0.56);
}

.settings-page .set-item {
  margin: 0 0 var(--set-item-gap);
  min-width: 0;
}

.settings-page .set-item:last-child {
  margin-bottom: 0;
}

.settings-page .set-item-title {
  margin: 0 0 0.4rem;
  font-family: var(--font-main);
  font-size: 1.12rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(var(--w), 0.92);
}

.settings-page .set-item-desc {
  margin: 0 0 1rem;
  max-width: 54ch;
  color: var(--text-muted);
  font-size: 0.875rem;
  line-height: 1.6;
}

.settings-page .set-note {
  margin: 0;
  max-width: 48ch;
  color: rgba(var(--w), 0.56);
  font-size: 0.78rem;
  line-height: 1.55;
}

/* Two equal settings side by side (Language|Handedness, Piano|Guitar).
   Flex rather than a media query: the pair splits the moment the column
   is wide enough for both at 280px, which is the right answer at every
   viewport AND at both sidebar states, without naming a breakpoint. */
.settings-page .set-duo {
  display: flex;
  flex-wrap: wrap;
  gap: 2.1rem 2.75rem;
}

.settings-page .set-duo > .set-item {
  flex: 1 1 280px;
  min-width: 0;
  margin-bottom: 0;
}

/* Help: the form takes roughly two thirds, Legal the rest, and they
   stack when there is no longer room for both. */
.settings-page .set-split {
  display: flex;
  flex-wrap: wrap;
  gap: 2.1rem 3rem;
}

.settings-page .set-split-main {
  flex: 3 1 320px;
  min-width: 0;
  margin-bottom: 0;
}

.settings-page .set-split-side {
  flex: 1 1 220px;
  min-width: 0;
  margin-bottom: 0;
}

/* ---- ONE CONTROL SYSTEM --------------------------------------------- */
/* .set-choice is every selectable option on the page: language, level,
   notation, instrument sound and each diagram-label option. The
   older per-component skins (.level-btn, .lang-btn, .instrument-sound-btn)
   still carry the handlers; this owns how they look. */

.settings-page .set-choice {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-height: var(--set-control-h);
  padding: 0.6rem 0.95rem;
  background: var(--set-choice-bg);
  border: 1px solid var(--set-line-strong);
  border-radius: var(--set-radius);
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--text-muted);
  text-align: left;
  white-space: normal;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}

.settings-page .set-choice:hover {
  background: rgba(var(--w), 0.055);
  border-color: rgba(var(--w), 0.3);
  color: var(--text-main);
}

/* A card may wrap a native radio (Handedness, Level display): its checked
   state is the same "active" as a button's class. */
.settings-page .set-choice.active,
.settings-page .set-choice:has(> input:checked) {
  background: var(--set-gold-soft);
  border-color: var(--set-gold-line);
  color: var(--accent);
  font-weight: 600;
}

.settings-page .set-choice:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* One focus ring for every interactive thing inside the page content. */
.settings-page .set-shell :is(.set-choice, .set-btn, .password-toggle, .set-legal-link, input, textarea):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The selection marker. Colour is never the only signal: an unselected
   cell shows a ring, a selected one a filled disc. */
.settings-page .set-mark {
  flex: 0 0 13px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid rgba(var(--w), 0.28);
  transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}

.settings-page .set-choice.active .set-mark {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.18);
}

.settings-page .set-choices {
  display: grid;
  gap: 0.6rem;
}

/* Every two-option setting is the same pair, at one width: wide enough
   for its longest label ("Progressive Learning") on a single line. */
.settings-page .set-choices--pair {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  max-width: 380px;
}

/* The learning pillars offer two guitar sounds, so this is a pair like
   Language, at the same width: stretched across the column, two buttons
   read as empty space. The one-column fallback is at the bottom of this
   block. */
.settings-page .set-choices--sound {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  max-width: 380px;
}

/* Every level visible at once, at every width — no hidden sideways
   scroll, which is what the old nowrap row had. */
.settings-page .set-choices--level {
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
}

/* A locked level renders its "Soon" caption through
   .level-btn.level-locked::after, which needs its own line. */
.settings-page .set-choices--level .set-choice {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0.1rem;
}

.settings-page .set-choices--level .level-btn.level-locked::after {
  margin-top: 0;
}

/* The native radio inside a card is visually hidden, so the card
   carries its focus ring. */
.settings-page .set-choice:has(> input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---- DIAGRAM LABELS -------------------------------------------------- */
/* Four independently configurable families in one compact list. Every
   option keeps its own visible label: there is no distant column header
   to decode and no desktop-only dot button without a name. */

.settings-page .set-diagram-list {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  max-width: 800px;
}

.settings-page .set-diagram-row {
  display: grid;
  grid-template-columns: minmax(150px, 172px) minmax(0, 1fr);
  align-items: center;
  gap: 1.15rem;
}

/* ONE ROW PER FAMILY, ONLY THE OPTIONS THAT FAMILY HAS. A flex row, not a
   fixed grid: a three-option family fills the row, the two-option
   interval family packs its two buttons to the left and simply ends —
   there is no third cell held open for a fingering mode intervals do not
   offer, and the two buttons still sit side by side rather than spread
   apart. Every option button caps at the same width, so intervals do not
   balloon. On a phone the buttons wrap; nothing scrolls sideways. The
   options keep their fixed DOM order (notes, function, fingering), so the
   first option down every family is Note names. */
.settings-page .set-diagram-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  min-width: 0;
}

.settings-page .set-diagram-rowlabel {
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.35;
  color: rgba(var(--w), 0.82);
}

.settings-page .set-diagram-choice {
  flex: 1 1 132px;
  max-width: 200px;
  min-width: 0;
  padding-inline: 0.75rem;
}

.settings-page .set-diagram-choice > :last-child {
  min-width: 0;
  overflow-wrap: anywhere;
}

.settings-page .set-diagram-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 0.65rem 1rem;
  max-width: 800px;
  margin-top: 1.2rem;
}

/* ---- BUTTONS --------------------------------------------------------- */

.settings-page .set-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: var(--set-control-h);
  padding: 0.6rem 1.2rem;
  background: transparent;
  border: 1px solid var(--set-line-strong);
  border-radius: var(--set-radius);
  color: var(--text-main);
  font-family: var(--font-main);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}

.settings-page .set-btn:hover {
  background: rgba(var(--w), 0.05);
  border-color: rgba(var(--w), 0.3);
}

.settings-page .set-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.settings-page .set-btn:disabled:hover {
  background: transparent;
  border-color: var(--set-line-strong);
}

.settings-page .set-btn-icon {
  flex: 0 0 auto;
  opacity: 0.8;
}

.settings-page .set-btn--primary {
  background: #b8862d;
  border-color: #b8862d;
  color: #111;
}

.settings-page .set-btn--primary:hover {
  background: #a07028;
  border-color: #a07028;
}

.settings-page .set-btn--quiet {
  padding-inline: 0.8rem;
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
  font-weight: 500;
}

.settings-page .set-btn--quiet:hover {
  background: rgba(var(--w), 0.05);
  border-color: transparent;
  color: var(--text-main);
}

/* Opening the delete flow is a SECONDARY action — an outline, never a
   filled red block. */
.settings-page .set-btn--danger {
  border-color: var(--set-danger-line);
  color: var(--set-danger-text);
}

.settings-page .set-btn--danger:hover {
  background: rgba(196, 90, 90, 0.1);
  border-color: rgba(196, 90, 90, 0.55);
  color: #e8a5a5;
}

.settings-page .set-btn--danger-solid {
  background: rgba(196, 90, 90, 0.16);
  border-color: rgba(196, 90, 90, 0.5);
  color: #e8a5a5;
}

.settings-page .set-btn--danger-solid:hover {
  background: rgba(196, 90, 90, 0.26);
  border-color: rgba(196, 90, 90, 0.65);
}

.settings-page .set-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-top: 0.35rem;
}

.settings-page .set-item-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  margin-top: 0.85rem;
}

/* ---- FORMS ----------------------------------------------------------- */

.settings-page .set-form {
  max-width: 500px;
  margin-top: 0;
  gap: 0.95rem;
}

.settings-page .set-form--wide {
  max-width: none;
}

.settings-page .set-label {
  margin-bottom: 0.4rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: rgba(var(--w), 0.62);
}

.settings-page .set-shell :is(.portal-form input, .portal-form textarea) {
  min-height: var(--set-control-h);
  padding: 0.7rem 0.95rem;
  background: var(--set-field-bg);
  border: 1px solid var(--set-line-strong);
  border-radius: var(--set-radius);
  font-size: 0.9rem;
}

.settings-page .set-shell :is(.portal-form input, .portal-form textarea):focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(216, 168, 79, 0.15);
}

.settings-page .set-shell .password-field input {
  padding-right: 3.1rem;
}

.settings-page .set-shell .password-toggle {
  right: 0.35rem;
  min-width: 40px;
  min-height: 40px;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
}

/* ---- FEEDBACK -------------------------------------------------------- */
/* An empty error/success line used to reserve `min-height: 1.2em` plus a
   1rem margin in six places at once, which is most of why the page was
   as long as it was. Empty means gone; the node stays in the DOM, so the
   live region is still there to announce the moment it is filled. */

.settings-page .set-feedback {
  margin: 0.9rem 0 0;
  min-height: 0;
  font-size: 0.85rem;
  line-height: 1.5;
}

.settings-page .set-feedback:empty {
  display: none;
}

.settings-page .set-feedback--error {
  color: #e58f8f;
}

.settings-page .set-feedback--success {
  color: #6fbf73;
}

.settings-page .set-feedback--muted {
  margin-top: 0.6rem;
  color: var(--text-muted);
  font-size: 0.82rem;
}

/* ---- ACCOUNT --------------------------------------------------------- */

.settings-page .set-identity {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 1.4rem;
  padding: 0.85rem 1.05rem;
  background: var(--set-surface);
  border: 1px solid var(--set-line);
  border-radius: 12px;
}

.settings-page .set-identity-avatar {
  display: inline-flex;
  flex: 0 0 38px;
  width: 38px;
  height: 38px;
  align-items: center;
  justify-content: center;
  background: rgba(var(--w), 0.07);
  border: 1px solid var(--set-line-strong);
  border-radius: 50%;
  color: rgba(var(--w), 0.72);
  font-size: 0.95rem;
  font-weight: 700;
  text-transform: uppercase;
}

.settings-page .set-identity-text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

/* One line, shortened with an ellipsis rather than broken mid-address —
   "…@ex / ample.com" reads as a bug. The element still holds the whole
   address (settings.js sets textContent AND title), so a screen reader
   reads all of it and a desktop hover shows all of it. */
.settings-page .set-identity-email {
  color: rgba(var(--w), 0.92);
  font-size: var(--type-body);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.settings-page .set-identity-badge {
  color: rgba(var(--w), 0.56);
  font-size: var(--type-xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* One banner for the whole tab, not a repeated note per form. */
.settings-page .set-banner {
  margin: 0 0 1.4rem;
  padding: 0.8rem 1rem;
  background: var(--set-surface);
  border: 1px solid var(--set-line-strong);
  border-left: 2px solid var(--set-gold-line);
  border-radius: 10px;
  color: rgba(var(--w), 0.72);
  font-size: 0.85rem;
  line-height: 1.55;
}

.settings-page .set-banner[hidden] {
  display: none;
}

.settings-page .set-block {
  padding: 1.4rem 1.5rem;
  background: rgba(var(--w), 0.018);
  border: 1px solid var(--set-line);
  border-radius: 14px;
}

/* A red edge and a red title. Not a red panel. */
.settings-page .set-danger {
  background: rgba(196, 90, 90, 0.035);
  border-color: var(--set-danger-line);
}

.settings-page .set-danger-title {
  color: #e0a0a0;
}

/* .portal-form's own display:flex would otherwise defeat the closed
   state, since it is defined later in this file than .settings-confirm
   used to be. */
.settings-page .settings-confirm {
  display: none;
  margin-top: 1.1rem;
}

.settings-page .settings-confirm.open {
  display: flex;
}

.settings-page .set-legal-link {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  font-size: 0.875rem;
}

/* ---- NARROW ---------------------------------------------------------- */

@media (max-width: 700px) {
  .settings-page {
    --set-item-gap: 1.7rem;
    --set-group-gap: 2.2rem;
  }

  .settings-page .set-head {
    margin-bottom: 1.2rem;
  }

  .settings-page .set-item-desc {
    margin-bottom: 0.85rem;
  }

  .settings-page .set-diagram-list {
    gap: 0;
  }

  .settings-page .set-diagram-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.55rem;
    padding: 0.95rem 0;
    border-top: 1px solid var(--set-line);
  }

  .settings-page .set-diagram-row:first-child {
    padding-top: 0;
    border-top: 0;
  }

  /* Phone: the family name sits above a fixed grid of equal-width option
     buttons — three columns per family, two for intervals (marked with
     `--pair` on the markup, so no `:has()` and no 2 + 1 wrap). The grid
     shrinks its own tracks rather than overflowing, and button text may
     wrap to two lines. */
  .settings-page .set-diagram-options {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.45rem;
  }

  .settings-page .set-diagram-options--pair {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .settings-page .set-diagram-choice {
    flex-direction: column;
    justify-content: center;
    gap: 0.35rem;
    padding: 0.6rem 0.5rem;
    font-size: 0.8rem;
    line-height: 1.25;
    text-align: center;
    /* Fill the grid cell; the desktop 200px cap does not apply here. */
    max-width: none;
  }

  .settings-page .set-block {
    padding: 1.15rem 1.1rem;
  }
}

@media (max-width: 560px) {
  /* Two per row, and a lone fifth level takes the whole width rather
     than sitting half-wide next to nothing. */
  .settings-page .set-choices--level {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .settings-page .set-choices--level > :last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }

  .settings-page .set-actions--pair {
    flex-direction: column;
    align-items: stretch;
  }

  .settings-page .set-actions--pair .set-btn {
    width: 100%;
  }
}

/* Below ~360px two sound presets stop fitting side by side; one column
   is correct there, and nothing may scroll sideways to avoid it. */
@media (max-width: 360px) {
  .settings-page .set-choices--sound {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Safe compact fallback for the tightest phones: the diagram grid keeps
     its 3 (or 2) columns — no reflow to one per row — but the option
     labels drop a step so words like "Vingerzetting" sit on one line
     instead of breaking mid-word. */
  .settings-page .set-diagram-choice {
    padding-inline: 0.3rem;
    font-size: 0.72rem;
    letter-spacing: -0.01em;
  }
}

@media (max-width: 768px) {
  /* See the header of this block: `clip` clips exactly as `hidden` does
     but does not make <body> a scroll container, which is what keeps the
     sticky tab row below actually sticky. */
  body.settings-page {
    overflow-x: clip;
  }

  .settings-page .set-tabs {
    position: sticky;
    top: 56px; /* .nav-mobile-topbar height */
    z-index: 120; /* below the topbar's 250 and the drawer's 149 */
    margin-inline: calc(var(--page-gutter) * -1);
    /* The top padding only gives the stuck row air under the topbar; at rest the tabs sit where every tab row sits. */
    margin-top: -0.7rem;
    margin-bottom: 1.6rem;
    padding-block: 0.7rem 1px; /* the last pixel holds the rail */
    padding-inline: var(--page-gutter);
    /* The sticky background runs edge to edge; the rail stays inside the gutter like every other tab row. */
    background: linear-gradient(rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.06)) var(--page-gutter) 100% / calc(100% - 2 * var(--page-gutter)) 1px no-repeat, var(--bg-main);
    border-bottom: 0;
  }
}

/* ONE LEFT LINE (Jappe, 13 October 2026): the plectrum, the five pillar labels and the account
   avatar start on the same axis as the labels. The wordmark artwork carries 17/370 of transparent
   glow on its left (8.5px at its 185px width), the 24px avatar sits 2px inside its 28px column;
   the logo and the account row sit 8px further out than the nav entries. */
@media (min-width: 769px) {
  html:not(.sidebar-collapsed) .platform-logo { padding-left: calc(1.1rem - 0.5px); }
  html:not(.sidebar-collapsed) .account-trigger { padding-left: calc(1.1rem - 2px); }
}
@media (min-width: 769px) and (max-width: 1280px) {
  html:not(.sidebar-collapsed) .platform-logo { padding-left: calc(0.625rem - 0.5px); }
  /* The tapered range narrows the avatar column to 26px: 1px inside. */
  html:not(.sidebar-collapsed) .account-trigger { padding-left: calc(0.625rem - 1px); }
}

/* Scrollable tab rows fade at the edge where more tabs wait (portal.js PortalRows, 13 October 2026). */
:is(.fb-tabs)[data-more-end="1"] { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent); }
:is(.fb-tabs)[data-more-start="1"] { -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 2rem), transparent); mask-image: linear-gradient(to left, #000 calc(100% - 2rem), transparent); }
:is(.fb-tabs)[data-more-start="1"][data-more-end="1"] { -webkit-mask-image: linear-gradient(to right, transparent, #000 2rem, #000 calc(100% - 2rem), transparent); mask-image: linear-gradient(to right, transparent, #000 2rem, #000 calc(100% - 2rem), transparent); }
