/* ==========================================================================
   PANEL TABS — in-panel menu bar canonical (2026-05-25, app-wide 2026-05-27)
   ==========================================================================

   In-panel tab bar (desktop) plus a chevron dropdown (mobile) and the
   fixed toolbar slot (per design.md "Menu bar layout consistency"). The
   bar sits at the top of every tool's main panel; tabs list every tool
   in the active category; the toolbar slot holds whichever per-tool
   controls (dropdowns / pill groups / icon buttons) the tool defines.

   Scope: APP-WIDE on every category tool page (cat-learn / cat-practice
   / cat-play / cat-tools — 15 pages). Originally shipped on CoF only;
   generalised to every category tool page when the FOUC fix moved the
   stylesheet to a static <link> on each page.

   ──────────────────────────────────────────────────────────────────────
   TO ROLL BACK — one git revert handles the original shipment, or do
   it manually:

     1. DELETE css/panel-tabs.css + js/core/panel-tabs.js
     2. REMOVE the static <link> + <script> for these on all 15 tool
        pages under pages/
     3. REMOVE the _injectPanelTabs fallback block in js/core/nav.js
   ────────────────────────────────────────────────────────────────────── */


/* ---- Outer chrome: title-row + subtitle stay above the panel.
   Only the global nav's chevron lip is hidden — the in-panel tab
   bar replaces sub-nav navigation. */
.nav-tools-toggle {
  display: none !important;
}

/* Zero the h1's user-agent margins AND force its line-box to equal
   its font-size so the .page-title-row wrapper collapses to exactly
   the rendered text height — no invisible leading or browser-default
   margin lurking inside the title-row box.

   Why so defensive (high-specificity body main.tool-page prefix +
   line-height: 1 + min-height: 0): an earlier `.page-title-row h1 {
   margin: 0 }` looked like it should have won (0,1,1 same as
   `.tool-page h1`, declared later in source) but a getBoundingClientRect
   measurement showed title-row was 40px tall while h1 itself was only
   21px — ~19px of unexplained space below the h1 inside the title-row.
   The bumped-specificity rule (0,2,3) plus the line-height/min-height
   defaults collapses the title-row to the tight text box conclusively.
   Mirrors the design.md "Title `min-height` MUST equal `line-height`"
   canonical applied at the page-title level instead of the panel
   `.cof-tonic-title` level. */
body main.tool-page .page-title-row h1 {
  margin: 0;
  padding: 0;
  line-height: 1;
  min-height: 0;
}
.page-title-row {
  min-height: 0;
}

/* Outer chrome margins — three independent gaps:
   - .tool-page padding-top: 1.5rem (base) → 0
     Without this override the 1.5rem padding adds INVISIBLY to the
     gap above the title, making it impossible to equalise gap above
     title with gap below subtitle by only touching the title margin.
     Zeroing it makes .page-title-row margin-top the SOLE contributor
     above the title.
   - title margin-top: 0.5rem (base) → 1.5rem
     Gap above the title (nav → title).
   - title→subtitle gap (.page-title-row margin-bottom): 0.4rem → 0.1rem
     Gap between title and subtitle — kept tight, deliberately small.
   - subtitle margin-bottom: 1rem → 1.5rem
     Gap below the subtitle (subtitle → panel). Same as title margin-top
     so the title+subtitle block has equal breathing room above and below.
   Net visible: 1.5rem above title, 0.1rem between title and subtitle,
   1.5rem below subtitle. The outer pair (above-title = below-subtitle)
   is equal; the inner gap is intentionally tight.
   All three are FLEX-ITEM margins inside .tool-page (display: flex)
   so none of them collapse with each other or with #cof-layout's
   margin: 0 auto (margin-top: 0). Value set = value rendered. */
.tool-page { padding-top: 0; }
body:not(.dashboard) .page-title-row {
  margin-top: 1.5rem;
  margin-bottom: 0.1rem;
}
.tool-page .page-subtitle { margin-bottom: 1.5rem; }


/* #cof-left keeps ASYMMETRIC padding (pL = pR + 21) so the grid's
   `justify-items: center` lands the wheel + title + notes + toggles at
   the VISUAL centre (between the accent stripe's right edge and the
   centre divider) rather than the box's geometric centre. The 21px =
   14px accent-stripe width + 7px to compensate for cof-left shrinking
   when the inner reserves the 14px scrollbar gutter. User 2026-05-28
   ninth pass: live getBoundingClientRect showed wheel.centre=283.75 but
   title.centre=290.75 without the extra left padding. */
#cof-left {
  padding: 24px 24px 24px 45px !important;
}
/* Vertical centring is handled by the grid 1fr diagram row (see
   circle-of-fifths.css #cof-left) — no spacer pseudo needed; hide it so
   it doesn't become a stray grid item. */
#cof-left::after {
  display: none;
}


/* No CSS pre-overrides on the panel — panel-tabs.js dynamically reads
   each panel's original display + flex props via getComputedStyle and
   transfers them to .panel-tabs-inner (so the original layout is
   preserved), then forces the panel itself to flex-column + padding:0
   via inline setProperty !important.

   Any CSS override here would TAINT the getComputedStyle reading
   (panel-layout.css's #cof-layout column override was the bug
   that made CoF side panels stack vertically — getComputedStyle
   returned 'column' instead of the original 'row'). */


/* ---- Wrapper: pure content-driven (min-height: 540 floor +
   height: auto) — same as original #cof-layout. NO position
   tricks, NO max-height. The no-grow-on-accordion-expand
   constraint is achieved by JS: panel-tabs.js measures the
   wrapper's initial rendered height and locks it via inline
   style.height after first paint. That way the "perfect"
   natural height is preserved and subsequent accordion
   expansions can't push it further. ---- */
.panel-tabs-inner {
  display: flex;
  flex-direction: column;
  min-height: 540px;
  height: auto;
  width: 100%;
  overflow: hidden;
}
/* Row-flex inner: CoF, Library, Scales, Modes — wheel/diagram-left +
   side-panel-right side-by-side at desktop, stacked vertically at
   ≤1100. Tagged by panel-tabs.js based on TOOL_CONFIG.split. Direction
   is CSS-driven (not inline) so it tracks viewport changes correctly
   — the prior inline-at-init approach got stuck at whatever value the
   media query resolved to on first paint. */
.panel-tabs-inner--row {
  flex-direction: row;
}
/* Stack split panels at the SAME breakpoint as menu-2 going flex-column
   (≤1200) per user 2026-05-30 "miss oplossen door wanneer menu
   responsive wordt, content ook responsive te maken (dus zodat side
   panel verdwijnt en komt er onder te staan)". Was 1100, bumped to
   1200 so menu + content responsive states are aligned. */
@media (max-width: 1200px) {
  .panel-tabs-inner--row {
    flex-direction: column;
  }
}

/* Ear-training / sight-reading: the quiz-theme rule pins
   `.et-exercise-box { height: 460px; max-height: 460px }` —
   designed when the box was the full panel (no bar). With
   panel-tabs adding a ~52px bar INSIDE the box and the inner
   wrapper carrying min-height: 540 (rule above), bar + inner =
   ~592 wants to render inside a 540-pinned box. `overflow: hidden`
   on the box then clips the bottom rows — the skip/next buttons
   (.et-bottom-actions, 44px tall) sit entirely outside the box's
   visible area and disappear. Per engineering.md "chrome height
   must ADD to panel total height — don't make original content
   area absorb it", let the box grow. min-height: 540 from
   panel-layout.css still acts as the floor for short layouts. */
body.quiz-theme .et-exercise-box {
  height: auto !important;
  max-height: none !important;
}

/* Force overflow-y: scroll on the side panel so its accordion
   content scrolls internally once the wrapper height is locked
   (and so macOS always shows the scrollbar, not autohide it). */
.panel-tabs-inner #cof-explainer {
  overflow-y: scroll;
}


/* ---- The new tab bar (button-styled tabs left, toolbar right).
   Uniform 8px applied EVERYWHERE: bar padding all four sides = 8px,
   gap between nav and toolbar = 8px (via justify-content + gap),
   gap inside nav between tab buttons = 8px, gap inside toolbar
   between dropdowns/dice = 8px (canonical .cof-controls gap).
   Result: 8px left of first tab, 8px between every pair of tabs,
   8px right of dice — the "margin" the user asked for keeps
   coming back identically. ---- */
/* ---- Shell bar: TOP row inside the panel (the app-wide chrome).
   Built by panel-tabs.js buildShellBar(). Holds logo + category
   nav (Hub/Learn/Practice/Play/Tools/Courses/Shop) + user-menu
   (Sign In / avatar dropdown) + fullscreen button. The category
   nav and user-menu are MOVED from the original #tool-nav into
   the slots after the bar is mounted, so existing nav.js /
   auth-ui.js logic still works unchanged. User 2026-05-27:
   "ALLES MOET IN DE PANEL." This is the new app-shell layout
   prep for desktop dmg/exe and mobile wrappers. ---- */
.panel-shell-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 6px 7px 12px;
  /* HEIGHT 44 (not 43) so the diff with the 30px logo / avatar /
     dropdowns = 14 = even = 7/7 symmetric centering. Was 43 (diff
     13 = odd → browser sub-pixel rounded to 7/6 asymmetric), which
     the user saw as "favicon in menu 1 lijkt niet meer dead vertical
     center te staan" 2026-05-30. With padding 7/7 = content area
     30, the logo exactly fills the content box — no centering math
     needed at all. tabs-bar bumped to 44 too so both bars match.
     +2px total page height (was 86 stacked, now 88) is acceptable.
     User 2026-05-28: "hij flasht eerst klein en dan wordt ie
     groter." height: lock prevents the reflow. */
  height: 44px;
  box-sizing: border-box;
  /* No border-bottom — bg colour change between shell bar (lichte
     panel-bg) and panel-tabs-bar (donker accent) IS the boundary.
     Adding a stroke makes menu 2 contents appear off-centre. */
  border-bottom: none;
  flex-shrink: 0;
  position: relative;
}
.panel-shell-logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
}
.panel-shell-logo img {
  /* 30×30 — matches the .nav-cat tab pill height in this row
     (also reduced to 30px). Source favicon-192.webp downscales
     crisply on retina. */
  height: 30px;
  width: 30px;
  display: block;
  border-radius: 6px;
}
.panel-shell-categories {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  justify-content: center;
}
/* Category tabs (.nav-cat) moved from #tool-nav into the shell bar —
   restyle them to match the panel-tabs bar visual rhythm (35px tall
   pill, 0.78rem uppercase). Without this they'd carry their original
   #tool-nav appearance (different padding, different bg). */
.panel-shell-categories .nav-cat {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  padding: 0 12px;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-subtle);
  text-decoration: none;
  border-radius: 8px;
  transition: color 0.15s;
  white-space: nowrap;
}
.panel-shell-categories .nav-cat:hover { color: var(--text-strong); }
.panel-shell-categories .nav-cat.active { color: var(--text-strong); }
.panel-shell-user {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  /* Hide the user widget until auth-ui.js has resolved the auth state
     and rendered the correct widget (Sign In OR user-menu). Without
     this guard, the page first renders the "Sign In" fallback (the
     onAuthChange callback fires synchronously with `null` at init,
     before supabase resolves the stored session) — then ~50-200ms
     later, when supabase resolves to a real user, it gets swapped
     for the user-menu. That swap is the "flash" the user reported
     2026-05-30 "als ik ingelogd ben dan flasht menu 1 en lijken
     margins te verspringen ... margins van menu1 moeten niet
     veranderen wanneer ingelogd." Hiding until ready means the user
     sees a brief empty slot, then the final widget — no swap-flash.

     `auth-ui.js` adds `body.auth-ready` once the first updateAuthUI
     call runs with a non-null user (or after the session has been
     queried). visibility: hidden (not display: none) keeps the slot
     in the layout so categories don't shift while auth resolves. */
}
body:not(.auth-ready) .panel-shell-user {
  visibility: hidden;
}
/* The avatar IMG is injected by auth-ui.js loadUserAvatar() via
   innerHTML with inline width/height 100%. Belt-and-braces CSS rule
   so the IMG is constrained to the parent 30×30 even if the inline
   style hasn't been applied yet (defends against natural-size flash
   on first render of a freshly fetched avatar URL). */
.panel-shell-user .user-avatar img {
  width: 100% !important;
  height: 100% !important;
  border-radius: 50%;
  object-fit: cover;
  display: block;
}
/* Cap the user-menu trigger at 30px so the shell-bar height stays at
   44px regardless of whether the user is signed in (avatar + name) or
   signed out ("Sign In" text). Without this, .user-menu-trigger's
   default padding (4 + 4) + 28px avatar + 1.5px border = 39px height,
   which exceeds the bar's 30px content area and grows the bar to
   ~52px. User 2026-05-27: "menu height is nog echt wrong!! moet
   exact zelfde als het menu eronder qua hoogte." Both states now fit
   in 30px. */
/* `.user-menu` wraps the inline-flex trigger as a `display: block`
   container by default. Browsers treat inline-flex children of a
   block as inline-level content — they get a line box with baseline
   alignment, and the line box height grows when a child has a
   replaced element (the avatar IMG) whose default `vertical-align:
   baseline` pulls the line-box's bottom past the trigger's bottom.
   Concretely: with "R" text inside .user-avatar the line box = 30,
   but once auth-ui.js swaps the avatar to <img>, the IMG sits ON
   the baseline and the line box grows ~4px to accommodate the
   "descender" zone below — making .user-menu 34 tall instead of
   30. That shifts the bar's grid contents into a taller row, and
   the centered favicon in the OTHER grid column drops by 2px.
   User 2026-05-30: "wanneer ik inlog duurt het heel ff voordat
   profile pic is gerendert. en dan verspringt hij naar beneden en
   staat niks dus nog vertical center (favicon ook niet)." Force
   `.user-menu` to flex layout so its height = its (inline-flex)
   trigger's 30px content height, independent of inline baseline
   metrics. */
.panel-shell-user .user-menu {
  display: flex !important;
  align-items: center !important;
  height: 30px !important;
  line-height: 1 !important;
}
.panel-shell-user .user-menu-trigger {
  padding: 0 8px 0 0 !important;
  /* No border — the transparent layout-reservation border ate into the
     content area when bumped to 2px (border-box), pushing the 28px
     avatar off-centre. User 2026-05-28 fourth pass: "margin top/bottom
     is off for the profile pic in menu1." */
  border: none !important;
  height: 30px !important;
  box-sizing: border-box !important;
  text-decoration: none !important;
  display: inline-flex !important;
  align-items: center !important;
  gap: 8px !important;
  color: var(--text-default) !important;
  font-size: 0.85rem !important;
  font-weight: 600 !important;
  font-family: inherit !important;
}
.panel-shell-user .user-menu-trigger:hover {
  color: var(--text-strong) !important;
}
.panel-shell-user .user-avatar {
  /* 30×30 to match the favicon (.panel-shell-logo img) in the same
     bar. */
  width: 30px !important;
  height: 30px !important;
  /* Initial sized to match the MENU-1 title (.panel-shell-categories
     .nav-cat on desktop / .panel-shell-hamburger on responsive), NOT
     the panel/CoF title. Both menu-1 title elements are 0.78rem at
     ALL viewports (verified live via getComputedStyle = 12.48px at
     1920/1440/1200/768/390 — the 2026-06-01 "titles match dropdown
     items" rule normalised them), so the avatar is 0.78rem everywhere.
     User 2026-06-01: "maak hem even groot als menu1 title ipv cof
     title. Is nu te groot." */
  font-size: 0.78rem !important;
  flex-shrink: 0 !important;
  /* overflow: hidden so the IMG inside (set via auth-ui.js's async
     `loadUserAvatar` innerHTML swap) is clipped to 30×30 even
     before its inline `style="width:100%;height:100%"` applies —
     prevents the "flash" of natural-size img during the swap. User
     2026-05-28: "items in menu1 flashen en daarna staan ze naar
     onder." */
  overflow: hidden !important;
}
.panel-shell-user .user-menu-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 140px;
}
.panel-shell-user .login-prompt-btn {
  padding: 0 8px !important;
  height: 30px !important;
}
.panel-shell-bar .panel-tabs-fs-btn {
  margin-left: 6px;
  flex-shrink: 0;
  /* !important needed to beat unified-buttons.css `.lib-dice-btn {
     width: 35 !important; height: 35 !important }` which has same
     specificity (0,1,0) — my class+class selector is (0,2,0) but
     can't override !important without its own !important. */
  width: 30px !important;
  height: 30px !important;
}

/* Hamburger button — hidden on desktop (categories shown inline);
   shown on mobile (categories collapse to a dropdown). Same 35×35
   icon-button geometry as .lib-dice-btn but with a thinner border
   to read as a "tool" icon, not a primary action. */
/* Category dropdown toggle in the shell bar (mobile only). Mirrors
   the .panel-tabs-switcher style in menu 2: text + chevron, click
   toggles the .panel-shell-categories dropdown. Was a hamburger
   icon — replaced with "[Current Category] ⌄" per user 2026-05-28
   later pass: "doe zelfde als 'CIRCLE OF FIFTHS + CHEVRON'. ipv
   hamburger menu. dus laat bijv 'learn' zien met chevron." */
.panel-shell-hamburger {
  display: none;
  height: 30px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--bg-control);
  border: 2px solid var(--border-default);
  border-radius: 10px;
  color: var(--text-subtle);
  cursor: pointer;
  flex-shrink: 0;
  padding: 0 12px;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  transition: color 0.15s, border-color 0.15s;
}
.panel-shell-hamburger:hover {
  color: var(--text-strong);
  border-color: var(--border-hover);
}
.panel-shell-hamburger-chevron {
  width: 12px;
  height: 8px;
  display: block;
  transition: transform 0.2s;
  flex-shrink: 0;
}
.panel-shell-hamburger[aria-expanded="true"] .panel-shell-hamburger-chevron {
  transform: rotate(180deg);
}

/* When the shell bar is present (panel-tabs.js sets body.app-shell
   on every tool that has TOOL_CONFIG entry), hide the OLD outer
   chrome — site-header (mobile logo), #tool-nav (where nav.js
   originally injected), page-title-row, page-subtitle. Their
   relevant content has been moved into the shell bar. */
body.app-shell .site-header,
body.app-shell #tool-nav,
body.app-shell .page-title-row,
body.app-shell .page-subtitle {
  display: none !important;
}

/* FLASH-PREVENTION: hide every tool panel until panel-tabs.js has
   finished wrapping its content (shell-bar + tabs-bar + inner) and
   added `body.shell-ready`. Without this, the user sees the panel
   render briefly with original content at the top, then everything
   jumps DOWN by 86px (shell 43 + tabs-bar 43) when JS prepends the
   bars. Visibility:hidden reserves the panel's layout space (so the
   page doesn't pop on reveal) but hides content during the wrap.
   User 2026-05-28: "de menu1 bar flasht nog bij refresh!! alles
   springt naar beneden." */
body.app-shell:not(.shell-ready) #cof-layout,
body.app-shell:not(.shell-ready) .cl-layout,
body.app-shell:not(.shell-ready) .ft-layout,
body.app-shell:not(.shell-ready) .et-exercise-box,
body.app-shell:not(.shell-ready) .bt-mixer,
body.app-shell:not(.shell-ready) .lp-tracks,
body.app-shell:not(.shell-ready) .tool-panel-exp,
body.app-shell:not(.shell-ready) .find-panel,
body.app-shell:not(.shell-ready) .dashboard-panel {
  visibility: hidden !important;
}

/* Universal panel-height lock — every tool's panel-tabs-inner is
   sized to EXACTLY 540px on desktop (= the original CoF canonical
   from panel-layout.css min-height; "elke tool moet diezelfde
   hoogte hebben als wat cof eerst had" — user 2026-05-27).
   Total panel = 44 shell + 44 bar + 540 inner = ~628px.

   Tools whose natural content is taller (Fretboard, Backing Tracks,
   Glossary, Jazz) scroll inside their inner via overflow-y: auto +
   the themed scrollbar block below. Tools shorter than 540
   (Library, ET/SR, Tuner, etc) render with empty space below
   content but a visually-consistent panel size.

   In immersive mode the inner becomes flex-fill so the panel
   stretches to whatever viewport-based size the panel itself has;
   scrollbar appears only when content exceeds that available space.

   Mobile (≤600px) drops the lock — natural page scroll. */
@media (min-width: 601px) {
  body.app-shell .panel-tabs-inner {
    /* 620 → 660 per user 2026-05-28: "vgm moet de gehele panel
       height nog iets meer voor die circle of fifths spacing
       correct." Gives CoF wheel + title block + subtitle 40px
       extra breathing room. */
    height: 660px !important;
    /* User 2026-05-28: "maak de scroll bar altijd visible!" — scroll
       (not auto) so the gutter is reserved + bar visible whenever
       content has scrollable region. */
    overflow-y: scroll !important;
    max-height: none !important;
  }
  /* PLAY tools (Looper + Backing Tracks) opt out of the universal
     660px height lock — their content varies hugely (Looper from
     ~500px with default setup to many-tracks+rhythm; BT mixer from
     6-row drums up to many channels) and a fixed 660 leaves a big
     accent-painted blank below content OR forces internal scrolling
     when the page would scroll more naturally. User 2026-05-28 later
     pass: "in looper de accent bar loopt te ver door ... panel
     height in 'PLAY' lijkt niet te kloppen. en panel staat niet
     verticaal gecentreerd op de pagina." Letting the inner size
     to content + relaxing the panel's min-height to 0 means the
     panel matches its actual content + bars + border. Accent
     stripe (top: 86 → bottom: 0) naturally ends at content bottom
     instead of stretching past it. */
  /* PLAY tools (Looper + Backing Tracks) used to opt out of the
     660px lock with `height: auto`, but the user 2026-05-28 later
     pass wants every tool at the SAME height as CoF: "ELKE TOOL
     moet exact zelfde hoogte als cof. looper bijv is nog
     verkeerd!!!!!" Cat-play now inherits the universal 660px lock
     above; content scrolls inside the inner like every other tool. */
}

/* Earlier we relaxed the 660px lock at ≤1100 so panels could grow
   with content — but user 2026-05-28 later pass reverted that ask:
   "ELKE TOOL moet exact zelfde hoogte als cof. looper bijv is nog
   verkeerd!!!!!" Inner now stays at 660 at every viewport so all
   tools render at the same rectangle. The page still scrolls
   naturally when the panel (bars + 660 + border) exceeds the
   viewport height (body has no overflow-y restriction). */

@media (min-width: 601px) {
  /* Direct children of the inner must NOT shrink. Without this, a
     flex-column inner with `overflow-y: auto` will compress its
     `flex: 0 1 auto` children to fit the locked height — no
     overflow detected, no scrollbar shown, content visually
     clipped. User 2026-05-28: "fretboard tool heeft ECHT GEEN
     SCROLLBAR!!!!!" Diagnosed: Fretboard's .ft-panel was at 150px
     visible despite scrollHeight 538 (squished). With shrink: 0
     children keep their natural height, inner overflows when
     total exceeds 620, scrollbar appears. */
  body.app-shell .panel-tabs-inner > * {
    flex-shrink: 0 !important;
  }
  body.app-shell.immersive .panel-tabs-inner {
    height: auto !important;
    flex: 1 1 auto !important;
  }
}

/* At responsive widths (≤1200) panels grow with their content and the
   PAGE scrolls naturally. This is the only way the side-panel (e.g.
   CoF explainer) is visible BELOW the wheel without a tiny squashed
   internal scroll. User 2026-05-29 after second iteration: "circle of
   fifths is heel klein. de notes/chords etc toggles staan DOOR de
   circle of fifths heen. en de side panel is volledig gone. die
   hoort bij responsive ONDER de panel met de cof te staan." Inner's
   own 660 height-lock from @media (min-width: 601) gets undone here
   so inner is content-driven. */
@media (max-width: 1200px) {
  body.app-shell:not(.immersive) .panel-tabs-inner {
    height: auto !important;
    min-height: 0 !important;
    overflow-y: visible !important;
  }
  /* No custom themed scrollbar at responsive — the inner doesn't
     scroll any more (page does), and any nested scrollable should
     use the browser's native scrollbar. User 2026-05-30: "geen
     scrollbar en geen accent bar hebben. alleen browser native
     scroll bar, maar niet de scrollbar die wij hadden ontworpen.
     die moet alleen bij desktop blijven." */
  body.app-shell .panel-tabs-inner::-webkit-scrollbar,
  body.app-shell .panel-tabs-inner::-webkit-scrollbar-track,
  body.app-shell .panel-tabs-inner::-webkit-scrollbar-thumb,
  body.app-shell .lib-glossary-grid::-webkit-scrollbar,
  body.app-shell .jz-cards::-webkit-scrollbar,
  body.app-shell #cof-explainer::-webkit-scrollbar {
    display: none !important;
    width: 0 !important;
    background: transparent !important;
  }
  /* TABLET + MOBILE: pin the panel (menu 1) to the TOP of the page.
     The base rule `body.app-shell:not(.immersive) .tool-page {
     justify-content: center }` is the DESKTOP floating-card centring.
     At ≤1200 the panel is full-bleed (screen-wide, no chrome), so a
     SHORT panel (looper / tuner / metronome / dashboard subpages with
     little content) would float to the vertical centre of the viewport,
     leaving a visible margin ABOVE menu 1 — page- and viewport-
     dependent (only when panel height < viewport height). flex-start
     moves the WHOLE panel (menu 1 + menu 2 + content, one flex child)
     up as a unit, so no internal gap appears. Desktop (≥1201) keeps
     `center` per user 2026-05-31 "je moest alleen edits maken in
     tablet en mobile view ... niet desktop". This lived in the
     @media (max-width: 600px) block (its own comment said "Tablet +
     mobile" but the boundary was mobile-only) — moved here so tablet
     (601-1200) is covered too. User 2026-06-01: "op sommige devices
     zoals tablet staat er een margin BOVEN menu1 ... die margin moet
     weg. menu moet top aligned." */
  body.app-shell:not(.immersive) .tool-page {
    justify-content: flex-start !important;
  }
}

/* Mobile (≤600) — outer panel becomes "invisible" so content runs
   100% screen-wide. No border, no bg, no radius. The page itself is
   the panel. User 2026-05-30: "op mobile moet je de panel niet meer
   kunnen zien. moet gewoon 100% width zijn. de outer panel mag weg,
   alles moet screen wide zeg maar. en mooi gecentreerd." */
@media (max-width: 600px) {
  /* Menu 1 (LEARN) + Menu 2 (FRETBOARD TOOL) titles → match the
     panel-content title size (1.2rem = matches .ft-panel-title,
     .cl-section-title, .ft-title). User 2026-05-31: "op mobile de
     font size van titles (eg SCALE information en C MAJOR in
     fretboard tool) is qua font size goed. maar!!!! pas die zelfde
     font size ook toe op menu 1 en menu 2 title (eg LEARN en
     FRETBOARD TOOL)." Override beats the @1200 `font-size: 1rem`
     rule via source order (this block loads later) for hamburger,
     and beats the base `font-size: 1rem` for switcher via specificity
     (added `body.app-shell` ancestor). */
  body.app-shell .panel-shell-hamburger,
  body.app-shell .panel-tabs-switcher {
    font-size: 1.2rem !important;
  }
  /* Hide the fullscreen button on mobile. The dice / leftmost icon
     takes its visual slot via natural flex-end, and the leftmost
     dropdown / search input flex-grows into the freed space. User
     2026-05-30: "mobile hoeft helemaal geen fullscreen button. zodra
     het menu naar responsive scaled mag de fullscreen button weg.
     de dice mag op de plek staan van waar nu de full screen icon
     staat. en de witruimte, vul die op door de dropdown/search bar
     of wat dan ook van de meest linker ICON breder te maken." */
  /* Match the higher-specificity rule at ~L1474 (.panel-tabs-toolbar >
     .panel-tabs-fs-btn { margin-left ... }) — without this longer
     selector our previous `body.app-shell .panel-tabs-fs-btn` was
     (0,2,1) and lost to the (0,3,1) toolbar-child rule even with
     !important. NOTE moved here from @media (max-width: 600) on
     2026-05-31: user said "zodra het menu naar responsive scaled mag
     de fullscreen button weg" — that's @1200 (the responsive
     breakpoint), not @600 (mobile). At @600 my earlier rule still
     fired but missed tablet portrait + zoomed-in desktop viewports
     in the 601-1200 band where the menu IS already responsive but
     the FS button stayed visible. */
  body.app-shell:not(.immersive) #cof-layout,
  body.app-shell:not(.immersive) .cl-layout,
  body.app-shell:not(.immersive) .ft-layout,
  body.app-shell:not(.immersive) .find-panel,
  body.app-shell:not(.immersive) .et-exercise-box,
  body.app-shell:not(.immersive) .bt-mixer,
  body.app-shell:not(.immersive) .lp-tracks,
  body.app-shell:not(.immersive) .tool-panel-exp,
  body.app-shell:not(.immersive) .lib-glossary,
  body.app-shell:not(.immersive) .jz-browse {
    background: transparent !important;
    border: none !important;
    border-radius: 0 !important;
    margin: 0 !important;
    max-width: none !important;
    width: 100% !important;
  }
  body.app-shell:not(.immersive) .tool-page {
    padding: 0 !important;
    max-width: none !important;
    /* Top-align (justify-content: flex-start) is set in the
       @media (max-width: 1200px) block above so it covers TABLET too
       (601-1200), not just mobile — see the comment there. */
  }
}

/* Themed scrollbar on every panel-tabs-inner. ALWAYS-VISIBLE design.
   User 2026-05-27: "ik zie geens crollbar bij fretboard tool!!!
   geen always visible scrollbar te zien hoor."

   IMPORTANT: do NOT set `scrollbar-width: thin` or `scrollbar-color`
   on the container. Chrome 121+ implements those standard properties
   and (per spec) DISABLES the legacy `::-webkit-scrollbar`
   pseudo-elements when EITHER is present. That's why my earlier
   thumb-color override was silently ignored — the new properties
   were winning and rendering an OS-default (auto-hiding on macOS)
   thin scrollbar. Removing both lets `::-webkit-scrollbar` styling
   take effect again, with a clearly-visible always-on bar.
   Engineering.md lesson added in same commit. */
/* Oldskool OS scrollbar — square corners, dark track, lichte thumb.
   Track uses the donkere accent token (matching the panel-tabs bar
   bg and dividers); thumb sits one tone lighter for visibility.
   User 2026-05-28: "scrollbar moet de zelfde kleur als de menu bar
   ... die stroke kleur. en scroll bar hoeft geen afgeronde hoeken.
   mag beetje oldskool OS vibes." */
body.app-shell .panel-tabs-inner::-webkit-scrollbar {
  width: 14px;
  background: var(--border-default);
}
body.app-shell .panel-tabs-inner::-webkit-scrollbar-track {
  background: var(--border-default);
  border-radius: 0;
}
body.app-shell .panel-tabs-inner::-webkit-scrollbar-thumb {
  background: var(--border-hover);
  border-radius: 0;
  min-height: 30px;
  /* No border on the thumb — the visible thumb sits as a clean solid
     bar inside the track. User 2026-05-28 sixth pass: "haal de stroke
     van de scroll bar rechts weg want dat covert de panel stroke in
     principe al." */
  border: none;
}
/* Hide the scrollbar end-buttons (up/down arrows) so the track
   extends fully from the top of the inner (immediately below menu 2)
   to the bottom of the panel — no vertical inset. User 2026-05-28
   sixth pass: "als ik helemaal omhoog scroll moet de scrollbar exact
   tot onderkant van menu2 lopen. dat is nu niet zo." */
body.app-shell .panel-tabs-inner::-webkit-scrollbar-button {
  display: none;
  width: 0;
  height: 0;
}
body.app-shell .panel-tabs-inner::-webkit-scrollbar-thumb:hover {
  background: #555;
}
/* Apply same scrollbar to glossary grid, jazz cards, cof-explainer
   sub-panels, and any other tool-internal scrollable region. User:
   "ook in glossary etc etc. kijk echt goed welke tools een
   scrollbar gebruiken!!" */
body.app-shell .lib-glossary-grid::-webkit-scrollbar,
body.app-shell .jz-cards::-webkit-scrollbar,
body.app-shell #cof-explainer::-webkit-scrollbar,
body.app-shell .saved-list::-webkit-scrollbar,
body.app-shell .score-grid::-webkit-scrollbar {
  width: 14px;
  background: var(--border-default);
}
body.app-shell .lib-glossary-grid::-webkit-scrollbar-track,
body.app-shell .jz-cards::-webkit-scrollbar-track,
body.app-shell #cof-explainer::-webkit-scrollbar-track,
body.app-shell .saved-list::-webkit-scrollbar-track,
body.app-shell .score-grid::-webkit-scrollbar-track {
  background: var(--border-default);
  border-radius: 0;
}
body.app-shell .lib-glossary-grid::-webkit-scrollbar-thumb,
body.app-shell .jz-cards::-webkit-scrollbar-thumb,
body.app-shell #cof-explainer::-webkit-scrollbar-thumb,
body.app-shell .saved-list::-webkit-scrollbar-thumb,
body.app-shell .score-grid::-webkit-scrollbar-thumb {
  background: var(--border-hover);
  border-radius: 0;
  border: 2px solid var(--border-default);
  min-height: 30px;
}

/* DESKTOP: vertically center the panel inside the viewport per
   the 2026-05-27 "vertical center de gehele panel op midden van
   scherm" preference. Centring is good when content is tall
   enough that no visible gap is left above (CoF, Library, FT etc.
   at desktop). Skipped in immersive mode — panel is position:
   fixed + inset 1cm there, parent flex doesn't apply.
   TABLET + MOBILE override the justify-content to flex-start
   below (in the @1200 block), per user 2026-05-31 third pass:
   "EN DE panel in desktop is gewijzigd. die was perfect vertical
   gecentreerd en die aligned nu met de top. je moest alleen
   edits maken in tablet en mobile view he. niet desktop." */
body.app-shell:not(.immersive) .tool-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}

/* Menu 1 responsive collapse — bumped from ≤600 to ≤1200 per user
   2026-05-29 ("menu 1 zou bij 150% al moeten scalen naar de dropdown
   versie. dat doet ie niet"). Same breakpoint as menu 2's collapse.
   Dropdown style matches menu 2 nav.open: no stroke, no shadow, 6px
   radius, bg = --bg-control. The bar becomes a 2-row grid: row 1 is
   [logo | hamburger | user-menu]; row 2 is the categories dropdown
   when .open, otherwise collapsed (0 height). */
@media (max-width: 1200px) {
  /* Hide the FS button as soon as the menu flips to responsive form.
     User 2026-05-31: "zodra het menu naar responsive scaled mag de
     fullscreen button weg. de dice mag op de plek staan van waar nu
     de full screen icon staat."

     Specificity trap saga: the existing rule at L~1700
     `body.app-shell .panel-tabs-toolbar > .panel-tabs-fs-btn {
     display: inline-flex !important }` has specificity (0,3,1). My
     earlier hide rule at the same (0,3,1) lost to it because both
     were !important AND the inline-flex rule came LATER in source
     order. Bumping selector to (0,4,1) by adding `.panel-tabs-bar`
     so this beats the existing rule regardless of source order. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .panel-tabs-fs-btn,
  body.app-shell .panel-tabs-bar .panel-tabs-fs-btn,
  body.app-shell .panel-tabs-fs-btn {
    display: none !important;
  }
  /* Menu 1 at responsive — height 43px (matches menu 2 toolbar line),
     items vertically centered. Hamburger styled like menu 2 switcher
     (no button bg, just text + chevron). User 2026-05-29 third pass:
     "menu height is ook fout!! is veel te hoog. moet even hoog als
     de toolbar line van menu 2 ... in menu1 staat 'Learn + chevron'
     nu in een button?? dat moet niet. maak exact zelfde font en
     styling als 'circle of fifths + chevron' in menu 2 bij
     responsive!!! en, heel belangrijk. bij responsive is NIKS meer
     vertically centered in menu 1." */
  body.app-shell:not(.immersive) .panel-shell-hamburger {
    /* Re-style as switcher: no button chrome, text-strong colour,
       1.2rem font (matches panel-title canonical .ft-panel-title /
       .cl-section-title / .ft-title). User 2026-05-31 follow-up:
       "op mobile de font size van titles ... is qua font size goed.
       maar pas die zelfde font size ook toe op menu 1 en menu 2
       title ... pas ook toe op de tablet viewport he. you did this
       but it looks a bit off. zijn ze echt exact even groot?" Was
       1rem at this @1200 breakpoint while a separate @600 rule
       bumped to 1.2rem only at mobile — now bumped here so it
       applies at both tablet AND mobile. */
    display: inline-flex !important;
    height: auto !important;
    background: transparent !important;
    border: none !important;
    border-radius: 0 !important;
    padding: 6px 0 !important;
    font-size: 1.2rem !important;
    font-weight: 700 !important;
    letter-spacing: 0.05em !important;
    color: var(--text-strong) !important;
  }
  body.app-shell:not(.immersive) .panel-shell-bar {
    display: grid !important;
    grid-template-columns: 1fr auto 1fr !important;
    /* In the full responsive band (≤1200, where menu 2 is in
       column-flex form with `padding: 14px` + 31px switcher + 14px
       gap = 59 effective), menu 1 bar height = 59 so the vertical
       margins around "LEARN" text match the 14px above + below
       "CIRCLE OF FIFTHS" in menu 2.
       User 2026-05-30: "op mobile klopt dit. zorg dat dit voor
       desktop vanaf 150% viewport ook werkt." 150% zoom on 1920 =
       effective vw 1280, on 1536 = vw 1024. Both fall in this
       @media (max-width: 1200) block.
       Desktop (≥1201) keeps the 43px height via the outer
       .panel-shell-bar rule (line ~214). */
    grid-template-rows: 59px !important;
    height: 59px !important;
    gap: 0 !important;
    padding: 0 14px !important;
    align-items: center;
    box-sizing: border-box;
  }
  body.app-shell:not(.immersive) .panel-shell-bar:has(.panel-shell-categories.open) {
    grid-template-rows: 59px auto !important;
    height: auto !important;
    padding: 0 14px 14px !important;
    /* row-gap: 0 so the dropdown sits at the row-1 bottom edge.
       Switcher is centred in the 59px row 1 with 14px above + 31
       text + 14px below, so the visible gap above the dropdown =
       14px (row-1's bottom padding-equivalent) — matching the
       14px gap ABOVE "LEARN". Symmetric. Bar's `padding-bottom:
       14` keeps the gap BELOW the dropdown at 14, also symmetric
       and matching menu 2. User 2026-05-31: "de margin BOVEN
       LEARN moet ook eronder blijven... je hebt 28px weg gehaald
       maar dat moest 14px zijn." Was 28 (14+14), now 14. */
    row-gap: 0 !important;
  }
  body.app-shell:not(.immersive) .panel-shell-logo {
    grid-column: 1 !important;
    grid-row: 1 !important;
    justify-self: start;
    align-self: center;
  }
  body.app-shell:not(.immersive) .panel-shell-hamburger {
    grid-column: 2 !important;
    grid-row: 1 !important;
    justify-self: center;
    align-self: center;
  }
  body.app-shell:not(.immersive) .panel-shell-user {
    grid-column: 3 !important;
    grid-row: 1 !important;
    justify-self: end;
    align-self: center;
  }
  body.app-shell:not(.immersive) .panel-shell-categories {
    grid-column: 1 / -1 !important;
    grid-row: 2 !important;
    /* bg = --border-default = menu 2 BAR background (donker accent),
       NOT --bg-control. User 2026-05-30 fourth pass: "menu 1 wanneer
       responsive (die dropdown met radius enzo) moet andere kleur
       hebben. zelfde kleur als menu 2 background." */
    display: none !important;
    position: static !important;
    flex-direction: column !important;
    /* Override base `.panel-shell-categories { gap: 8px }` from desktop
       row-flex — at responsive widths the dropdown stacks items
       vertically, and the 8px gap there translates to 8px of vertical
       space BETWEEN rows, which doesn't match menu 2's `gap: 0` flush
       rows. User 2026-05-31: "menu 1 dropdown op mobile zijn de
       verticale marges tussen de menu items in menu 1 nog niet gelijk
       aan menu 2. en op desktop vanaf viewport 150% ook niet." */
    gap: 0 !important;
    width: 100% !important;
    background: var(--border-default) !important;
    border: none !important;
    border-radius: 6px !important;
    box-shadow: none !important;
    padding: 0 !important;
    margin: 0 !important;
    flex: 0 0 auto;
    justify-content: flex-start;
  }
  body.app-shell:not(.immersive) .panel-shell-categories.open {
    display: flex !important;
  }
  body.app-shell:not(.immersive) .panel-shell-categories .nav-cat {
    /* Match menu 2 dropdown items EXACTLY: same padding 14×20, same
       text colour, NO border-bottom (menu 2 items have
       `border: none !important` via the high-specificity
       `body.app-shell .panel-tabs-bar .panel-tab` rule, so they
       render edge-to-edge with no visible divider — the same here).
       User 2026-05-31: "menu 1 dropdown op mobile zijn de verticale
       marges tussen de menu items in menu 1 nog niet gelijk aan
       menu 2. en op desktop vanaf viewport 150% ook niet." Earlier
       I assumed menu 2 had a 2px divider; probe showed bb=0 so the
       divider was menu 2's BAR border-bottom, not item border. */
    width: 100% !important;
    justify-content: flex-start !important;
    padding: 14px 20px !important;
    height: auto !important;
    background: transparent !important;
    border: none !important;
    border-radius: 0 !important;
    color: var(--text-default) !important;
    line-height: 1.2 !important;
  }
  body.app-shell:not(.immersive) .panel-shell-categories .nav-cat:last-child {
    border-bottom: none !important;
  }
  /* Current category SHOWN in dropdown with white text (per user
     "circle of fifths (current) niet staan in de dropdown. dat moet
     wel!!!! en die moet wit. geldt voor menu1 wanneer uitgeklapt
     ook"). Drop the display:none override added earlier. */
  body.app-shell:not(.immersive) .panel-shell-categories .nav-cat.active {
    display: flex !important;
    color: var(--text-strong) !important;
  }
}

.panel-tabs-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 6px 7px 12px;
  /* HEIGHT 44 (was 43) to match panel-shell-bar — see the long
     comment there for the sub-pixel centering fix rationale. Both
     bars need to stay equal-height per user "menu 1 in BT is HOGER
     dan menu1 in eg looper" requirement. */
  height: 44px;
  box-sizing: border-box;
  /* User 2026-05-28: "geef de background kleur van de sub-menu bar
     ... de zelfde bg kleur als de stroke rondom en de dividers."
     Sub-menu bar = donkere accent (border-default). Active tab pops
     up against this bg via .panel-tab.active { background: bg-
     control }. */
  background: var(--border-default);
  border-bottom: 2px solid var(--bg-deeper);
  flex-shrink: 0;
  position: relative;
}

/* ---- Force every <select> / icon-button moved into the toolbar slot
   to render with CoF canonical sizing + styling (.gt-select +
   .lib-dice-btn spec). Without this, tools that use their own select
   classes (.bt-select, .lp-select, .cl-select, .ft-select, .jz-input
   etc.) render at their native size + colour and break the bar's
   uniform appearance. Targeting `.panel-tabs-toolbar select` /
   `button` catches every variant once they're inside the slot. ---- */
.panel-tabs-toolbar select {
  height: 30px !important;
  box-sizing: border-box !important;
  padding: 0 26px 0 10px !important;
  background-color: var(--bg-control) !important;
  border: 2px solid var(--border-default) !important;
  border-radius: 10px !important;
  font-size: 0.8rem !important;
  font-weight: 600 !important;
  color: var(--text-default) !important;
  font-family: inherit !important;
  appearance: none !important;
  -webkit-appearance: none !important;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 4l4 4 4-4' fill='none' stroke='%23888' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 10px center !important;
  background-size: 12px !important;
}
.panel-tabs-toolbar select:hover,
.panel-tabs-toolbar select:focus {
  border-color: var(--border-hover) !important;
  color: var(--text-strong) !important;
}
.panel-tabs-toolbar button {
  height: 30px !important;
  box-sizing: border-box !important;
}
/* Search inputs (glossary + jazz-standards) moved into the toolbar
   slot — render with the same shape as the menu-2 dropdowns / icon
   buttons (donker bg via in-shell override below, no border, 6px
   radius). User 2026-05-29: "kleur van de search bar moet zelfde als
   panel bg kleur. ook in jazz standards. en de divider links van de
   search bar wijkt af ... zorg dat het matcht met cof." Without this,
   the input's own `border: 2px solid --border-default` paints a
   visible rectangle next to the menu-2 divider, reading as a second
   inconsistent divider. Same height (30px) + no border = the search
   reads as one continuous strip with the toolbar dropdowns. */
.panel-tabs-toolbar input[type="text"],
.panel-tabs-toolbar input[type="search"] {
  height: 30px !important;
  box-sizing: border-box !important;
  font-size: 0.8rem !important;
  font-family: inherit !important;
  /* No vertical padding — content fills the 30px box exactly like the
     selects + buttons do, so the search bar height + baseline matches
     the toolbar dropdowns. Per-element rules (.lib-glossary-search /
     #jz-search) still own horizontal padding for the magnifier icon
     leading edge. User 2026-05-29: "de hoogte van de search bar in
     glossary is WRONG. moet matchen met de buttons enzo." */
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}

/* Reset every toolbar variant moved into the bar — original toolbars
   (.cof-controls / .cl-controls / .ft-controls / .find-controls /
   .bt-toolbar-row / .lp-toolbar-row / .jz-search-wrap) each carry
   their own margin / min-height / flex-wrap from their per-tool CSS;
   in the bar slot they all need to render identically. Targeting the
   .panel-tabs-toolbar slot's direct child catches every variant.
   `flex: 1 1 auto` + `width: auto` makes the inner toolbar fill the
   reserved 480px slot so its own children can spread evenly inside. */
.panel-tabs-toolbar > * {
  margin: 0 !important;
  min-height: auto !important;
  flex-wrap: nowrap !important;
  justify-content: flex-end !important;
  /* `flex: 0 0 auto` instead of `1 1 auto` so each toolbar wrapper
     (.cof-controls / .ft-controls / .cl-controls / .find-controls)
     sizes to its INTRINSIC content width — items inside pack tight
     with their canonical 8px gap, with no extra space being injected
     between items by a stretched wrapper. The toolbar slot is wider
     than the wrapper → empty space ends up on the LEFT of the wrapper
     (since toolbar has `justify-content: flex-end` so the wrapper
     itself sits at the right edge against dice + fs). User 2026-05-30:
     "sommige dropdowns hebben heel veel witruimte er tussen. dat moet
     niet. het moet wel allemaal doorlopen met max 8px gap." */
  flex: 0 0 auto !important;
  width: auto !important;
}
/* Search-bar wrappers (glossary + jazz-standards) STAY flex-grow so
   the search input fills the available toolbar slot width. They're a
   different role — single full-width search input, not a row of
   role-keyed dropdowns. */
.panel-tabs-toolbar > .lib-glossary-search-wrap,
.panel-tabs-toolbar > .jz-search-wrap {
  flex: 1 1 auto !important;
}
/* Fullscreen button overrides the > * reset — it's not a toolbar
   wrapper, it's a standalone 35×35 icon button that sits at the
   end of the slot. Without these the > * rule stretches it to fill
   leftover space (e.g. 45-150px wide depending on the tool). */
/* Icon buttons inside the toolbar slot (.lib-dice-btn / dice /
   randomise / bell / info / volume / fs button) — shrunk from
   default 35×35 to 30×30 to match the new 30px tab-pill height.
   `--icon-btn-size` stays 35 globally; we override locally so
   non-bar uses of .lib-dice-btn (none today, but defensive)
   aren't affected. */
.panel-tabs-toolbar .lib-dice-btn,
.panel-tabs-toolbar .lib-dice-btn.lib-dice-btn {
  width: 30px !important;
  height: 30px !important;
}

/* Dropdowns + every form-control wrapper inside the toolbar grow
   to fill remaining space inside the slot; 8px gaps from the inner
   .cof-controls / .cl-controls / .ft-controls / .find-controls
   (already canonical per design.md "Top-toolbar gap — ONE value
   across every tool") stay equal because only the wrappers/inputs
   grow, gaps don't. Pill groups (.cof-pill-group, Notes/Chords
   toggle) and icon buttons (.lib-dice-btn / .met-bell-btn /
   .ph-hint-btn / .ph-audio-btn etc.) keep their content width —
   they're indivisible visual units.

   `[class*="-wrap"]` matches every per-select wrapper variant
   (.gt-select-wrap, .cl-select-wrap, .ft-select-wrap,
   .ls-select-wrap, .bt-select-wrap, .lp-select-wrap,
   .ph-select-wrap, .jz-select-wrap, .find-select-wrap). Direct
   <select> and <input> grow too (covers Library .cl-select being
   a bare select in some panels, Jazz .jz-search input). */
.panel-tabs-toolbar > * > [class*="-wrap"],
.panel-tabs-toolbar > * > select,
.panel-tabs-toolbar > * > input {
  flex: 1 1 auto !important;
  min-width: 0 !important;
  width: auto !important;
}

/* Uniform dropdown widths by ROLE across tools — switching between
   CoF / Fretboard / Library / Analysis keeps the toolbar layout
   visually stable. User 2026-05-29: "DIE EXACTE dropdown met key/root
   selector moet in elke tool exact even breed qua breedte en padding.
   analysis heeft instrument toggle. fretboard tool ook. die moeten
   dus ook exact even breed." The dropdown CONTENT is unchanged — only
   the visible WIDTH is locked. 8px gap between adjacent items is
   preserved by the existing toolbar `gap: 8px`. */
/* Key / root selector — uniform 110px */
body.app-shell .panel-tabs-toolbar #cof-root-select,
body.app-shell .panel-tabs-toolbar #ft-root,
body.app-shell .panel-tabs-toolbar #cl-root,
body.app-shell .panel-tabs-toolbar #find-key-root {
  flex: 0 0 110px !important;
  width: 110px !important;
}
/* Scale / scale-category selector — uniform 210px */
body.app-shell .panel-tabs-toolbar #cof-scale-select,
body.app-shell .panel-tabs-toolbar #ft-category,
body.app-shell .panel-tabs-toolbar #cl-type,
body.app-shell .panel-tabs-toolbar #find-key-scale {
  flex: 0 0 210px !important;
  width: 210px !important;
}
/* Instrument selector — uniform 110px */
body.app-shell .panel-tabs-toolbar #ft-instrument,
body.app-shell .panel-tabs-toolbar #find-instrument {
  flex: 0 0 110px !important;
  width: 110px !important;
}
/* Lib type (Chords/Scales/Modes) + Analysis sub-tool selector +
   Analysis input-mode — these are per-tool unique roles without
   counterparts in other tools, so size to content. */
/* Selects + inputs INSIDE a wrapper fill the wrapper's width so
   the dropdown chevron sits flush against the right edge. */
.panel-tabs-toolbar [class*="-wrap"] select,
.panel-tabs-toolbar [class*="-wrap"] input {
  width: 100% !important;
}


/* ---- Tab nav (desktop): nav itself grows to fill all bar space
   left of the toolbar (flex: 1). Tab buttons inside also each
   grow (flex: 1 1 auto) so they spread out and the LAST tab's
   right edge meets the toolbar's left edge with exactly the bar's
   8px gap between them — no leftover empty space anywhere. ---- */
.panel-tabs-nav {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
  min-width: 0;
}

/* Solid button tab — bg = active .cof-pill colour
   (var(--border-default) = #2a2d3a) at 50% opacity for a
   subtler "ghost button" look. The colour token's literal value
   is hardcoded as rgba here because var() doesn't compose with
   alpha in a single shorthand; color-mix() would also work but
   rgba is the simplest, widest-support form. No border. Height
   locked to 35px (= dice button). Each tab flex-grows
   (flex: 1 1 auto) so the nav's available width is split between
   tabs naturally; justify-content: center centres the tab text
   horizontally within the grown button. */
.panel-tab {
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  padding: 0 10px;
  box-sizing: border-box;
  background: transparent;
  border: none;
  border-radius: 8px;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-subtle);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  font-family: inherit;
  line-height: 1.2;
  transition: color 0.15s, background 0.15s;
}

.panel-tab:hover { color: var(--text-strong); }
/* Active tab stands out against the donkere bar bg via the lichte
   panel surface. User 2026-05-28: "onthoud dat dit de main 2
   kleuren zijn van de toolkit: die lichte en die donkere grijs." */
.panel-tab.active {
  color: var(--text-strong);
  background: var(--bg-control);
}


/* ---- Toolbar: sits right of the tab nav. The bar's 8px gap
   handles Glossary→divider; the divider (::before at left:0,
   width 1.5px) sits at toolbar-left. padding-left: 9.5px
   = divider width (1.5px) + 8px gap, so the "C" dropdown's
   left edge is exactly 8px from the divider's right edge.
   No margin-left (bar gap already gives 8px before the divider).

   Fixed slot width on desktop so the tab nav always claims the
   same horizontal area across all tools in a category — tabs no
   longer reshuffle when the user switches tools (per user 2026-
   05-26: "ik wil eigenlijk dat de toolbar dropdowns en menu items
   NIET verspringen tussen tools (binnen eg learn)"). 480px fits
   the widest case in Learn (Analysis Progression Finder, 4
   dropdowns) without forcing other tools' dropdowns to render too
   wide; selects inside flex-grow to fill the remaining space so
   the 8px gaps between items stay equal. */
.panel-tabs-toolbar {
  /* `flex: 0 1 480px` — basis 480, no grow, allow shrink so the bar
     doesn't overflow at narrower widths (600-1100 tablet range).
     At ≥1100 panel max-width, the toolbar lands at exactly 480. */
  flex: 0 1 480px;
  display: flex;
  align-items: center;
  position: relative;
  margin-left: 0;
  padding-left: 9.5px;
  justify-content: flex-end;
  box-sizing: border-box;
}

/* Per-category basis overrides — categories with bigger toolbars
   need a wider slot than the 480 default. Tools (Tuner + Metronome)
   has Tuner with 2 dropdowns AND 2 slider-with-labels rows; at 480
   the dropdowns truncate visibly ("Guitar (6" / "Standa" per user
   screenshot 2026-05-26 "tuner is niet breed genoeg"). 640 fits all
   four items without truncation; the 8px gaps stay equal because
   only the dropdowns/select-wraps grow inside. */
/* Dashboard pages (settings/profile/saved/progress/subscription) have
   no toolbar — the slot is built but empty. Hide it entirely so the
   panel-tabs-nav can fill the bar and the tabs centre instead of
   left-justifying against an empty 480px reservation. User
   2026-05-27: "geen van de settings pages heeft een toolbar. dus
   het menu (profile, settings, my library etc) mag gecentreerd." */
body.dashboard .panel-tabs-toolbar {
  display: none !important;
}
body.dashboard .panel-tabs-nav {
  justify-content: center !important;
}
body.dashboard .panel-tab {
  /* Override the default `flex: 1 1 auto` which makes tabs grow to
     fill the row — on dashboard we want the tabs at their natural
     content width, centred together as a group. */
  flex: 0 0 auto !important;
  padding: 0 14px !important;
}

/* TOOLS toolbar uses the default 480px slot — same divider position
   as LEARN per user 2026-05-30: "ZORG VOOR ELK DAT DE DIVIDER OP
   EXACT ZELFDE POSITIE STAAT ALS BIJ LEARN OP DESKTOP!!!" The earlier
   632px override for the legacy 4-dropdown TOOLS grid is retired now
   that A4 + sensitivity sliders are hidden from the bar. */

/* Keep the slot reserved when no toolbar is present (Glossary) so
   the nav width — and therefore every tab's pixel position — stays
   identical across the category. Only the divider is hidden when
   there's nothing to the right of it. */
.panel-tabs-toolbar:empty::before {
  display: none;
}

.panel-tabs-toolbar::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 2px;
  height: 20px;
  background: var(--border-default);
}


/* ---- Mobile switcher button — only shown ≤600px ---- */
.panel-tabs-switcher {
  display: none;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: none;
  padding: 6px 0;
  /* 0.78rem matches the menu 2 dropdown items (.panel-tab) AND the
     menu 1 dropdown items (.panel-shell-categories .nav-cat) so the
     title reads at the same size as the entries it switches between.
     User 2026-06-01: "maak menu 1 en menu 2 title even groot als
     menu 1 en 2 dropdown items." */
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-strong);
  cursor: pointer;
  font-family: inherit;
}

.panel-tabs-chevron {
  width: 12px;
  height: 8px;
  transition: transform 0.2s;
  flex-shrink: 0;
}

.panel-tabs-switcher[aria-expanded="true"] .panel-tabs-chevron {
  transform: rotate(180deg);
}


/* ---- Side panel: always-visible scrollbar so Mac auto-hide doesn't
   leave users wondering whether scrolling is possible (per
   engineering.md "If you genuinely need fixed-height + scroll, make
   the scrollbar very visible") ---- */
#cof-explainer {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}

#cof-explainer::-webkit-scrollbar {
  width: 10px;
}

#cof-explainer::-webkit-scrollbar-track {
  background: transparent;
}

#cof-explainer::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.22);
  border-radius: 5px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

#cof-explainer::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.4);
  background-clip: padding-box;
}


/* ---- Compact (≤1200px) — chevron dropdown.
   Earlier breakpoint was 1000px, then 600px; both still left the
   bar overlapping at common 150% browser zoom on 14"/16" MacBook
   Pro screens (effective viewport ~1008-1152). User 2026-05-28
   later pass: "de menu bar is nog niet responsive bij 150%."
   Bumped to 1200px so anything narrower than ~1200px (small
   laptops at 150% zoom, tablets, phones) switches to the
   chevron dropdown layout. Regular external displays (1440+)
   stay on the horizontal layout.
   align-items: center on the bar centers both the title-switcher
   and the toolbar horizontally. Nav is in-flow (not absolute) so
   opening the dropdown pushes the toolbar (and the panel content
   below) down instead of overlaying. ---- */
@media (max-width: 1200px) {
  .panel-tabs-bar {
    flex-direction: column;
    align-items: center;
    /* Gap = 14px (= scrollbar width + accent-stripe width) so the
       margin above + below the open nav-dropdown matches the panel's
       left accent stripe and right scrollbar. User 2026-05-29: "de
       margin BOVEN en ONDER de menu dropdown moet exact even hoog
       als dikte van de scrollbar en panel left accent bars." */
    gap: 14px;
    padding: 14px 16px;
    /* Override desktop's `height: 43px` so the bar grows naturally
       with its column-stacked children (switcher + toolbar +
       open nav). Without this, the bar stays at 43px and its
       overflowing children paint OVER the panel content below
       (e.g. the .ft-title "C MAJOR" disappearing under the bar).
       User 2026-05-28 later pass: "menu bar is responsive bij
       150% maar title valt weg onder menu 2." */
    height: auto;
  }

  /* Dashboard pages (Account category — Settings / Profile / Saved /
     Progress / Subscription) have NO toolbar items (no dropdowns /
     dice), so menu 2 should be the SAME height as menu 1 (59px), not
     taller. The empty .panel-tabs-toolbar is still a flex item in the
     column, so it reserves the 14px column gap → menu 2 rendered at
     69px (10px taller than menu 1). Hide the empty toolbar (its only
     child is the already-hidden FS button) so the gap disappears, and
     bump vertical padding 14→16 so the switcher row = 16+27+16 = 59 =
     menu 1. height stays auto, so the menu-2 nav dropdown still
     expands the bar when opened (gap 14 to the open nav preserved).
     User 2026-06-01: "wanneer er geen toolbar items (dropdowns etc)
     zijn moet menu 2 even dik zijn als menu1 op mobile en tablet."
     NB: specificity (0,4,1) via `body.dashboard.app-shell` is needed
     to beat the @1200 `body.app-shell .panel-tabs-bar
     .panel-tabs-toolbar { display: flex !important }` rule (0,3,1,
     later source ~L3380) that otherwise keeps the empty toolbar a
     flex item — which is why the base `body.dashboard
     .panel-tabs-toolbar { display: none }` (L1230) was being defeated
     at responsive widths. */
  body.dashboard.app-shell .panel-tabs-bar .panel-tabs-toolbar {
    display: none !important;
  }
  body.dashboard.app-shell .panel-tabs-bar {
    padding-top: 16px !important;
    padding-bottom: 16px !important;
  }

  .panel-tabs-switcher {
    display: inline-flex;
    align-self: center;
    /* 1.2rem matches panel-title canonical. User: "pas die zelfde
       font size ook toe op menu 1 en menu 2 title ... pas ook toe op
       de tablet viewport he." */
    font-size: 1.2rem !important;
  }

  .panel-tabs-nav {
    display: none;
    position: static;             /* in-flow → pushes content down */
    flex-direction: column;
    gap: 0;
    align-items: stretch;
    width: 100%;
    /* No stroke, no glow, smaller radius = match the toolbar
       dropdowns (.panel-tabs-toolbar select { border-radius: 6px }
       + bg `--tk-medium-grijs` = `--bg-control`). User 2026-05-29:
       "de dropdown bij responsive moet geen stroke en glow hebben.
       moet zelfde kleur als de dropdowns en panel bg, en zelfde
       radius." */
    background: var(--bg-control);
    border: none;
    border-radius: 6px;
    box-shadow: none;
    /* Reset desktop's flex/gap so the dropdown stacks vertically. */
    flex: 0 0 auto;
    height: auto;
    justify-content: flex-start;
  }

  .panel-tabs-nav.open {
    display: flex;
  }

  /* In the mobile dropdown each tab is a flat list row — strip the
     desktop button chrome (solid bg, radius, full border, fixed
     height, flex-grow) and use border-bottom as a divider between
     rows. */
  .panel-tabs-nav .panel-tab {
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-start;
    height: auto;
    padding: 14px 20px;
    background: transparent;
    border: none;
    border-radius: 0;
    border-bottom: 2px solid var(--border-default);
  }

  /* Current tool SHOWN in menu 2 dropdown with white text per user
     2026-05-29 third pass: "in menu2 uitgeklapt zie ik circle of
     fifths (current) niet staan in de dropdown. dat moet wel!!!!
     en die moet wit." */
  .panel-tabs-nav .panel-tab.active {
    display: flex;
    color: var(--text-strong);
  }

  .panel-tabs-nav .panel-tab:last-child {
    border-bottom: none;
  }

  /* Toolbar full-width on mobile — no divider, no horizontal offsets
     (those only exist on desktop where the bar is a single row).
     RESET `flex` to content-height: on desktop the toolbar has
     `flex: 0 1 480px` (basis 480 = WIDTH along the row main axis).
     On mobile the bar is flex-column, so the same basis becomes a
     480px HEIGHT — toolbar renders 480px tall = huge empty gap
     under the switcher. `flex: 0 0 auto` collapses it to content
     height. User 2026-05-27: "op mobile staat de menu bar heel raar.
     moet direct onder de title".

     `order` puts the toolbar visually right below the switcher
     (order: 1) and the nav-tabs panel after the toolbar (order: 2)
     when expanded — so the chevron-fold drops the tab list under
     the toolbar instead of between the title and the toolbar.

     Higher-specificity override (body.app-shell .panel-tabs-bar
     .panel-tabs-toolbar) is needed because the desktop divider rule
     at the same selector specificity sets border-left + padding-left
     + margin-left and would otherwise win regardless of @media.
     User 2026-05-28: "bij viewport vanaf 150% moet de menu2 bar
     ... full width, en de divider moet weg." */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar {
    width: 100%;
    margin-left: 0;
    padding-left: 0;
    border-left: none;
    justify-content: flex-start;
    gap: 8px;
    flex: 0 0 auto;
    order: 1;
  }
  /* Inner toolbar wrappers (.ft-controls / .cl-controls / .cof-controls
     / .find-controls / .ph-controls-row / etc.) — 8px gap between
     EVERY element (dropdowns + icons), dropdowns grow to fill the
     remaining whitespace via flex-grow on their wrappers. Icons stay
     at their fixed 30×30 shape (no flex-grow) so the user gets:
     "margin TUSSEN elk element moet 8px ... bij witruimte, maak
     dropdowns maar breder qua padding. icons moeten wel exact zo
     blijven, pas de vorm niet aan." User 2026-05-29. */
  /* Wrapper layer (.cof-controls / .ft-controls / .cl-controls /
     .find-controls / .ph-controls-row / etc.) — fills the toolbar
     so the RIGHTMOST icon ends up at the toolbar's right edge (=
     where the FS button used to be). User 2026-05-31: "de meest
     rechter icon moet op de plek waar eerst full screen icon stond.
     dat staat nu dus nog fout." Earlier the wrapper sat at content
     width (no flex-grow on the wrapper itself), so the rightmost
     icon ended at content-end which was somewhere mid-toolbar — not
     at the right edge.
     - flex: 1 1 auto → wrapper fills toolbar width.
     - justify-content: flex-end → wrapper's children pack RIGHT.
     - First-child of wrapper (leftmost dropdown) still gets
       flex-grow: 999 below so it absorbs all freed space and pushes
       icons to the right edge. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > * {
    flex: 1 1 auto !important;
    justify-content: flex-end !important;
    gap: 8px !important;
  }
  .panel-tabs-switcher { order: 0; }
  .panel-tabs-nav { order: 2; }

  .panel-tabs-toolbar::before {
    display: none;
  }

  /* All toolbar wrappers (.cof-controls / .ft-controls / .cl-controls
     / .ls-controls / .find-controls / .ph-controls-row / .bt-toolbar-row
     / .lp-toolbar-row / .jz-search-wrap / .jz-settings-row /
     .lib-glossary-search-wrap / etc.) must grow + justify-end + 8px
     gap at responsive — the wrapper fills the toolbar so the
     RIGHTMOST icon ends at the toolbar's right edge (= former FS
     position). Per-wrapper rules used to set width: auto +
     justify-content: center which pulled icons to the middle. Use
     universal `> *:not(.panel-tabs-fs-btn)` to cover every wrapper
     in one rule (including any future tool's). FS button stays as
     a sibling at flex: 0 0 30px but is display:none at this
     breakpoint anyway. User 2026-05-31: "de dice moet helemaal naar
     rechts (maar nog wel met margin aan de rechter kant). moet op
     elke pagina voor elke toolbar." */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > *:not(.panel-tabs-fs-btn) {
    flex: 1 1 auto !important;
    width: auto !important;
    justify-content: flex-end !important;
    gap: 8px !important;
  }

  /* Stop dropdowns / select wraps from flex-growing to fill the bar
     at responsive. At desktop they need `flex: 1 1 auto` so 3-4
     dropdowns share the 480px toolbar slot evenly; at responsive the
     toolbar takes the whole bar width (~960px) so the same rule
     stretches each dropdown to 200-300px wide — earlier the user said
     "in metronome in responsive die menu 2 toolbar met dropdowns fillt
     de hele page??", so the previous rule capped them at content
     width. User 2026-05-29 reversed that for narrow viewports: "bij
     witruimte, maak dropdowns maar breder qua padding. icons moeten
     wel exact zo blijven." So dropdowns grow to fill the bar with
     8px gaps; icons keep their fixed 30×30 (flex: 0 0 auto sets that
     elsewhere). */
  .panel-tabs-toolbar > * > [class*="-wrap"],
  .panel-tabs-toolbar > * > select,
  .panel-tabs-toolbar > * > input {
    flex: 1 1 auto !important;
  }

  /* Non-first wrapper children stay at content width — only the
     leftmost dropdown grows. User 2026-05-31: "de meest rechter icon
     moet helemaal naar rechts ... de rest van de dropdowns moet je
     niet aanpassen." Without this, ALL dropdowns inherit
     `flex: 1 1 auto` from the earlier broad rule and grow
     proportionally (first child takes 999/1002 ≈ 99.7%, others ~0.1%
     each). Setting flex-grow: 0 on non-first children gives ALL the
     freed space to the leftmost dropdown. */
  body.app-shell .panel-tabs-toolbar > * > *:not(:first-child) {
    flex-grow: 0 !important;
  }

  /* When FS is hidden (which it is at this @1200 breakpoint), shift
     icons + dice to the right so they collectively sit where they
     used to (with FS as the rightmost). Achieved by giving the
     LEFTMOST dropdown (first child of the toolbar wrapper) a
     dominant flex-grow weight (999) so it absorbs ALL the freed
     space on the left, while other dropdowns shrink to their
     content width. Icons stay flex: 0 0 30px so their shape doesn't
     distort. User 2026-05-31: "alles moet zodanig aligned worden
     dat de dice icon komt te staan op de plek waar eerst full
     screen icon stond. dus de dropdowns van de meest linker icon
     moet je extenden ... bij tools met meerdere icons zoals harmony
     trainer ... alle andere icons schuiven eentje naar rechts. en
     dropdown loopt dan door tot waar de eerste icon eindigde
     voordat de icons verschoven." */
  body.app-shell .panel-tabs-toolbar > * > *:first-child {
    flex-grow: 999 !important;
  }

  /* Fullscreen icon sits AFTER .cof-controls in the toolbar slot. Its
     desktop `margin-left: 8px` matches desktop cof-controls gap (8px).
     At responsive the cof-controls gap drops to 4px (above), so the
     FS margin must also drop to 4px to keep the gap rhythm uniform.
     User 2026-05-28 later pass: "bij responsive lijken de margins
     tussen tuulbar items niet te kloppen, margin tussen dice en full
     screen icon is groter dan tussen de dropdowns." */
  body.app-shell .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 4px !important;
  }

  /* No #cof-layout override at narrow. Earlier we capped the panel at
     max-height: 80vh "to keep it a useful height on mobile" — but
     combined with min-height: 540 from the base rule, the 80vh cap
     loses to min-height and the panel ends up at exactly 540. Inner
     content (cof-left 638 + cof-explainer 473 stacked at ≤1100) =
     1111px needs to scroll — but with the panel itself locked at 540,
     the inner gets squashed to its own 540 min-height, while the body
     becomes EXACTLY viewport height (no page scroll either). User
     2026-05-29: "bij 150% viewport past het niet meer op m'n scherm en
     wordt ie responsive. mooi. maar de scrollbar zou dan moeten werken
     bij o.a. cof. en dat doet ie niet!" Every other tool grows
     naturally at narrow viewports (panel = 43 shell + 95 bar + 660
     inner + 4 border = 802) which lets the page scroll. CoF was the
     unique outlier. Dropping the rule aligns CoF with the rest. */
}

.panel-tabs-fs-btn { flex-shrink: 0; }

/* ---- Immersive / Fullscreen mode ----
   User 2026-05-27 (final): "FULLSCREEN MOET DAADWERKELIJK FULL
   SCREEN, ZONDER BROWSER RAND!" Combined with B+C: bigger panel
   in FS than default, but capped via breakpoints so on giant
   screens (4K TV) we don't blow up to silly proportions and don't
   stretch line lengths.

   JS calls `documentElement.requestFullscreen()` AND adds
   body.immersive class. Both must succeed for the look to land:
   - requestFullscreen hides browser URL bar / tabs / OS chrome
   - body.immersive hides our own page chrome + sizes the panel

   On page navigation the browser exits FS automatically (security);
   panel-tabs.js's fullscreenchange listener clears body.immersive +
   sessionStorage so the new page renders normally. User clicks the
   FS button on the new page to re-enter (1 extra gesture per tool
   switch — accepted trade-off vs no real FS at all). */

/* ============================================================
   DESIGN SYSTEM v2 (2026-05-28) — two-color toolkit
   ============================================================
   User: "onthoud dat dit de main 2 kleuren zijn van de toolkit:
   die lichte en die donkere grijs."
   - LICHTE  = var(--bg-control)     #1a1d27 — panel surfaces,
                                              active tab
   - DONKERE = var(--border-default) #2a2d3a — sub-menu bar,
                                              ALL buttons, dividers,
                                              scrollbar track

   Plus: no gradients, no borders on any button, scrollbar always
   visible with square corners. Applied scoped to body.app-shell
   so non-app pages (index, courses) are untouched.
   ============================================================ */

/* Shell bar 3-column grid → cat-nav DEAD CENTER tov the entire
   panel (not centered between favicon and user-menu). User: "doe
   de menu items (learn practice play tools courses shop) DEAD
   CENTER tov de gehele panel." */
body.app-shell .panel-shell-bar {
  display: grid !important;
  grid-template-columns: 1fr auto 1fr !important;
  align-items: center;
  /* Explicit per-child align-self: center too — `align-items: center`
     on the grid alone leaves vertical centering to the browser, and
     sub-pixel rounding can land one child at y=84 and another at y=83
     when the bar height (43) minus child height (30) = 13 is odd.
     User 2026-05-30: "favicon in menu 1 lijkt niet meer dead vertical
     center te staan in menu1 ... zorg dat alles exact zelfde staat
     wanneer ingelogd als wanneer niet ingelogd!!" Forcing the same
     align-self on every direct child guarantees uniform rounding so
     logo / categories / user widget all sit at the same y regardless
     of auth state or content. */
  /* Explicit !important height lock so the bar can never reflow
     differently between tools — e.g. if a tool's user-menu /
     hamburger / cats content briefly resolves to a taller size
     during init, the bar would stay at 43 instead of growing.
     User 2026-05-28 later pass: "menu 1 in backing tracks is
     HOGER in height dan menu1 in eg looper?? dus in backing
     tracks is ie fout!!" */
  height: 44px !important;
  /* Bar bg stays bg-control (the lichte panel surface) to match
     the panel beneath it. The donkere accent is reserved for the
     panel-tabs-bar (sub-menu bar). */
  background: var(--bg-control);
  /* No border-bottom — the lichte → donker bg colour change between
     shell bar and panel-tabs-bar IS the visual boundary. Adding a
     stroke here makes menu 2's buttons appear off-centre because the
     user's eye includes the stroke in menu 2's perceived top edge.
     User 2026-05-28 third pass: "buttons and dropdowns etc are not
     centered in menu 2 vertically. probably bc there is a 3px (or
     whatever) stroke on top ... so get rid of that." */
  border-bottom: none;
}
body.app-shell .panel-shell-logo { grid-column: 1; justify-self: start; align-self: center; }
body.app-shell .panel-shell-categories {
  grid-column: 2;
  justify-self: center;
  align-self: center;
  /* Drop the flex: 1 1 auto from earlier rule — grid handles
     positioning now. */
  flex: 0 0 auto !important;
}
body.app-shell .panel-shell-user { grid-column: 3; justify-self: end; align-self: center; }
/* FS button moves OUT of the shell bar — appended into the
   panel-tabs-toolbar by panel-tabs.js (8px margin via the toolbar's
   existing flex gap). Hidden if it lands in the shell. */
body.app-shell .panel-shell-bar .panel-tabs-fs-btn {
  display: none !important;
}

/* Tabs in panel-tabs-bar: transparent + no border (bar bg shows
   through). Toolbar items (selects, dice, fs): no border, blend
   with bar bg for a flat-icon look. User: "de stroke rondom de
   buttons in die menu bar en ook de toolbar icons (dice dropdowns
   fullscreen icon etc) kan weg." */
body.app-shell .panel-tabs-bar .panel-tab,
body.app-shell .panel-tabs-bar .panel-tabs-switcher {
  background: transparent !important;
  border: none !important;
}
/* Tabs + toolbar items use DONKER GRIJS bg (--tk-donker-grijs =
   --bg-page, same as the site bg). User 2026-05-28 (revised naming):
   "die kleur accent colour 2 (donker grijs) wil ik hebben ...
   voor bijv de button kleur van circle of fifths, fretboard tool,
   etc." */
body.app-shell .panel-tabs-bar .panel-tab {
  /* Non-active tab = MEDIUM GRIJS (panel-bg). Stands out as a light
     block against the donker bar bg. User 2026-05-28: "non-current
     moet tk-medium grijs." */
  background: var(--tk-medium-grijs) !important;
  border: none !important;
  color: var(--tk-text-grijs) !important;
  border-radius: 6px !important;
}
body.app-shell .panel-tabs-toolbar select,
body.app-shell .panel-tabs-toolbar input[type="text"],
body.app-shell .panel-tabs-toolbar input[type="search"],
body.app-shell .panel-tabs-toolbar button,
body.app-shell .panel-tabs-toolbar .lib-dice-btn,
body.app-shell .panel-tabs-fs-btn {
  /* Dropdowns + search inputs + icon buttons = MEDIUM GRIJS
     (non-current tab colour). User 2026-05-28 fifth pass: "in menu 2,
     maak de dropdowns, de icons, de current, maak allemaal kleur van
     non-current." User 2026-05-29 added the search inputs (glossary /
     jazz-standards) to the same family: "kleur van de search bar moet
     zelfde als panel bg kleur."
     `background-color` (not the shorthand) so the chevron SVG
     bg-image on selects + the magnifier-glass bg-image on the search
     input aren't wiped. */
  background-color: var(--tk-medium-grijs) !important;
  border: none !important;
  color: var(--tk-text-grijs) !important;
  border-radius: 6px !important;
}
body.app-shell .panel-tabs-toolbar input[type="text"],
body.app-shell .panel-tabs-toolbar input[type="search"] {
  /* Search input font colour = --tk-text-grijs to match the body text
     inside glossary boxes / jazz card titles — keeps the search bar
     reading as part of the same content family. User 2026-05-29:
     "maak font kleur en size van 'find a term' consistent met de
     text kleur en font in glossary boxes. gebruik hiervoor de theme
     kleur voor text: --tk-text-grijs. pas ook toe in jazz standards." */
  color: var(--tk-text-grijs) !important;
}
body.app-shell .panel-tabs-bar .panel-tab:hover,
body.app-shell .panel-tabs-toolbar select:hover,
body.app-shell .panel-tabs-toolbar input[type="text"]:hover,
body.app-shell .panel-tabs-toolbar input[type="search"]:hover,
body.app-shell .panel-tabs-toolbar input[type="text"]:focus,
body.app-shell .panel-tabs-toolbar input[type="search"]:focus,
body.app-shell .panel-tabs-toolbar button:hover,
body.app-shell .panel-tabs-toolbar .lib-dice-btn:hover,
body.app-shell .panel-tabs-fs-btn:hover {
  color: var(--tk-text-wit) !important;
}
body.app-shell .panel-tabs-bar .panel-tab.active {
  /* Active tab = MEDIUM GRIJS — same colour as non-current tabs
     and the toolbar dropdowns/icons. ALL controls in MENU 2 sit at
     this one colour; active vs inactive is marked by text colour
     only (white vs grey). User 2026-05-28 fifth pass: "in menu 2,
     maak de dropdowns, de icons, de current, maak allemaal kleur
     van non-current." */
  color: var(--tk-text-wit) !important;
  background: var(--tk-medium-grijs) !important;
}
/* FS button — square 30×30 (was rendering as a rectangle because
   parent `button { height: 30 !important }` won width-wise via the
   `.lib-dice-btn` class width default 35). User 2026-05-28: "moet
   vierkant blijven he (wel afgeronde hoek uiteraard), maar nu is
   hij rechthoek???" Lock both dimensions. 8px margin from dice via
   margin-left + 8px from bar right edge via the bar's padding +
   no extra margin-right. */
body.app-shell .panel-tabs-toolbar > .panel-tabs-fs-btn {
  /* `flex: 0 0 30px` overrides the `.panel-tabs-toolbar > * { flex:
     1 1 auto !important }` rule earlier in this file which was
     growing the FS button to fill leftover width. flex-grow: 0
     prevents expansion. */
  flex: 0 0 30px !important;
  width: 30px !important;
  min-width: 30px !important;
  max-width: 30px !important;
  height: 30px !important;
  /* 8px from the dice — matches the gap rhythm between every other
     adjacent control inside the toolbar (per inner toolbar's
     `gap: 8px`). User 2026-05-28 second pass: "spacing tussen dice
     en full screen icon klopt niet. moet gelijk aan spacing tussen
     alle buttons enzo." Earlier 2px was a deliberate tight pin;
     now relaxed to the canonical 8px. */
  margin-left: 8px !important;
  margin-right: 0 !important;
  border-radius: 6px !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
}

/* At narrow viewports (≤1200px) the toolbar gets `gap: 8px` between
   its direct children, so the FS button's own `margin-left: 8px`
   above DOUBLES the gap (8 gap + 8 margin = 16px between dice and
   FS). Zero out the margin in the narrow @media so only the toolbar
   gap applies. Placed AFTER the top-level rule so source order wins
   regardless of specificity (both selectors equal at 0,3,1). User
   2026-05-29: "viewports vanaf 150% check spacing tussen dice en
   full screen button dat moet 8px zijn toch?? is het niet." */
@media (max-width: 1200px) {
  body.app-shell .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
}

/* ALL buttons app-wide: donkere bg, no border, light text. User:
   "de buttons moeten ook die kleur krijgen, ALLE buttons. dus ook
   ascending descending view all products etc etc. ook in settings,
   sign up buttons, ALLES!!" */
body.app-shell .btn,
body.app-shell .cl-btn,
body.app-shell .ft-btn,
body.app-shell .fd-btn,
body.app-shell .sf-btn,
body.app-shell .kf-btn,
body.app-shell .kf-btn-add,
body.app-shell .bt-action-btn,
body.app-shell .lp-action-btn,
body.app-shell .settings-btn,
body.app-shell .saved-item-btn,
body.app-shell .auth-btn,
body.app-shell .auth-social-btn,
body.app-shell .et-action-btn,
body.app-shell .et-answer-btn,
body.app-shell .fn-opt-btn,
body.app-shell .journey-next-btn,
body.app-shell .journey-quiz-opt,
body.app-shell .settings-btn-primary,
body.app-shell .settings-btn-secondary,
body.app-shell .settings-btn-danger,
body.app-shell .jz-back-btn,
body.app-shell .cl-pos-btn,
body.app-shell .fd-card-btn,
body.app-shell .kf-card-link,
body.app-shell .sf-card-btn,
body.app-shell .sf-card-link,
body.app-shell .fd-show-more,
body.app-shell .kf-show-more,
body.app-shell .sf-show-more,
body.app-shell .tuner-reset-btn {
  background: var(--border-default) !important;
  border: none !important;
  color: var(--text-strong) !important;
  box-shadow: none !important;
}
body.app-shell .btn:hover,
body.app-shell .cl-btn:hover,
body.app-shell .ft-btn:hover,
body.app-shell .fd-btn:hover,
body.app-shell .sf-btn:hover,
body.app-shell .kf-btn:hover,
body.app-shell .kf-btn-add:hover,
body.app-shell .bt-action-btn:hover,
body.app-shell .lp-action-btn:hover,
body.app-shell .settings-btn:hover,
body.app-shell .saved-item-btn:hover,
body.app-shell .auth-btn:hover,
body.app-shell .auth-social-btn:hover,
body.app-shell .et-action-btn:hover,
body.app-shell .fd-card-btn:hover,
body.app-shell .kf-card-link:hover,
body.app-shell .sf-card-btn:hover,
body.app-shell .sf-card-link:hover,
body.app-shell .fd-show-more:hover,
body.app-shell .kf-show-more:hover,
body.app-shell .sf-show-more:hover,
body.app-shell .login-prompt-btn:hover {
  background: var(--border-hover) !important;
  color: var(--text-strong) !important;
}
/* Pills (cof-pill segmented controls, jz-chip filter chips,
   et-exercise-tab, lib-glossary-cat) — bg matches the button family
   but keep the group border / active state from their existing
   rules (segmented control language). */
body.app-shell .cof-pill {
  background: transparent !important;
  border: none !important;
}
body.app-shell .cof-pill.active {
  background: var(--bg-control) !important;
}
body.app-shell .cof-pill-group {
  background: var(--border-default) !important;
  border: none !important;
}
body.app-shell .jz-chip,
body.app-shell .et-exercise-tab,
body.app-shell .lib-glossary-cat {
  /* NO stroke — pills are flat tiles on the panel. User 2026-05-30:
     "glossary category pills moeten GEEN stroke hebben. ze hebben
     nu een lichtere stroke. dat moet NIET. die moet weg!!! pas ook
     toe bij jazz standards." Active + non-active share the SAME bg
     (--border-default = menu 2 bg) so only text colour distinguishes
     (subtle on non-active, white on active). */
  background: var(--border-default) !important;
  border: none !important;
  color: var(--text-default) !important;
}
body.app-shell .jz-chip.active,
body.app-shell .et-exercise-tab.active,
body.app-shell .lib-glossary-cat.active {
  background: var(--border-default) !important;
  color: var(--text-strong) !important;
}

/* Toggles (segmented controls) KEEP their stroke. User 2026-05-28:
   "de notes/chords en standard/simplified toggles moeten wel een
   stroke hebben. geldt voor alle toggles, overal. maar buttons
   hoeven dus geen stroke meer." The cof-pill-group is the
   container that carries the stroke around BOTH pills as a unit;
   pills inside stay borderless. */
body.app-shell .cof-pill-group {
  background: var(--bg-control) !important;
  /* Real CSS border REMOVED — replaced with ::after pseudo-element
     below. With a CSS border, `overflow: hidden` clips the pill bg
     at the PADDING box (inside the border), creating a visible "gap"
     between the pill bg and the bottom stroke. With the ::after
     trick, the pill bg fills the FULL border-radius shape and the
     visible border paints ON TOP via the pseudo. User 2026-05-28
     seventh pass: "notes/chords en stand/simplified toggles hebben
     de juiste radius niet meer. en er zit nog steeds een donkere
     gap tussen de current selected bg en de stroke aan de
     onderkant." */
  border: none !important;
  border-radius: 8px !important;
  overflow: hidden;
  position: relative;
  /* Force flex with NO gap so the two pills share the row 50/50
     without inter-pill spacing — earlier the active pill was wider
     than the inactive (content-driven), creating an asymmetric
     toggle. User 2026-05-28: "note/chord toggle is buggy!!!! niet
     mooi, zie de rand." */
  display: inline-flex !important;
  gap: 0 !important;
  padding: 0 !important;
}
/* Paint the toggle's visible stroke as a pseudo-element ON TOP of the
   pills — so the active pill bg can fill the FULL group box (incl.
   the corner zones) without being clipped by a real CSS border. */
body.app-shell .cof-pill-group::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 2px solid var(--tk-licht-grijs);
  border-radius: inherit;
  pointer-events: none;
  z-index: 2;
}
body.app-shell .cof-pill {
  flex: 1 1 0 !important;
  min-width: 0 !important;
  background: transparent !important;
  border: none !important;
  border-radius: 0 !important;
  height: 100% !important;
  padding: 0 14px !important;
}
/* Library instrument toggle (.cl-inst-toggle) — pills size to their
   CONTENT instead of equal-width, so "UKULELE" (the longest label)
   gets full 14px padding on both sides like the other pills, instead
   of being squeezed into a 74px box with text overflowing the
   padding zone. User 2026-05-29: "library instrument toggle padding
   is fout, eg padding bij ukulele is klein rechts, maar padding
   links van eg guitar niet ... padding rechts van ukulele komt niet
   overeen!!!" */
body.app-shell .cl-inst-toggle .cof-pill {
  flex: 0 0 auto !important;
  min-width: auto !important;
}
body.app-shell .cof-pill.active {
  /* Active pill = LICHT GRIJS (#2a2d3a = --border-default = divider
     colour). The active state stands OUT as a lighter tile against
     the medium-grijs panel bg. User 2026-05-28 fifth pass: "current
     selected ... maak de zelfde kleur als de dividers." */
  background: var(--tk-licht-grijs) !important;
}
body.app-shell .cof-pill:not(.active) {
  /* Non-active pill = MEDIUM GRIJS (panel bg), matches the non-
     current tab in MENU 2. User 2026-05-28 fifth pass: "non-selected
     de zelfde kleur als eg non-current in MENU 2." */
  background: var(--tk-medium-grijs) !important;
}
/* Pills fill the full inner height of the group (no visible line
   at bottom). User 2026-05-28: "check de toggles, zit een lijn aan
   de onderkant. moet niet ... er zit een ruimte nog tussen ofzo."
   align-self: stretch on each pill + group flex stretch ensures
   pills cover edge-to-edge of the group's content box. */
body.app-shell .cof-pill-group {
  align-items: stretch !important;
}
body.app-shell .cof-pill {
  align-self: stretch !important;
  height: auto !important;
}
body.app-shell .cof-pill-group {
  /* Group bg = MEDIUM GRIJS (matches non-active pill so the wrapper
     visually merges with the inactive segment). Border = --tk-licht-
     grijs (the divider colour) — the toggle reads as a deliberate
     framed segmented control against the panel surface. */
  background: var(--tk-medium-grijs) !important;
  border-color: var(--tk-licht-grijs) !important;
}

/* Divider streep between panel-tabs-nav (tab buttons) and panel-
   tabs-toolbar (dropdowns + icons) inside the sub-menu bar.
   User 2026-05-28 second pass: divider colour now matches the
   panel-bg (`--tk-medium-grijs`), same as the toolbar section bg
   so the two visually fuse — the divider just becomes the LEFT
   EDGE of the lichte toolbar section against the donkere bar bg.
   The toolbar section itself fills with panel-bg ("toolbar section
   van menu 2 rechts van de divider ... moet zelfde kleur als
   CURRENT in het menu, dus die panel bg kleur").

   Desktop only — at ≤1200px the bar stacks vertically (switcher
   above, toolbar below), where a vertical divider makes no sense.
   User 2026-05-28: "bij viewport vanaf 150% moet de menu2 bar ...
   full width, en de divider moet weg." */
@media (min-width: 1201px) {
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar {
    /* Toolbar bg = same as the bar bg (donker accent shows through) so
       the bar reads as one continuous donker strip from left to right.
       User 2026-05-28 third pass: "toolbar on right side of menu2.
       should be same background colour as left side." The visible
       "divider" is just the border-left in --tk-medium-grijs (panel-bg)
       — a thin lichte line between the bar's two halves (both donker). */
    background: transparent;
    border-left: 2px solid var(--tk-medium-grijs);
    padding-left: 10px;
    margin-left: 4px;
  }
}

/* Bar padding — left + right = scrollbar width (14px). User 2026-
   05-28: "de spacing rechts van de buitenkant moet meer!!! moet
   identiek aan de breedte van de scroll bar. geldt ook links!!!" */
body.app-shell .panel-tabs-bar,
body.app-shell .panel-shell-bar {
  padding-left: 14px !important;
  padding-right: 14px !important;
}

/* Sub-menu bar — drop the bottom border so the scrollbar gutter
   flows seamlessly upward into the bar (no visible gap line). The
   bar's donkere bg (`--border-default`) and the scrollbar track
   (same colour) are continuous when the divider is removed.
   User 2026-05-28: "scrollbar moet doorlopen tot boven!!! zodat je
   geen gap ziet." */
body.app-shell .panel-tabs-bar {
  border-bottom: none !important;
}

/* Diatonic note/chord pills — REMOVE stroke. User 2026-05-28:
   "ook de diatonic note/chord pills in eg circle of fifths,
   library fretboard tool en backing tracks en in key finder, die
   hoeven ook geen stroke meer." */
body.app-shell .chord-card,
body.app-shell .kf-diatonic-btn,
body.app-shell .ft-note-chip,
body.app-shell .cl-note-chip,
body.app-shell .kf-chord-chip {
  border: none !important;
}

/* Sign In button (.login-prompt-btn) — NO background, NO stroke.
   User 2026-05-28: "sign in button hoeft geen background te
   hebben." Sits in the shell bar with just text, hover bumps to
   text-strong. */
body.app-shell .login-prompt-btn,
body.app-shell .panel-shell-user .login-prompt-btn {
  background: transparent !important;
  border: none !important;
  color: var(--text-default) !important;
  /* iOS Safari fix: a <button> without -webkit-appearance: none gets native
     styling, and Safari paints button text via -webkit-text-fill-color which
     can leave it transparent/invisible (the user reported "Sign In invisible
     on Safari mobile, visible on Chrome"). Force both so the #ccc text always
     paints. WebKit isn't installable in this sandbox so this is a targeted
     defensive fix, not a reproduced one. User 2026-06-01. */
  -webkit-appearance: none !important;
  appearance: none !important;
  -webkit-text-fill-color: var(--text-default) !important;
  padding: 0 8px !important;
}
body.app-shell .login-prompt-btn:hover {
  color: var(--text-strong) !important;
  -webkit-text-fill-color: var(--text-strong) !important;
}

/* All buttons + toggles → 30px tall (matches the panel-tabs tab
   pill height in the bar above). User 2026-05-28: "qua hoogte,
   maak die buttons (ALLE BUTTONS!!! IN ELKE PAGE ELKE TOOL ELK
   SETTINGS MENU ALLES) even hoog als de buttons 'circle of fifths
   library glossary etc' in de menu bar." */
body.app-shell .btn,
body.app-shell .cl-btn,
body.app-shell .ft-btn,
body.app-shell .fd-btn,
body.app-shell .sf-btn,
body.app-shell .kf-btn,
body.app-shell .kf-btn-add,
body.app-shell .bt-action-btn,
body.app-shell .lp-action-btn,
body.app-shell .settings-btn,
body.app-shell .saved-item-btn,
body.app-shell .auth-btn,
body.app-shell .auth-social-btn,
body.app-shell .et-action-btn,
body.app-shell .et-answer-btn,
body.app-shell .fn-opt-btn,
body.app-shell .jz-back-btn,
body.app-shell .fd-card-btn,
body.app-shell .kf-card-link,
body.app-shell .sf-card-btn,
body.app-shell .sf-card-link,
body.app-shell .fd-show-more,
body.app-shell .kf-show-more,
body.app-shell .sf-show-more,
body.app-shell .tuner-reset-btn,
body.app-shell .jz-chip,
body.app-shell .et-exercise-tab,
body.app-shell .lib-glossary-cat {
  height: 30px !important;
  padding: 0 14px !important;
  line-height: 1 !important;
  font-size: 0.78rem !important;
  font-weight: 700 !important;
  text-transform: uppercase !important;
  letter-spacing: 0.05em !important;
}
/* .cof-pill INTENTIONALLY NOT in the height-30 list above — pills
   inside a .cof-pill-group must stretch to the GROUP's full 35px
   height (no gap at bottom). Apply the typography here separately
   from the height rule. User 2026-05-28: "er zit nog steeds een
   donkere gap tussen de current selected bg en de stroke aan de
   onderkant" — the 30px lock plus align-items: stretch (where
   explicit height OVERRIDES stretch in CSS Box Alignment) caused
   the pill to sit at flex-start with a 5px gap at the bottom.
   Now `height: 100% !important` wins (pill fills full 35px group),
   no gap. */
body.app-shell .cof-pill {
  height: 100% !important;
  padding: 0 14px !important;
  line-height: 1 !important;
  font-size: 0.78rem !important;
  font-weight: 700 !important;
  text-transform: uppercase !important;
  letter-spacing: 0.05em !important;
}

/* Black/colour glow rondom CoF panel weg. User 2026-05-28: "de
   zwarte glow rondom circle of fifths kan weg." Likely a
   box-shadow on #cof-layout or .cof-panel-visible. */
body.app-shell #cof-layout,
body.app-shell .ft-layout,
body.app-shell .cl-layout {
  box-shadow: none !important;
}

/* Scrollbar should fill to the TOP edge of the panel-tabs-inner.
   User 2026-05-28: "de scroll bar - fill tot aan de bovenrand."
   Inner had padding-top that pushed scrollbar down; the scrollbar
   itself uses the container's content box, so any inner padding
   moves the bar inward. Move padding from inner to a sub-wrapper
   OR set padding-top: 0 here and let the original content's own
   spacing handle the top gap. */
body.app-shell .panel-tabs-inner {
  padding-top: 0 !important;
}

/* Tablet (601-1200): nuclear width override — force EVERY level of
   the wrapping chain (panel, panel-tabs-inner, bars, .tool-page) to
   be exactly 100vw with zero margin / padding / border so any leftover
   panel chrome (max-width centring, inline padding from panel-tabs.js,
   borders, margins) cannot create the visible "rand" on the page
   edges. User 2026-06-01 fourth pass: "kijk goed naar screenshot.
   snap je welke rand ik bedoel? geldt voor elke pagina, elke tool,
   alles. alleen op tablet, NIET OP MOBIEL!!! het is de rand waar
   eerst panel stond maar die heb je verwijderd. ipv panel staat er
   nu een rand. ... on right too."

   The rand was the leftover of the centred-panel structure: panel
   had `max-width: 1100; margin: 0 auto` at base, so when the panel
   was wider than the viewport-allowed content area something was
   adding sides margins. Combined with panel-tabs.js's INLINE
   `inner.style.width = '100%'` (which respects the panel's narrower
   width when max-width was capping), the inner ended up at less than
   100vw too.

   At tablet: force the panel + inner + bars + tool-page all to 100vw
   with margin/padding/border 0. Mobile (≤600) keeps the original
   panel chrome (per "NIET OP MOBIEL"). */
@media (min-width: 601px) and (max-width: 1200px) {
  body.app-shell .panel-tabs-inner {
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
    width: 100% !important;
    max-width: none !important;
    border: none !important;
  }
  body.app-shell .tool-panel-exp,
  body.app-shell .find-panel.active,
  body.app-shell #cof-layout,
  body.app-shell .ft-layout,
  body.app-shell .cl-layout,
  body.app-shell .et-exercise-box,
  body.app-shell .bt-mixer,
  body.app-shell .lp-tracks,
  body.app-shell .lib-glossary,
  body.app-shell .jz-browse,
  body.app-shell .jz-detail,
  body.app-shell .dashboard-panel {
    width: 100vw !important;
    max-width: 100vw !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    border: none !important;
    border-radius: 0 !important;
    background: transparent !important;
  }
  body.app-shell:not(.immersive) .tool-page {
    width: 100vw !important;
    max-width: 100vw !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  /* Menu 2 bar's border-bottom — even at `var(--bg-deeper)` (#12141c
     vs body #0f1117) this is a 2px stripe visible as part of the
     "rand". Drop it at tablet. */
  body.app-shell .panel-tabs-bar {
    border-bottom: none !important;
  }
}

/* CoF #container is the diagram-row grid item. `margin: auto 0` centres
   it vertically within the 1fr row (= between notes-bottom and toggle-
   top); `justify-items: center` on #cof-left centres it horizontally.
   This replaced the old `position: absolute; top: 50%` which pinned the
   wheel to cof-left's GEOMETRIC centre — wrong, because the title block
   above is taller than the toggle below, so the wheel read ~5px high.
   User 2026-06-01: "center cof circle vertically tussen subtitle en
   instrument toggle, in elke viewport." */
body.app-shell #cof-left {
  position: relative !important;
}
body.app-shell #cof-left > #container {
  position: static !important;
  margin: auto 0 !important;
}
/* At responsive (≤1200) cof-layout becomes flex-column and cof-left
   grows to its content height — the 1fr diagram row has no slack to
   absorb, so give #container symmetric 16px margins for an even gap
   between the notes above and the toggles below. */
@media (max-width: 1200px) {
  body.app-shell #cof-left > #container {
    margin: 16px auto !important;
  }
}
body.immersive {
  overflow: hidden;
}
body.immersive .site-header,
body.immersive #tool-nav,
body.immersive .page-title-row,
body.immersive .page-subtitle {
  display: none !important;
}
body.immersive .tool-page {
  padding: 0 !important;
  margin: 0 !important;
  max-width: none !important;
  width: 100vw !important;
  height: 100vh !important;
  min-height: 0 !important;
  box-sizing: border-box !important;
  background: var(--bg-page);
  /* Centre the panel inside the now-full viewport. Flex centring is
     symmetric — the panel sits in the middle of the screen
     regardless of viewport size. */
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}
/* Panel sizing in immersive — Option B with breakpoints (B+C mix
   per user). Smaller screens: fill viewport minus 32px. Mid screens
   (1600-2400): cap at 1500×950 (Spotify / Notion convention).
   Huge screens (>2400, 4K TV): proportional 60vw × 70vh so we
   don't strand the panel as a tiny window in a sea of body-bg.

   panel-layout.css's `#cof-layout` etc. use ID specificity (1,0,0)
   for max-width 1100. We override with (0,2,0) + !important. */
body.immersive #cof-layout,
body.immersive .ft-layout,
body.immersive .cl-layout,
body.immersive .find-panel,
body.immersive .et-exercise-box,
body.immersive .bt-mixer,
body.immersive .lp-tracks,
body.immersive .tool-panel-exp {
  position: relative !important;
  top: auto !important;
  left: auto !important;
  right: auto !important;
  bottom: auto !important;
  /* Default for screens <1600 — fill viewport minus ~1cm each side */
  width: calc(100vw - 32px) !important;
  height: calc(100vh - 32px) !important;
  max-width: none !important;
  max-height: none !important;
  min-height: 0 !important;
  /* `auto` horizontal margins centre the panel within its wrapper. For the
     tools whose panel is a DIRECT flex child of .tool-page (CoF / Fretboard /
     Analysis) this is a no-op (justify-content: center already centres them).
     But Library (.cl-layout inside the #lib-scales/#lib-chords/#lib-modes
     block wrapper) and Glossary (.lib-glossary.tool-panel-exp inside the
     #lib-glossary block wrapper) are NOT direct flex children — the flex
     parent centres the full-width WRAPPER, and a plain `margin: 0` then
     left-aligns the calc(100vw-32px) panel inside it, leaving all 32px gap
     on the right (off-centre). `margin: 0 auto` centres the panel in the
     wrapper. User 2026-06-07: library + glossary not horizontally centred in
     full-screen mode. */
  margin: 0 auto !important;
  z-index: 100 !important;
}
/* Immersive: the intermediate wrappers cap at max-width: 1100 (ear/sight
   via panel-layout.css `body.cat-practice #ph-ear-train/#ph-staff-practice`,
   glossary via `#lib-glossary`). The pinned panel inside is sized to
   calc(100vw - 32px) (= 1368 at vw=1400) — WIDER than the 1100 wrapper —
   so it overflowed the wrapper to the right by ~118px at 1400. Unbind the
   wrapper cap in immersive so the panel fits and centres. CoF/Library/
   Fretboard/Analysis have no such wrapper, so they were already fine.
   Found by the immersive sweep 2026-06-01. (0,3,1) beats the (0,2,1)
   panel-layout rule. */
body.app-shell.immersive #ph-ear-train,
body.app-shell.immersive #ph-staff-practice,
body.app-shell.immersive #lib-glossary {
  max-width: none !important;
  width: 100% !important;
}
/* Mid-range screens (1600 to 2400) — cap at 1500×950 */
@media (min-width: 1600px) {
  body.immersive #cof-layout,
  body.immersive .ft-layout,
  body.immersive .cl-layout,
  body.immersive .find-panel,
  body.immersive .et-exercise-box,
  body.immersive .bt-mixer,
  body.immersive .lp-tracks,
  body.immersive .tool-panel-exp {
    width: 1500px !important;
    height: 950px !important;
  }
}
/* Giant screens (>2400) — proportional, not capped. Keeps the
   panel filling most of the viewport on 4K TV's so it doesn't
   feel like a postage stamp in a black void. */
@media (min-width: 2400px) {
  body.immersive #cof-layout,
  body.immersive .ft-layout,
  body.immersive .cl-layout,
  body.immersive .find-panel,
  body.immersive .et-exercise-box,
  body.immersive .bt-mixer,
  body.immersive .lp-tracks,
  body.immersive .tool-panel-exp {
    width: 60vw !important;
    height: 70vh !important;
  }
}
/* Category accent stripe stays visible in immersive mode — user
   2026-05-27: "full screen nu is de blauwe rand links weg." The
   `::before` pseudo-element with the inset box-shadow stripe
   carries through, but its border-radius needs to keep matching
   the panel's outer radius for the curve to align at the corners. */
/* Inner wrapper (created by panel-tabs.js) — let it expand to the
   new available height. The JS-frozen inline height is cleared in
   setImmersive() but rules below act as a safety net. */
body.immersive .panel-tabs-inner {
  height: auto !important;
  min-height: 0 !important;
  flex: 1 1 auto !important;
  max-height: none !important;
}

/* ============================================================
   Per-category toolbar GRID — fixed slot widths per role.
   ============================================================

   User 2026-05-31: "ik wil dat de dropdowns zo min mogelijk
   verspringen wanneer ik switch tussen tools ... DIE EXACTE dropdown
   met key/root selector moet in elke tool exact even breed qua
   breedte en padding ... pas dit toe op alle toolbar dropdowns op
   web en mobile. MAAR!!! ZORG dat de margin er tussen exact zelfde
   blijft: 8px ... doe het bij alle menu's (learn + practice +
   play + tools)."

   Approach: at desktop (≥1201), the .panel-tabs-toolbar becomes a
   CSS GRID with NAMED LINES per role-slot. Wrappers (.cof-controls,
   .ft-controls etc + their inner .X-select-wrap) use display:contents
   so their children become direct grid items. Each <select> /
   <input> / <button> sits in a named column via grid-column.
   Result: items with the same role share the SAME column → same
   x position + same width across tools, regardless of how many
   other dropdowns the tool has.

   Per category, slot widths differ (different roles per category). */
@media (min-width: 1201px) {

  /* ---- LEARN ---------------------------------------------------
     Per-slot fixed widths per user 2026-05-29 spec. Toolbar stays at
     the DEFAULT `flex: 0 1 480px` (divider position preserved).

     User's 6 numbered slots (all role-equivalent items share width):
       w1 = 100px — slot 1: CoF root, FT instrument, LIB type,
                    Analysis instrument
       w2 = 284px — slot 2: CoF scale, Analysis find-type (CF mode)
       w3 =  60px — slot 3: FT root, LIB root
       w4 = 216px — slot 4: FT scale, LIB scale
       w5 = 392px — slot 5: Glossary search bar
       w6 =  30px — slot 6: dice / FS icons
     Derived from user spec (w2 = w3 + 8 + w4; w5 = w1 + 8 + w2;
     Analysis find-type = w2 + 8 + w6 since Analysis has no dice).

     CSS grid uses 5 columns matching the smallest decomposition
     (s1, s3, s4, d, f). w2 is realised as a SPAN across s3+gap+s4;
     w5 is realised as a SPAN across s1+gap+s3+gap+s4; Analysis
     find-type spans s3 to f (= s3+gap+s4+gap+d).

     Grid template:
       [s1] 100  [s3]  60  [s4] 216  [d] 30  [f] 30  [end]
     Total = 100+60+216+30+30 = 436 + 4×8 gaps = 468. Plus 12px
     padding-left + border-left = 480 = toolbar width. EXACT fit
     so the 8px gap rule holds everywhere ("er mag nooit meer dan
     8px tussen twee elementen in de toolbar zitten"). No 1fr slack
     column anywhere. */
  body.cat-learn .panel-tabs-toolbar {
    flex: 0 1 480px;
  }
  body.app-shell.cat-learn .panel-tabs-toolbar {
    display: grid !important;
    grid-template-columns:
      [s1] 100px
      [s3] 60px
      [s4] 216px
      [d] 30px
      [f] 30px [end];
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  body.app-shell.cat-learn .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .ft-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .cl-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .ls-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .find-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .lib-glossary-search-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .gt-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .ft-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .cl-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .ls-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .find-select-wrap:not(.find-key-wrap):not(#kf-input-mode-wrap) {
    display: contents !important;
  }
  /* FS button — the legacy flex-layout rule at line ~1529 sets
     `margin-left: 8px !important` to maintain 8px from dice in the
     pre-grid flex era. In our grid layout, the toolbar's `gap: 8px`
     already provides that spacing — the extra margin-left DOUBLES
     it to 16px and pushes FS 8px past the toolbar's right edge.
     Zero it out for cat-learn grid. */
  body.app-shell.cat-learn .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  /* find-key-wrap is `display: none` by default (KF mode hides them);
     `.visible` class is added by analysis.js when entering KF mode.
     Only contents-flatten when visible — otherwise the wrappers stay
     hidden and don't take grid slots. Same for kf-input-mode-wrap. */
  body.app-shell.cat-learn .panel-tabs-toolbar .find-key-wrap.visible {
    display: contents !important;
  }
  /* Item grid-column placements moved OUTSIDE the @media block
     (see "LEARN — grid-column placements" further down) so they
     apply at both the desktop band (≥1201) AND the narrow band
     (601-1500). At display:flex (mobile ≤600) the rules are
     inert — grid-column has no effect on flex children. */
  body.app-shell.cat-learn .panel-tabs-toolbar select,
  body.app-shell.cat-learn .panel-tabs-toolbar input[type="text"],
  body.app-shell.cat-learn .panel-tabs-toolbar input[type="search"] {
    width: 100% !important;
    min-width: 0 !important;
    flex: initial !important;
  }
  /* Override the legacy "uniform role widths" ID-based rules (line
     940-960) — those used `flex: 0 0 110px` which forces width
     regardless of grid column. ID specificity is (0,2,1); we add
     cat-learn to outspecificity (0,3,1). */
  body.app-shell.cat-learn .panel-tabs-toolbar #cof-root-select,
  body.app-shell.cat-learn .panel-tabs-toolbar #ft-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #ft-instrument,
  body.app-shell.cat-learn .panel-tabs-toolbar #cl-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #ls-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #lm-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #find-instrument,
  body.app-shell.cat-learn .panel-tabs-toolbar #find-key-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #cof-scale-select,
  body.app-shell.cat-learn .panel-tabs-toolbar #ft-category,
  body.app-shell.cat-learn .panel-tabs-toolbar #cl-type,
  body.app-shell.cat-learn .panel-tabs-toolbar #ls-scale,
  body.app-shell.cat-learn .panel-tabs-toolbar #lm-scale,
  body.app-shell.cat-learn .panel-tabs-toolbar #find-key-scale {
    width: 100% !important;
    flex: initial !important;
    min-width: 0 !important;
  }

  /* ---- LEARN — Analysis Key Finder mode ----------------------
     When Key Finder panel is active, analysis.js clears the inline
     `display:none` on #kf-input-mode-wrap (sets style.display = '').
     We detect that via :has + attribute selector and re-shape the
     grid for KF mode: 3 dropdowns + FS instead of 1 dropdown + FS.
     User 2026-05-29: "in analysis KF mode there are 3 dropdowns and
     then the full screen icon."

     KF-mode layout (still 480px toolbar, divider preserved):
       100 (instrument) + 8 + 157 (find-type) + 8 + 157 (kf-input-mode)
       + 8 + 30 (FS) + 12 padding = 480.

     Both middle dropdowns equal-pair 157 each per user 2026-05-30
     "pas ook toe bij analysis key finder. maak key finder dropdown
     en chord/note input dropdown even breed. exact zelfde breedte
     als die 2 dropdowns in sight reading die we net deden." Same
     math as PRACTICE slot 16/10: (468 − 100 − 30 − 3×8) / 2 = 157. */
  body.app-shell.cat-learn .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) {
    grid-template-columns:
      [s1] 100px
      [t] 157px
      [kf] 157px
      [f] 30px [end] !important;
  }
  /* KF mode display:contents + placements: moved outside @media
     so they apply at narrow viewports too (see block further down). */

  /* ---- PRACTICE -----------------------------------------------
     User 2026-05-30 slot system (extends LEARN's slot system):
       w1 = 100  (level/key — shared with LEARN slot 1)
       w6 = 30   (icons — shared with LEARN slot 6)
       w7 = 208  (harmony scale; "tikje groter dan slot 4" math:
                  100 + w7 + 4×30 + 5×8 = 468 → w7 = 208)
       w8 = 322  (ET intervals type; "even groot als slot 2" via
                  the row math: 100 + w8 + 30 + 2×8 = 468 → w8 = 322)
       w9 = 105  (ET-HF cat / SR section / TOOLS metronome click;
                  "tikje groter dan slot 1")
       w10 = 209 (ET-HF type / SR type; "tikje groter dan slot 7";
                  constrained by 100 + w9 + w10 + 30 + 24 = 468
                  → w9 + w10 = 314 → split 105/209)

     Toolbar width = 480px (matches LEARN — divider at the exact
     same X position per user "ZORG VOOR ELK DAT DE DIVIDER OP EXACT
     ZELFDE POSITIE STAAT ALS BIJ LEARN OP DESKTOP"). Grid total
     468 + 12 padding/border = 480. The grid template SWITCHES per
     tool via `:has()` so each tool gets the right column widths. */
  body.cat-practice .panel-tabs-toolbar {
    flex: 0 1 480px;
  }
  /* Default — harmony trainer (most items): root + scale + 4 icons */
  body.app-shell.cat-practice .panel-tabs-toolbar {
    display: grid !important;
    grid-template-columns:
      [s1] 100px
      [s7] 208px
      [a1] 30px
      [a2] 30px
      [d] 30px
      [f] 30px [end];
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  /* Ear training — non-HF mode (intervals/notes/chords/scales/modes/rhythm):
     level + exercise (slot 8 = 322) + fs */
  body.app-shell.cat-practice .panel-tabs-toolbar:has(#et-difficulty-select):not(:has(.et-fn-ctrl:not([style*="none"]))) {
    grid-template-columns:
      [s1] 100px
      [s8] 322px
      [f] 30px [end] !important;
  }
  /* Ear training — HF mode (Harmonic Function selected) + Sight reading:
     level (s1=100) + equal-pair slot A + equal-pair slot B + fs.
     User 2026-05-30: "maak het zo dat de identification/construction
     dropdown even groot is als de note pitch/key sig etc dropdown ...
     moeten even groot zijn ... pas het ook toe bij ear training wanneer
     harmonic function = selected."
     Math: (468 − 100 − 30 − 3×8) / 2 = 157 each. So both middle slots
     are 157px wide. Slot 16 stays the LEFT one, slot 10 stays the
     RIGHT one — but they now share the same width. */
  body.app-shell.cat-practice .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])),
  body.app-shell.cat-practice .panel-tabs-toolbar:has(#sp-difficulty-select) {
    grid-template-columns:
      [s1] 100px
      [s16] 157px
      [s10] 157px
      [f] 30px [end] !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar > .ph-controls-row,
  body.app-shell.cat-practice .panel-tabs-toolbar > .ph-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar > .et-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar > .sp-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar .ph-select-wrap:not(.et-fn-ctrl),
  body.app-shell.cat-practice .panel-tabs-toolbar .gt-select-wrap {
    display: contents !important;
  }
  /* ear-training Harmonic Function mode: .et-fn-ctrl wrapper is
     `display: none` by default; ear-training.js sets style.display
     = '' when isFn=true. */
  body.app-shell.cat-practice .panel-tabs-toolbar .et-fn-ctrl:not([style*="none"]) {
    display: contents !important;
  }
  /* FS button — override legacy margin-left: 8px (the toolbar's
     `gap: 8px` already provides that spacing). */
  body.app-shell.cat-practice .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar select,
  body.app-shell.cat-practice .panel-tabs-toolbar input[type="text"] {
    width: 100% !important;
    min-width: 0 !important;
    flex: initial !important;
  }

  /* ---- PLAY ---------------------------------------------------
     User 2026-05-30 slot system:
       w1 = 100  (root/time-sig — shared with LEARN slot 1)
       w5 = 392  (jazz browse search — shared with LEARN slot 5)
       w6 = 30   (icons)
       w8 = 322  (jazz voicing / looper bars; same value as PRACTICE
                  slot 8 — see "11 = 1 + 8 + 8" identity in user spec)
       w11 = 430 (jazz song-view region; spans inst@s1 + voicing@s8,
                  total = 100 + 8 + 322 = 430. Identity check:
                  w11 = w5 + 8 + w6 = 392 + 8 + 30 ✓
                  w11 = w1 + 8 + w8 = 100 + 8 + 322 ✓)
       w12 = 200 (BT scale; "tikje kleiner dan slot 7"; constrained
                  by 100 + w12 + w13 + 2×30 + 4×8 = 468 → w12 = 200)
       w13 = 76  (BT time-sig = w6 + 8 + w6 + 8 = 30 + 8 + 30 + 8)
       w14 = 322 (looper bars = w12 + 8 + w13 + 8 + w6 = 200+8+76+8+30)
                  (= w8 — same span, hence single 322 slot used)

     Toolbar width = 480px (matches LEARN — divider at the same X
     position). Per-tool grids switch via :has(). */
  body.cat-play .panel-tabs-toolbar {
    flex: 0 1 480px;
  }
  /* Default — backing tracks (most items) */
  body.app-shell.cat-play .panel-tabs-toolbar {
    display: grid !important;
    grid-template-columns:
      [s1] 100px
      [s12] 200px
      [s13] 76px
      [d] 30px
      [f] 30px [end];
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  /* Jazz Standards — browse (default panel): search + random + fs */
  body.app-shell.cat-play .panel-tabs-toolbar:has(#jz-search) {
    grid-template-columns:
      [s5] 392px
      [d] 30px
      [f] 30px [end] !important;
  }
  /* Jazz Standards — song view (detail panel):
     instrument (s1) + voicing (s8) + fs */
  body.app-shell.cat-play .panel-tabs-toolbar:has(#jz-instrument) {
    grid-template-columns:
      [s1] 100px
      [s8] 322px
      [f] 30px [end] !important;
  }
  /* Looper: time-sig (s1) + bars (s8) + fs */
  body.app-shell.cat-play .panel-tabs-toolbar:has(#lp-time-sig) {
    grid-template-columns:
      [s1] 100px
      [s8] 322px
      [f] 30px [end] !important;
  }
  body.app-shell.cat-play .panel-tabs-toolbar > .lp-toolbar-row,
  body.app-shell.cat-play .panel-tabs-toolbar > .bt-toolbar-row,
  body.app-shell.cat-play .panel-tabs-toolbar > .lp-controls,
  body.app-shell.cat-play .panel-tabs-toolbar > .bt-controls,
  body.app-shell.cat-play .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-search-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row > .jz-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row > .jz-ctrl-group,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row > .jz-ctrl-group > .jz-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar .lp-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar .bt-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar .gt-select-wrap {
    display: contents !important;
  }
  /* FS button — override legacy margin-left: 8px. */
  body.app-shell.cat-play .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  body.app-shell.cat-play .panel-tabs-toolbar select,
  body.app-shell.cat-play .panel-tabs-toolbar input[type="text"] {
    width: 100% !important;
    min-width: 0 !important;
    flex: initial !important;
  }

  /* ---- TOOLS --------------------------------------------------
     User 2026-05-30 slot system:
       w1 = 100  (metronome time-sig — shared with LEARN slot 1)
       w6 = 30   (icons)
       w9 = 105  (metronome click — same as PRACTICE slot 9)
       w15 = 211 (tuner instrument + tuning — both equal-width,
                  derived from (468 − 30 − 16) / 2 = 211 per user
                  "pak totale width van de toolbar - slot 6 - 8 - 8,
                  delen door 2")
       w16 = 171 (metronome note-type — constrained by
                  100 + w9 + w16 + 2×30 + 4×8 = 468 → w16 = 171)

     Toolbar width = 480px (matches LEARN — divider same X position).
     Tuner bar shows only "instrument / tuning / full screen" — the
     A4 + mic-sensitivity sliders live inside .tuner-stage as of
     2026-05-30 (HTML moved, panel-tabs.js TOOL_CONFIG.tuner.toolbar
     dropped .tuner-sliders). */
  /* Default — tuner (2 dropdowns + fs) */
  body.app-shell.cat-tools .panel-tabs-toolbar {
    display: grid !important;
    grid-template-columns:
      [s15a] 211px
      [s15b] 211px
      [f] 30px [end];
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  /* Metronome: time-sig (s1) + note-type (s16=171) + click (s9=105) + bell + fs.
     User 2026-05-30: "in metronome kun je de click en quarter notes
     dropdowns omdraaien?" — Quarter Notes / subdiv now sits in the
     wider slot 16 ahead of Click, swapping the on-screen order. */
  body.app-shell.cat-tools .panel-tabs-toolbar:has(#met-time-sig) {
    grid-template-columns:
      [s1] 100px
      [s16] 171px
      [s9] 105px
      [d] 30px
      [f] 30px [end] !important;
  }
  body.app-shell.cat-tools .panel-tabs-toolbar > .tuner-controls,
  body.app-shell.cat-tools .panel-tabs-toolbar > .met-controls,
  body.app-shell.cat-tools .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-tools .panel-tabs-toolbar > .tuner-sliders,
  body.app-shell.cat-tools .panel-tabs-toolbar .gt-select-wrap,
  body.app-shell.cat-tools .panel-tabs-toolbar .tuner-control,
  body.app-shell.cat-tools .panel-tabs-toolbar .met-control {
    display: contents !important;
  }
  /* A4 + mic-sensitivity sliders moved out of the toolbar entirely
     2026-05-30 — they now live inside .tuner-stage (HTML + tuner.css).
     panel-tabs.js no longer moves .tuner-sliders into the toolbar slot. */
  /* FS button — override legacy margin-left: 8px. */
  body.app-shell.cat-tools .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  body.app-shell.cat-tools .panel-tabs-toolbar select,
  body.app-shell.cat-tools .panel-tabs-toolbar input {
    width: 100% !important;
    min-width: 0 !important;
    flex: initial !important;
  }
}

/* ============================================================
   LEARN — grid-column placements (unconditional)
   ----------------------------------------------------------
   Placements apply at every viewport where the toolbar is
   `display: grid` — both the desktop band (@media ≥1201) and
   the narrow band (@media 601-1500). At display:flex they're
   inert. Moved out of @media because @media (min-width: 1201)
   would otherwise leave items auto-placed at 1101-1200.
   ============================================================ */
/* CoF: root (slot 1) at s1, scale (slot 2 = s3+gap+s4) spans s3/d. */
body.app-shell.cat-learn .panel-tabs-toolbar #cof-root-select { grid-column: s1 / s3; }
body.app-shell.cat-learn .panel-tabs-toolbar #cof-scale-select { grid-column: s3 / d; }
/* Fretboard: instrument (s1) + root (s3) + scale (s4). */
body.app-shell.cat-learn .panel-tabs-toolbar #ft-instrument { grid-column: s1 / s3; }
body.app-shell.cat-learn .panel-tabs-toolbar #ft-root { grid-column: s3 / s4; }
body.app-shell.cat-learn .panel-tabs-toolbar #ft-category { grid-column: s4 / d; }
/* Library Chords. */
body.app-shell.cat-learn .panel-tabs-toolbar #cl-lib-type { grid-column: s1 / s3; }
body.app-shell.cat-learn .panel-tabs-toolbar #cl-root { grid-column: s3 / s4; }
body.app-shell.cat-learn .panel-tabs-toolbar #cl-type { grid-column: s4 / d; }
/* Library Scales (ls-*). */
body.app-shell.cat-learn .panel-tabs-toolbar #ls-lib-type { grid-column: s1 / s3; }
body.app-shell.cat-learn .panel-tabs-toolbar #ls-root { grid-column: s3 / s4; }
body.app-shell.cat-learn .panel-tabs-toolbar #ls-scale { grid-column: s4 / d; }
/* Library Modes (lm-*). */
body.app-shell.cat-learn .panel-tabs-toolbar #lm-lib-type { grid-column: s1 / s3; }
body.app-shell.cat-learn .panel-tabs-toolbar #lm-root { grid-column: s3 / s4; }
body.app-shell.cat-learn .panel-tabs-toolbar #lm-scale { grid-column: s4 / d; }
/* Analysis: instrument (s1). find-type CF mode spans s3 / f. */
body.app-shell.cat-learn .panel-tabs-toolbar #find-instrument { grid-column: s1 / s3; }
body.app-shell.cat-learn .panel-tabs-toolbar #find-type { grid-column: s3 / f; }
body.app-shell.cat-learn .panel-tabs-toolbar #kf-input-mode { grid-column: d / f; }
/* PF mode key dropdowns — only when their wrapper has .visible. */
body.app-shell.cat-learn .panel-tabs-toolbar .find-key-wrap.visible #find-key-root { grid-column: s3 / s4; }
body.app-shell.cat-learn .panel-tabs-toolbar .find-key-wrap.visible #find-key-scale { grid-column: s4 / d; }
/* Glossary: search spans s1 / d (= w1 + 8 + w2 = 392 desktop). */
body.app-shell.cat-learn .panel-tabs-toolbar #glossary-search { grid-column: s1 / d; }
/* Dice + random + FS — fixed end columns. */
body.app-shell.cat-learn .panel-tabs-toolbar #cof-dice,
body.app-shell.cat-learn .panel-tabs-toolbar #ft-dice,
body.app-shell.cat-learn .panel-tabs-toolbar #cl-dice,
body.app-shell.cat-learn .panel-tabs-toolbar #ls-dice,
body.app-shell.cat-learn .panel-tabs-toolbar #lm-dice,
body.app-shell.cat-learn .panel-tabs-toolbar #glossary-random-btn { grid-column: d / f; }
body.app-shell.cat-learn .panel-tabs-toolbar .panel-tabs-fs-btn { grid-column: f / end; }
/* KF mode (Analysis Key Finder): when #kf-input-mode-wrap inline
   style doesn't contain "none" (= analysis.js cleared display:none),
   re-position find-instrument / find-type / kf-input-mode at the
   KF template's column lines (t, kf). Unconditional so it applies
   at both desktop and narrow bands. */
body.app-shell.cat-learn .panel-tabs-toolbar #kf-input-mode-wrap:not([style*="none"]) {
  display: contents !important;
}
body.app-shell.cat-learn .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) #find-instrument {
  grid-column: s1 / t;
}
body.app-shell.cat-learn .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) #find-type {
  grid-column: t / kf;
}
body.app-shell.cat-learn .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) #kf-input-mode {
  grid-column: kf / f;
}

/* ============================================================
   PRACTICE — grid-column placements (unconditional)
   ----------------------------------------------------------
   Per-tool placements apply at every viewport where the toolbar
   is `display: grid` — both desktop (≥1201) and narrow band
   (≤1500). At display:flex (mobile fallback) the rules are inert.
   Same pattern as LEARN above.

   The grid template differs per tool (via `:has()` selectors in
   the main blocks), so each tool's items reference column lines
   that ONLY exist in their own template — references to missing
   lines from other templates are inert.
   ============================================================ */
/* Harmony trainer: key (s1) + scale (s7) + audio + hint + dice + fs */
body.app-shell.cat-practice .panel-tabs-toolbar #gt-key { grid-column: s1 / s7; }
body.app-shell.cat-practice .panel-tabs-toolbar #gt-scale { grid-column: s7 / a1; }
body.app-shell.cat-practice .panel-tabs-toolbar #ph-audio { grid-column: a1 / a2; }
body.app-shell.cat-practice .panel-tabs-toolbar #ph-hint { grid-column: a2 / d; }
body.app-shell.cat-practice .panel-tabs-toolbar #ph-dice { grid-column: d / f; }
/* Ear training — non-HF mode: level (s1) + exercise (s8) + fs */
body.app-shell.cat-practice .panel-tabs-toolbar #et-difficulty-select { grid-column: s1 / s8; }
body.app-shell.cat-practice .panel-tabs-toolbar #et-exercise-select { grid-column: s8 / f; }
/* Ear training — HF mode override: level (s1) + fn-cat (s16=171) + exercise (s10=143) + fs
   Higher specificity (:has()) wins over the non-HF rules above. Slot 9
   retired here per user 2026-05-30 — dropdown text "Scales"/"Chords" /
   "Identification"/"Construction" needs the equal-pair slot to not truncate.
   Order swap 2026-05-30: HF type (et-exercise-select) sits LEFT, scales-vs-
   chords (fn-cat-select) sits RIGHT per user "zet de scales/modes dropdown
   RECHTS van harmonic function dropdown ipv links zoals het nu staat." */
body.app-shell.cat-practice .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])) #et-difficulty-select {
  grid-column: s1 / s16;
}
body.app-shell.cat-practice .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])) #et-exercise-select {
  grid-column: s16 / s10;
}
body.app-shell.cat-practice .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])) #fn-cat-select {
  grid-column: s10 / f;
}
/* Sight reading: level (s1) + section (s16=171) + exercise (s10=143) + fs */
body.app-shell.cat-practice .panel-tabs-toolbar #sp-difficulty-select { grid-column: s1 / s16; }
body.app-shell.cat-practice .panel-tabs-toolbar #sp-section-select { grid-column: s16 / s10; }
body.app-shell.cat-practice .panel-tabs-toolbar #sp-exercise-select { grid-column: s10 / f; }
/* FS always rightmost */
body.app-shell.cat-practice .panel-tabs-toolbar .panel-tabs-fs-btn { grid-column: f / end; }

/* ============================================================
   PLAY — grid-column placements (unconditional)
   ============================================================ */
/* Backing tracks: key (s1) + scale (s12) + time-sig (s13) + dice + fs */
body.app-shell.cat-play .panel-tabs-toolbar #bt-key { grid-column: s1 / s12; }
body.app-shell.cat-play .panel-tabs-toolbar #bt-scale { grid-column: s12 / s13; }
body.app-shell.cat-play .panel-tabs-toolbar #bt-time-sig { grid-column: s13 / d; }
body.app-shell.cat-play .panel-tabs-toolbar #bt-randomise { grid-column: d / f; }
/* Jazz browse: search (s5) + random (d) + fs */
body.app-shell.cat-play .panel-tabs-toolbar #jz-search { grid-column: s5 / d; }
body.app-shell.cat-play .panel-tabs-toolbar #jz-random-btn { grid-column: d / f; }
/* Jazz song view: instrument (s1) + voicing (s8) + fs */
body.app-shell.cat-play .panel-tabs-toolbar #jz-instrument { grid-column: s1 / s8; }
body.app-shell.cat-play .panel-tabs-toolbar #jz-voicing { grid-column: s8 / f; }
/* Looper: time-sig (s1) + bars (s8) + fs */
body.app-shell.cat-play .panel-tabs-toolbar #lp-time-sig { grid-column: s1 / s8; }
body.app-shell.cat-play .panel-tabs-toolbar #lp-loop-len { grid-column: s8 / f; }
/* FS always rightmost */
body.app-shell.cat-play .panel-tabs-toolbar .panel-tabs-fs-btn { grid-column: f / end; }

/* ============================================================
   TOOLS — grid-column placements (unconditional)
   ============================================================ */
/* Tuner: instrument (s15a) + tuning (s15b) + fs */
body.app-shell.cat-tools .panel-tabs-toolbar #tuner-instrument { grid-column: s15a / s15b; }
body.app-shell.cat-tools .panel-tabs-toolbar #tuner-tuning { grid-column: s15b / f; }
/* Metronome: time-sig (s1) + subdiv/Quarter Notes (s16) + sound/Click (s9) + bell (d) + fs.
   Grid column order is [s1] [s16] [s9] [d] [f] so visually subdiv
   comes BEFORE click — swap per user 2026-05-30. */
body.app-shell.cat-tools .panel-tabs-toolbar #met-time-sig { grid-column: s1 / s16; }
body.app-shell.cat-tools .panel-tabs-toolbar #met-subdiv { grid-column: s16 / s9; }
body.app-shell.cat-tools .panel-tabs-toolbar #met-sound { grid-column: s9 / d; }
body.app-shell.cat-tools .panel-tabs-toolbar #met-bell { grid-column: d / f; }
/* FS always rightmost */
body.app-shell.cat-tools .panel-tabs-toolbar .panel-tabs-fs-btn { grid-column: f / end; }

/* ============================================================
   LEARN — scaled grid for narrow desktop viewports + mobile
   ----------------------------------------------------------
   At browser zoom > 125% (effective viewport < 1500px), the
   desktop grid kept the toolbar at a fixed 480px sitting inside
   the 1100 panel cap — leaving large empty bar margins around.
   User 2026-05-30: "bij 150% covert hij niet de 100% page
   width. hij moet van edge tot edge lopen (moet wel margin
   houden links en rechts net als in menu 1). houd de
   verhoudingen EXACT gelijk zodat er niks verspringt. PAS
   NIKS AAN IN VIEWPORTS 125% EN DAARONDER!!!!!! geldt alleen
   voor viewport groter dan 125% en op mobile enzo."

   Three changes in this band (vw 601-1500, LEARN only):
   1. Panels widen from the global 1100 max-width to
      `vw - 28` (= 14px margin each side, matching what menu 1
      already does at ≤1100). Only fires when the panel would
      otherwise be capped (1101-1500); at ≤1100 it's already
      filling viewport naturally.
   2. Toolbar grid switches from fixed-px columns to
      `minmax(0, Nfr)` units that preserve every slot's RATIO
      so all elements scale horizontally in the same proportion
      (100 : 60 : 216 : 30 : 30 stays exact). `display: grid` is
      forced because the existing `@media (max-width: 1200px)`
      flex fallback would otherwise win in the 1101-1200 band.
   3. Toolbar `flex: 1 1 auto` lets it grow to fill the bar.

   At vw ≥ 1501 ("125% en daaronder is PERFECT") and at true
   mobile (vw ≤ 600) nothing changes — the existing flex
   fallback handles mobile.
   ============================================================ */
/* ============================================================
   Panel widening for narrow viewports — RETIRED 2026-05-30
   ----------------------------------------------------------
   Earlier this block widened panels to `calc(100vw - 28px)` at
   vw 1101-1500 so the bar could run edge-to-edge at "150% zoom on
   1920" (= vw=1280). Together with the @media (max-width: 1500)
   narrow grid block, it caused a discontinuous divider-position
   JUMP at the vw=1500/1501 boundary: panel suddenly widens from
   1100 → 1472 and toolbar flips from 480 fixed to flex-grow, so
   the divider shifts ~50-200px to the right.

   On a 1650-1700 monitor that boundary falls at 110% zoom — the
   user reported "als ik inzoom van 100 naar 110% viewport
   verspringt soms de divider in menu2, gebeurt sowieso in tools en
   in practice." Removing the widening (panel stays at the
   panel-layout.css default 1100 max-width across the desktop zoom
   range) means the divider position only shifts gradually as the
   centred panel's right edge moves with viewport — no boundary
   jump.

   Trade-off: at 150% zoom on 1920 (= vw=1280) the panel is now
   1100 centred with 90px margin each side, no longer edge-to-edge.
   If you want that back, use a smooth `clamp(1100px, calc(...vw),
   <max>)` on max-width instead of a binary @media switch.
   ============================================================ */
/* ============================================================
   LEARN — narrow viewport toolbar (vw ≤ 1500)
   ----------------------------------------------------------
   User 2026-05-30 follow-up: "YAY PERFECT op mobile portrait
   mode. maar als ik mobiel draai naar landscape mode
   (horizontal). dan is het fout!! eigenlijk het gedrag van
   viewport 150 tot 300. vanaf 300 is hij perfect!!!"

   Symptom at landscape mobile (~844vw): the prior 601-1500
   scaled-grid block used `minmax(0, Nfr)` for EVERY slot —
   including dice and FS — so icons grew from 30→~50px and the
   slot-1 dropdown ballooned past its 100px target. Per the
   earlier turn's constraint ("icons 30 vast"), icons must stay
   square 30×30 at every narrow viewport, not just at vw≤600.

   Fix: extend the mobile pattern to cover the full vw≤1500
   band. Slots 1 (root/instrument, 100px), 3 (root, 60px), and
   6 (icons d/f, 30px each) lock at desktop pixel widths.
   Slot 4 (scale/type) flexes via `minmax(0, 1fr)` to fill
   whatever space remains. Slots 2 and 5 are SPANS built on
   slot 4 (s2 = s3 + 8 + s4; s5 = s1 + 8 + s2), so they
   auto-stretch/shrink with slot 4.

   Width math at typical viewports:
   - vw=390 (portrait): bar ~362, s4 ≈ 100
   - vw=844 (landscape): bar ~800, s4 ≈ 540
   - vw=1100 (just below the new boundary): bar full-width, s4 fills

   Threshold changed 2026-05-30 from `(max-width: 1500)` to
   `(max-width: 1100)` to eliminate the divider-position JUMP at
   the boundary. On a ~1650 monitor, 110% zoom = vw=1500. With the
   new 1200 threshold (bumped 2026-05-30 from 1100 to align with the
   menu-2 flex-column breakpoint per user "wanneer menu responsive
   wordt, content ook responsive"), the boundary maps to: 1366=132% /
   1440=125% / 1650=137% / 1920=160%. At vw=1201-1500 the desktop
   fixed-480 grid stays in effect (from @media min-width: 1201) so
   the divider position is continuous across the desktop zoom range.
   At vw=1200 the bar flips to flex-column (no divider visible anyway)
   AND the toolbar narrow grid fires — both at the same breakpoint so
   there's no awkward transition zone where the bar is column-mode
   but the toolbar still acts as desktop-row-grid.
   ============================================================ */
@media (max-width: 1200px) {
  /* LEARN narrow grid: 1fr moved to s1 (root/instrument/type — the
     dropdown the user wants widened) and the [f] column dropped
     entirely so the dice (at [d]) ends at the toolbar's right edge
     = FS button's former position. User 2026-05-31: "voor COF /
     fretboard tool / library / analysis / glossary: maak [root/
     instrument/type/search] dropdown 30+8px breder. ... ik zie dice
     nog steeds niet op de juiste plek. er staat nog steeds een gap
     waar FS stond. wss is er iets fixed in de code."

     Was: [s1] 100  [s3] 60  [s4] 1fr  [d] 30  [f] 30  [end]
     Now: [s1] 1fr  [s3] 60  [s4] 216  [d] 30  [end]

     Items previously at `d / f` or `s3 / f` need their right line
     remapped to `end` — overrides below in this @1200 block. The
     FS button placement (`f / end`) becomes invalid but FS is
     `display: none` at this breakpoint so it doesn't matter. */
  body.app-shell.cat-learn .panel-tabs-toolbar {
    display: grid !important;
    flex: 1 1 auto !important;
    max-width: none !important;
    grid-template-columns:
      [s1] minmax(0, 1fr)
      [s3] 60px
      [s4] 216px
      [d] 30px [end] !important;
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  /* KF mode (Analysis Key Finder): instrument grows, type + input-mode
     fixed at 130px each. User 2026-05-31: "analysis key finder: maak
     instrument dropdown 30+8px breder." */
  body.app-shell.cat-learn .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) {
    grid-template-columns:
      [s1] minmax(0, 1fr)
      [t] 130px
      [kf] 130px [end] !important;
  }
  /* Remap placements that used `f` as a column boundary — now that
     f is dropped, those items extend to `end` instead. */
  body.app-shell.cat-learn .panel-tabs-toolbar #cof-dice,
  body.app-shell.cat-learn .panel-tabs-toolbar #ft-dice,
  body.app-shell.cat-learn .panel-tabs-toolbar #cl-dice,
  body.app-shell.cat-learn .panel-tabs-toolbar #ls-dice,
  body.app-shell.cat-learn .panel-tabs-toolbar #lm-dice,
  body.app-shell.cat-learn .panel-tabs-toolbar #glossary-random-btn {
    grid-column: d / end !important;
  }
  body.app-shell.cat-learn .panel-tabs-toolbar #find-type {
    grid-column: s3 / end !important;
  }
  body.app-shell.cat-learn .panel-tabs-toolbar #glossary-search {
    grid-column: s1 / d !important;
  }
  body.app-shell.cat-learn .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) #kf-input-mode {
    grid-column: kf / end !important;
  }
  /* Same display:contents + width overrides + FS margin reset as
     the 601-1500 block. */
  body.app-shell.cat-learn .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .ft-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .cl-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .ls-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .find-controls,
  body.app-shell.cat-learn .panel-tabs-toolbar > .lib-glossary-search-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .gt-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .ft-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .cl-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .ls-select-wrap,
  body.app-shell.cat-learn .panel-tabs-toolbar .find-select-wrap:not(.find-key-wrap):not(#kf-input-mode-wrap) {
    display: contents !important;
  }
  body.app-shell.cat-learn .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  body.app-shell.cat-learn .panel-tabs-toolbar #cof-root-select,
  body.app-shell.cat-learn .panel-tabs-toolbar #ft-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #ft-instrument,
  body.app-shell.cat-learn .panel-tabs-toolbar #cl-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #cl-lib-type,
  body.app-shell.cat-learn .panel-tabs-toolbar #ls-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #ls-lib-type,
  body.app-shell.cat-learn .panel-tabs-toolbar #lm-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #lm-lib-type,
  body.app-shell.cat-learn .panel-tabs-toolbar #find-instrument,
  body.app-shell.cat-learn .panel-tabs-toolbar #find-key-root,
  body.app-shell.cat-learn .panel-tabs-toolbar #find-type,
  body.app-shell.cat-learn .panel-tabs-toolbar #kf-input-mode,
  body.app-shell.cat-learn .panel-tabs-toolbar #cof-scale-select,
  body.app-shell.cat-learn .panel-tabs-toolbar #ft-category,
  body.app-shell.cat-learn .panel-tabs-toolbar #cl-type,
  body.app-shell.cat-learn .panel-tabs-toolbar #ls-scale,
  body.app-shell.cat-learn .panel-tabs-toolbar #lm-scale,
  body.app-shell.cat-learn .panel-tabs-toolbar #find-key-scale,
  body.app-shell.cat-learn .panel-tabs-toolbar #glossary-search {
    width: 100% !important;
    flex: initial !important;
    min-width: 0 !important;
  }

  /* ============================================================
     PRACTICE — narrow viewport toolbar (vw ≤ 1500)
     Same pattern as LEARN: slot 1 (level/key, 100px) and icons (30px)
     lock at desktop widths; the wider middle slot (slot 7 for
     harmony, slot 8 for ET intervals, slot 10 for HF/SR) flexes via
     `minmax(0, 1fr)` to fill remaining bar width. Slot 9 (105px) for
     HF/SR keeps its fixed width — only the rightmost dropdown grows.
     ============================================================ */
  /* PRACTICE narrow grids — drop [f] column entirely (FS hidden,
     rightmost item ends at FS's former position). 1fr stays on the
     existing flexible column (user didn't specify a target dropdown
     for practice tools so the existing distribution is preserved). */
  body.app-shell.cat-practice .panel-tabs-toolbar {
    display: grid !important;
    flex: 1 1 auto !important;
    max-width: none !important;
    grid-template-columns:
      [s1] 100px
      [s7] minmax(0, 1fr)
      [a1] 30px
      [a2] 30px
      [d] 30px [end] !important;
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  /* ET non-HF: level + exercise (flex). */
  body.app-shell.cat-practice .panel-tabs-toolbar:has(#et-difficulty-select):not(:has(.et-fn-ctrl:not([style*="none"]))) {
    grid-template-columns:
      [s1] 100px
      [s8] minmax(0, 1fr) [end] !important;
  }
  /* ET HF / SR: equal-pair slots = both 1fr. */
  body.app-shell.cat-practice .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])),
  body.app-shell.cat-practice .panel-tabs-toolbar:has(#sp-difficulty-select) {
    grid-template-columns:
      [s1] 100px
      [s16] minmax(0, 1fr)
      [s10] minmax(0, 1fr) [end] !important;
  }
  /* Remap PRACTICE placements that used `f` as a column boundary. */
  body.app-shell.cat-practice .panel-tabs-toolbar #ph-dice {
    grid-column: d / end !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar #et-exercise-select {
    grid-column: s8 / end !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar #sp-exercise-select {
    grid-column: s10 / end !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar > .ph-controls-row,
  body.app-shell.cat-practice .panel-tabs-toolbar > .ph-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar > .et-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar > .sp-controls,
  body.app-shell.cat-practice .panel-tabs-toolbar .ph-select-wrap:not(.et-fn-ctrl),
  body.app-shell.cat-practice .panel-tabs-toolbar .gt-select-wrap {
    display: contents !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar .et-fn-ctrl:not([style*="none"]) {
    display: contents !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  body.app-shell.cat-practice .panel-tabs-toolbar #gt-key,
  body.app-shell.cat-practice .panel-tabs-toolbar #gt-scale,
  body.app-shell.cat-practice .panel-tabs-toolbar #et-difficulty-select,
  body.app-shell.cat-practice .panel-tabs-toolbar #fn-cat-select,
  body.app-shell.cat-practice .panel-tabs-toolbar #et-exercise-select,
  body.app-shell.cat-practice .panel-tabs-toolbar #sp-difficulty-select,
  body.app-shell.cat-practice .panel-tabs-toolbar #sp-section-select,
  body.app-shell.cat-practice .panel-tabs-toolbar #sp-exercise-select {
    width: 100% !important;
    flex: initial !important;
    min-width: 0 !important;
  }

  /* ============================================================
     PLAY — narrow viewport toolbar (vw ≤ 1500)
     ============================================================ */
  /* PLAY narrow grids — 1fr moved to the user-targeted dropdown per
     tool (BT root, Jazz search, Jazz instrument, Looper time-sig),
     [f] column dropped entirely so rightmost icon (dice / random)
     sits at FS's former position. */

  /* BT: root (s1) grows, scale fixed at 200, time-sig fixed at 76. */
  body.app-shell.cat-play .panel-tabs-toolbar {
    display: grid !important;
    flex: 1 1 auto !important;
    max-width: none !important;
    grid-template-columns:
      [s1] minmax(0, 1fr)
      [s12] 200px
      [s13] 76px
      [d] 30px [end] !important;
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  /* Jazz browse: search (s5) already grows — just drop f. */
  body.app-shell.cat-play .panel-tabs-toolbar:has(#jz-search) {
    grid-template-columns:
      [s5] minmax(0, 1fr)
      [d] 30px [end] !important;
  }
  /* Jazz song view: instrument (s1) grows, voicing fixed at 322. */
  body.app-shell.cat-play .panel-tabs-toolbar:has(#jz-instrument) {
    grid-template-columns:
      [s1] minmax(0, 1fr)
      [s8] 322px [end] !important;
  }
  /* Looper: time-sig (s1) grows, bars fixed at 322. */
  body.app-shell.cat-play .panel-tabs-toolbar:has(#lp-time-sig) {
    grid-template-columns:
      [s1] minmax(0, 1fr)
      [s8] 322px [end] !important;
  }
  /* Remap PLAY placements that used `f` boundary → `end`. */
  body.app-shell.cat-play .panel-tabs-toolbar #bt-randomise,
  body.app-shell.cat-play .panel-tabs-toolbar #jz-random-btn {
    grid-column: d / end !important;
  }
  body.app-shell.cat-play .panel-tabs-toolbar #jz-voicing,
  body.app-shell.cat-play .panel-tabs-toolbar #lp-loop-len {
    grid-column: s8 / end !important;
  }
  body.app-shell.cat-play .panel-tabs-toolbar > .lp-toolbar-row,
  body.app-shell.cat-play .panel-tabs-toolbar > .bt-toolbar-row,
  body.app-shell.cat-play .panel-tabs-toolbar > .lp-controls,
  body.app-shell.cat-play .panel-tabs-toolbar > .bt-controls,
  body.app-shell.cat-play .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-search-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row > .jz-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row > .jz-ctrl-group,
  body.app-shell.cat-play .panel-tabs-toolbar > .jz-settings-row > .jz-ctrl-group > .jz-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar .lp-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar .bt-select-wrap,
  body.app-shell.cat-play .panel-tabs-toolbar .gt-select-wrap {
    display: contents !important;
  }
  body.app-shell.cat-play .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  body.app-shell.cat-play .panel-tabs-toolbar #bt-key,
  body.app-shell.cat-play .panel-tabs-toolbar #bt-scale,
  body.app-shell.cat-play .panel-tabs-toolbar #bt-time-sig,
  body.app-shell.cat-play .panel-tabs-toolbar #jz-search,
  body.app-shell.cat-play .panel-tabs-toolbar #jz-instrument,
  body.app-shell.cat-play .panel-tabs-toolbar #jz-voicing,
  body.app-shell.cat-play .panel-tabs-toolbar #lp-time-sig,
  body.app-shell.cat-play .panel-tabs-toolbar #lp-loop-len {
    width: 100% !important;
    flex: initial !important;
    min-width: 0 !important;
  }

  /* ============================================================
     TOOLS — narrow viewport toolbar (vw ≤ 1500)
     ============================================================ */
  /* TOOLS narrow grids — both targeted dropdowns get the freed FS
     slot space.
     Tuner: BOTH dropdowns equal width (per user spec) — both already
     1fr. Just drop f column.
     Metronome: click dropdown (s9) grows, note-type (s16) fixed at
     171px (matching desktop), drop f. User 2026-05-31: "voor
     metronome: maak click dropdown breder (30+8px breder). bell icon
     verschuift dan naar plek waar FS eerst stond." */
  body.app-shell.cat-tools .panel-tabs-toolbar {
    display: grid !important;
    flex: 1 1 auto !important;
    max-width: none !important;
    grid-template-columns:
      [s15a] minmax(0, 1fr)
      [s15b] minmax(0, 1fr) [end] !important;
    grid-auto-flow: dense !important;
    grid-template-rows: 30px !important;
    gap: 8px !important;
    align-items: center !important;
    justify-content: start !important;
  }
  /* Metronome: click (s9) grows, note-type fixed. */
  body.app-shell.cat-tools .panel-tabs-toolbar:has(#met-time-sig) {
    grid-template-columns:
      [s1] 100px
      [s16] 171px
      [s9] minmax(0, 1fr)
      [d] 30px [end] !important;
  }
  /* Remap TOOLS placements: tuner-tuning + met-bell that used `f` as
     a column boundary. */
  body.app-shell.cat-tools .panel-tabs-toolbar #tuner-tuning {
    grid-column: s15b / end !important;
  }
  body.app-shell.cat-tools .panel-tabs-toolbar #met-bell {
    grid-column: d / end !important;
  }
  body.app-shell.cat-tools .panel-tabs-toolbar > .tuner-controls,
  body.app-shell.cat-tools .panel-tabs-toolbar > .met-controls,
  body.app-shell.cat-tools .panel-tabs-toolbar > .cof-controls,
  body.app-shell.cat-tools .panel-tabs-toolbar > .tuner-sliders,
  body.app-shell.cat-tools .panel-tabs-toolbar .gt-select-wrap,
  body.app-shell.cat-tools .panel-tabs-toolbar .tuner-control,
  body.app-shell.cat-tools .panel-tabs-toolbar .met-control {
    display: contents !important;
  }
  /* .tuner-slider-label moved out of toolbar entirely 2026-05-30. */
  body.app-shell.cat-tools .panel-tabs-toolbar > .panel-tabs-fs-btn {
    margin-left: 0 !important;
  }
  body.app-shell.cat-tools .panel-tabs-toolbar #tuner-instrument,
  body.app-shell.cat-tools .panel-tabs-toolbar #tuner-tuning,
  body.app-shell.cat-tools .panel-tabs-toolbar #met-time-sig,
  body.app-shell.cat-tools .panel-tabs-toolbar #met-sound,
  body.app-shell.cat-tools .panel-tabs-toolbar #met-subdiv {
    width: 100% !important;
    flex: initial !important;
    min-width: 0 !important;
  }

  /* ===== UNIFORM EQUAL SCALING — overrides per-category grid =====
     User 2026-05-31 follow-up: "als ik puur naar de toolbar kijk dan
     schaalt alleen de meest linker dropdown kleiner. eg in library
     blijven bij tablet en bij mobile viewport de scales/chords/modes
     dropdown en de root dropdown exact even breed. ipv dat alleen de
     meest linker dropdown schaalt, kun je op elke pagina voor elke
     toolbar ze allemaal evenveel laten schalen?"

     The per-category grid templates above had ONE column at minmax(0,
     1fr) and the others at fixed widths — so only the 1fr column
     scaled when the viewport shrank. The other dropdowns stayed at
     their fixed widths.

     This block REPLACES the grid layout with a uniform FLEX layout
     at @1200. Every select / input gets `flex: 1 1 0` (equal share
     of leftover space), buttons stay at fixed 30×30. The wrappers
     are flattened via `display: contents` so the selects + buttons
     are direct flex items of the toolbar.

     As the viewport shrinks, ALL dropdowns shrink proportionally
     together — no single dropdown dominates the width. */

  /* (Bar padding-zero rule moved out of this @1200 block — it was
     dropping bar padding on MOBILE too, which the user explicitly
     said was wrong. The replacement rule below targets tablet only
     (601-1200) so mobile keeps its 14px bar padding. User 2026-05-31
     correction: "je hebt op mobile de margin links en rechts van de
     toolbar menu 2 weggehaald. dat is niet goed dus revert dat
     stukje en pas het opnieuw aan ALLEEN op tablet viewport.") */

  /* Force flex layout (overrides any earlier `display: grid` from
     the per-category templates). */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    gap: 8px !important;
    align-items: center !important;
    width: 100% !important;
    /* Nullify grid props — inert under flex but cleaner if a future
       refactor flips display back to grid. */
    grid-template-columns: none !important;
    grid-template-rows: none !important;
  }

  /* Flatten ONLY the OUTER tool wrapper (.cof-controls / .find-controls
     / etc) — its children (the select wraps + dice buttons) become
     direct flex items of the toolbar.
     CRITICAL: the SELECT WRAPS themselves are NOT flattened (kept as
     real flex items, not display: contents). This preserves their
     `position: relative` so the root-display overlay (absolute span
     inside .gt-select-wrap / .ft-select-wrap etc) renders ABOVE the
     correct select.
     User 2026-05-31 SECOND PASS: previous fix flattened the wraps via
     display: contents, breaking the overlay's positioning context.
     The overlay span fell back to the panel-tabs-bar (position:
     relative) and rendered at its top-left, overlapping the leftmost
     dropdown. Concrete bug: library modes view showed "MModes" on
     leftmost dropdown (overlay text "M..." stacked on lm-lib-type's
     "Modes" text) and the lm-root dropdown rendered blank. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .cof-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .ft-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .cl-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .ls-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .find-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .lib-glossary-search-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .ph-controls-row,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .et-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .sp-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .bt-toolbar-row,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .lp-toolbar-row,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .jz-search-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .jz-settings-row,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .jz-settings-row > .jz-ctrl-group,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .tuner-controls,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > .met-controls {
    display: contents !important;
  }

  /* Select wraps STAY as flex items — equal flex-grow share. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .gt-select-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ft-select-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .cl-select-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ls-select-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .find-select-wrap:not(.find-key-wrap):not(#kf-input-mode-wrap),
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .find-key-wrap.visible,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar #kf-input-mode-wrap:not([style*="none"]),
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .bt-select-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .lp-select-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .jz-select-wrap,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-select-wrap:not(.et-fn-ctrl),
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .et-fn-ctrl:not([style*="none"]) {
    display: flex !important;
    flex: 1 1 0 !important;
    min-width: 0 !important;
    position: relative !important;
    align-items: stretch !important;
    width: auto !important;
    height: 30px !important;
  }
  /* Hidden wraps stay display: none. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .find-key-wrap:not(.visible) {
    display: none !important;
  }

  /* Selects + inputs INSIDE wraps fill their wrap. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar select,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar input[type="text"],
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar input[type="search"] {
    width: 100% !important;
    min-width: 0 !important;
    flex: 1 1 auto !important;
  }

  /* Bar padding handled by a separate @media (601-1200) block AFTER
     this @1200 block so mobile inherits the base 14px. See the rule
     "Tablet-only bar padding zero" further below. */

  /* ===== Per-mode flex-weight overrides =====
     User 2026-05-31 second pass: in some sub-tool modes the user
     wants ONE specific dropdown to keep its narrower-mode width
     instead of shrinking when a new dropdown appears. The others
     share the remaining space equally.

     Analysis KF instrument/type/input sizing now lives in the Analysis
     block further below (instrument pinned to the library-matching
     calc((100vw-82px)/3); find-type "Key Finder" + kf-input-mode split
     the rest equally). That supersedes the old flex-grow: 2 rule that
     used to live here (it made the instrument 1/2 the toolbar — too
     wide — and left find-type "veel te smal"). */

  /* Ear Training Harmonic Function mode: same shape — when HF is
     selected, .et-fn-ctrl (containing #fn-cat-select) becomes
     visible as a 3rd dropdown on the right. Difficulty keeps its
     CF-mode width (= 1/2 toolbar); exercise narrows + fn-cat
     equal-width share the other 1/2. User 2026-05-31 second pass:
     "bij harmonic function moet er RECHTS een dropdown bij komen
     voor scales/modes. die staat nu links er van. en de rest
     moet niet verspringen, alleen de harmonic function moet
     minder breed worden." */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])) #et-diff-wrap {
    flex-grow: 2 !important;
  }

  /* ===== Per-tool dropdown width ratios — TABLET + MOBILE ONLY =====
     User 2026-06-02: the root/key dropdowns are too wide on tablet/
     mobile. Make root ~half a normal dropdown ("X" ≈ 114px @vw768) and
     hand the freed width to the scale/adjacent dropdown — total toolbar
     width unchanged (flex fills → no gaps). PROPORTIONAL: flex-grow with
     flex-basis 0 (from the .*-select-wrap rule above) so every dropdown
     scales together as the viewport widens; icons stay 30px; the 8px
     item gaps + 14px outer bar padding are unchanged. Desktop (≥1201) is
     a SEPARATE grid block and is NOT touched. Ratios chosen so X ≈ 114
     and a normal dropdown ≈ 229 at vw≈768. */

  /* CoF — root(X):scale = 1:5 */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .gt-select-wrap:has(#cof-root-select) { flex-grow: 1 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .gt-select-wrap:has(#cof-scale-select) { flex-grow: 5 !important; }

  /* Fretboard — instrument:root(X):category = 2:1:3 */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ft-select-wrap:has(#ft-instrument) { flex-grow: 2 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ft-select-wrap:has(#ft-root) { flex-grow: 1 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ft-select-wrap:has(#ft-category) { flex-grow: 3 !important; }

  /* Library chords — type:root(X):quality = 2:1:3 */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .cl-select-wrap:has(#cl-lib-type) { flex-grow: 2 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .cl-select-wrap:has(#cl-root) { flex-grow: 1 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .cl-select-wrap:has(#cl-type) { flex-grow: 3 !important; }
  /* Library scales + modes (both use .ls-select-wrap) — type:root(X):scale = 2:1:3 */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ls-select-wrap:has(#ls-lib-type) { flex-grow: 2 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ls-select-wrap:has(#ls-root) { flex-grow: 1 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ls-select-wrap:has(#ls-scale) { flex-grow: 3 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ls-select-wrap:has(#lm-lib-type) { flex-grow: 2 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ls-select-wrap:has(#lm-root) { flex-grow: 1 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ls-select-wrap:has(#lm-scale) { flex-grow: 3 !important; }

  /* Analysis — instrument EXACTLY equal to library/fretboard's first
     dropdown at EVERY viewport. Those tools are 3 dropdowns + dice
     (proportional 2:1:3), so their first dropdown is always
     (toolbarW − dice − gaps) × 2/6 = ((100vw−28) − 30 − 24)/3 =
     (100vw−82px)/3. Pin the analysis instrument to that same calc in
     BOTH modes (no proportional drift — user 2026-06-02 "instrument in
     analysis is niet gelijk aan instrument in library").
     CF: find-type fills the rest.
     KF (measured live: instrument + find-type "Key Finder" +
     kf-input-mode — find-key-root/scale are hidden in the panel): the
     find-type ("key finder") and kf-input-mode ("chord/note input")
     dropdowns split the remaining width EXACTLY equally (flex: 1 1 0
     each). find-type was "veel te smal" because it had no KF rule. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:not(:has(#kf-input-mode-wrap:not([style*="none"]))) .find-select-wrap:has(#find-instrument) { flex: 0 0 calc((100vw - 82px) / 3) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:not(:has(#kf-input-mode-wrap:not([style*="none"]))) .find-select-wrap:has(#find-type) { flex: 1 1 0 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) .find-select-wrap:has(#find-instrument) { flex: 0 0 calc((100vw - 82px) / 3) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) .find-select-wrap:has(#find-type) { flex: 1 1 0 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:has(#kf-input-mode-wrap:not([style*="none"])) #kf-input-mode-wrap { flex: 1 1 0 !important; }

  /* Harmony Trainer — root PINNED to the CoF-root formula
     calc((100vw-74px)/6) so it's EXACTLY equal to CoF root at every
     width (was proportional 4.33, only equal at 768). Scale fills the
     rest. User 2026-06-02 "harmony root moet even breed als cof root,
     haal ruimte van scales af". */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-select-wrap:has(#gt-key) { flex: 0 0 calc((100vw - 74px) / 6) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-select-wrap:has(#gt-scale) { flex: 1 1 0 !important; }

  /* Ear Training — level PINNED to the met-sound formula
     calc((100vw-82px)/3) in BOTH modes, so it's EXACTLY equal to the
     metronome click/drums dropdown at every width AND does NOT jump
     when Harmonic Function is selected (same calc in both). User
     2026-06-02. Non-HF: exercise (intervals/notes/chords) fills the
     rest. HF: scales/modes (fn-cat) PINNED to its current width
     calc((100vw-44px)/4) so it stays exactly the same; harmonic-
     function (exercise) fills the rest (gets wider). */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:not(:has(.et-fn-ctrl:not([style*="none"]))) .ph-select-wrap:has(#et-difficulty-select) { flex: 0 0 calc((100vw - 82px) / 3) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:not(:has(.et-fn-ctrl:not([style*="none"]))) .ph-select-wrap:has(#et-exercise-select) { flex: 1 1 0 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])) #et-diff-wrap { flex: 0 0 calc((100vw - 82px) / 3) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])) .et-fn-ctrl:has(#fn-cat-select) { flex: 0 0 calc((100vw - 44px) / 4) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar:has(.et-fn-ctrl:not([style*="none"])) .ph-select-wrap:has(#et-exercise-select) { flex: 1 1 0 !important; }

  /* Backing Tracks — root PINNED to the CoF-root formula
     calc((100vw-74px)/6) so it's EXACTLY equal to CoF root at every
     width (user 2026-06-02 "BT root moet exact even breed als cof root,
     haal van scales af"). Time-sig PINNED to the metronome-time-sig
     formula calc((100vw-82px)/4) so it stays = "Y" exactly. Scale fills
     the rest (absorbs the root increase). */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .bt-select-wrap:has(#bt-key) { flex: 0 0 calc((100vw - 74px) / 6) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .bt-select-wrap:has(#bt-time-sig) { flex: 0 0 calc((100vw - 82px) / 4) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .bt-select-wrap:has(#bt-scale) { flex: 1 1 0 !important; }

  /* Metronome — time-sig : sound(click/drums) : subdivision = 1.5 : 2 :
     2.5. time-sig = "Y" (= BT time-sig, identical toolbar so exactly
     equal), the extra width taken from the eighth/quarter-notes
     (subdivision) dropdown; click/drums (sound) kept at 2 (≈229). User
     2026-06-02. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .gt-select-wrap:has(#met-time-sig) { flex-grow: 1.5 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .gt-select-wrap:has(#met-sound) { flex-grow: 2 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .gt-select-wrap:has(#met-subdiv) { flex-grow: 2.5 !important; }

  /* Sight Reading — level PINNED to the met-sound formula
     calc((100vw-82px)/3), EXACTLY equal to metronome click/drums at
     every width. Section (identification/construction) + exercise
     (note-pitch/clef/time-sig/…) are BOTH flex: 1 1 0 so they split the
     space after level EXACTLY equally at every width. User 2026-06-02
     "maak section en exercise dropdown exact even breed, pas verder
     niks aan". */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-select-wrap:has(#sp-difficulty-select) { flex: 0 0 calc((100vw - 82px) / 3) !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-select-wrap:has(#sp-section-select) { flex: 1 1 0 !important; }
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-select-wrap:has(#sp-exercise-select) { flex: 1 1 0 !important; }

  /* Buttons (dice / random / bell / audio / hint / FS) stay fixed
     30×30 — they don't share leftover. FS button is also display:none
     at @1200 via the earlier rule at line 786-790 so it doesn't
     occupy a flex slot. */
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar > button,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .lib-dice-btn,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .met-bell-btn,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-audio-btn,
  body.app-shell .panel-tabs-bar .panel-tabs-toolbar .ph-hint-btn {
    flex: 0 0 30px !important;
    width: 30px !important;
    height: 30px !important;
    margin: 0 !important;
  }
}

/* ===== Tablet-only (601-1200) layout adjustments =====
   User 2026-06-01: "favicon en signin staan nu tegen die rand aan
   maar daar moet nog een margin. moet zelfde uitgelijnd als toolbar!!"
   → restore 14px bar L/R padding so favicon + Sign In (in menu 1)
   align with the toolbar dropdowns (in menu 2) at x=14 — same as
   mobile. The earlier "drop bar padding to 0" interpretation was
   wrong: the user wanted the outer PANEL chrome gone (the 22px
   outer panel padding) but NOT the 14px bar padding.
   The toolbar's separate `padding-left: 14px` override is removed
   here because the bar padding already insets dropdowns. */
@media (min-width: 601px) and (max-width: 1200px) {
  /* Bars keep their 14px L/R padding (base @1200 rules at line 838
     for panel-shell-bar and line 1360 for panel-tabs-bar already
     set this). No override needed — REMOVED the earlier 0 override
     plus the toolbar inset override. Mobile (≤600) already inherits
     14px from those same base rules. */
}

/* ===== Menu 1 + Menu 2 titles match the dropdown ITEM size =====
   User 2026-06-01: "maak menu 1 en menu 2 title even groot als
   menu 1 en 2 dropdown items." Items are 0.78rem (.panel-tab +
   .panel-shell-categories .nav-cat), so titles drop to 0.78rem
   on responsive too. Same value as the desktop base at line ~1292
   so titles + items + tabs all read at one size across viewports.
   Selector specificity uses :not(.immersive) so it beats the
   @1200 rule at line 822 (0,3,1). */
@media (max-width: 1200px) {
  body.app-shell:not(.immersive) .panel-shell-hamburger,
  body.app-shell:not(.immersive) .panel-tabs-switcher {
    font-size: 0.78rem !important;
  }
}

/* ============================================================
   PAGE CONTENT MARGINS — TABLET + MOBILE ONLY (≤1200)
   User 2026-06-02: on tablet/mobile every panel's content must sit at
   EXACTLY 14px L/R — identical to the menu 1/2 toolbar items (favicon,
   root dropdown all sit at x=14 because the bars have 14px padding).
   Mechanism: the panels are full-bleed (L0) at ≤1200, so inset their
   content via the inner wrapper's padding (14px = the bar inset), then
   ZERO every content container that adds its OWN L/R padding (was
   16/24/32/12) so it doesn't stack on top of the 14. Desktop (≥1201)
   keeps its asymmetric 38/22 accent-stripe padding — NOT touched.
   ============================================================ */
@media (max-width: 1200px) {
  /* Uniform 14px content inset — overrides the inline padding
     panel-tabs.js copies from the (full-bleed, 0-padding) panel. */
  body.app-shell .panel-tabs-inner {
    padding-left: 14px !important;
    padding-right: 14px !important;
  }
  /* Zero the FULL-BLEED content halves' own L/R padding so the only
     inset is the inner's 14px. (cof-left is EXCLUDED — its 12px is tied
     to the wheel-canvas centring in circle-of-fifths.js.) NOT included:
     kf-unified-box / lp-click-track / settings-card — those are VISIBLE
     cards (solid bg + border); inner=14 already positions their BOX at
     14px (the page margin), and they keep their own internal padding so
     content isn't cramped against the card border. */
  body.app-shell #cof-explainer,
  body.app-shell .ft-panel,
  body.app-shell .ft-top,
  body.app-shell .cl-info-side,
  body.app-shell .cl-diagram-side,
  body.app-shell .settings-content {
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  /* Glossary grid + category pills: their box escapes the inner padding
     (negative-margin / scrollbar-gutter setup), so set 14px DIRECTLY.
     Pills stay CENTRED at every viewport (user 2026-06-02 "op mobile
     staan ze niet center" — centre everywhere); the 14px padding keeps
     wrapped pill rows off the panel edge at narrow widths. */
  body.app-shell .lib-glossary-grid,
  body.app-shell .lib-glossary-cats {
    padding-left: 14px !important;
    padding-right: 14px !important;
  }
  body.app-shell .lib-glossary-cats {
    justify-content: center !important;
  }
  /* Transport rows (Looper + Backing Tracks): span full width so the
     leftmost control (BPM) sits at 14 and the rightmost (latency/mic /
     transpose) at vw-14, instead of the desktop centred cluster. */
  body.app-shell .lp-main-transport,
  body.app-shell .bt-toolbar-transport {
    justify-content: space-between !important;
  }
  body.app-shell .lp-transport-left,
  body.app-shell .bt-toolbar-left {
    justify-content: flex-start !important;
  }
  body.app-shell .lp-transport-right,
  body.app-shell .bt-toolbar-right {
    justify-content: flex-end !important;
  }
  /* Top/bottom panel divider runs edge-to-edge (0 margin) on tablet +
     mobile. The divider IS the bottom panel's own `border-top`
     (#cof-explainer / .ft-panel / .cl-info-side). These panels sit
     inset 14px because their parent .panel-tabs-inner has 14px L/R
     padding; pulling them out with margin -14 makes the border-top
     span the full inner padding-box (0..W = screen edge), while a
     compensating padding +14 keeps their CONTENT at 14 — so the
     content position is unchanged and INTERNAL dividers (.cof-detail /
     .ft-detail / .cl-detail border-top, "Scale Information" etc.) stay
     inset at 14. -14 lands exactly on .panel-tabs-inner's padding-box
     edge, so overflow-x:hidden on the inner does NOT clip it. User
     2026-06-02: "de divider tussen top en bottom panel moet doorlopen
     tot de site rand ... geldt NIET voor eg de divider onder scale
     information ... alleen de top/bottom panel divider." */
  body.app-shell #cof-explainer,
  body.app-shell .ft-panel,
  body.app-shell .cl-info-side {
    box-sizing: border-box !important;
    width: calc(100% + 28px) !important;
    margin-left: -14px !important;
    margin-right: -14px !important;
    padding-left: 14px !important;
    padding-right: 14px !important;
  }
}
