/* ══════════════════════════════════════════════
   CHANGED 2026-09-27, per Jim: default header title/subtitle font-size
   CSS-variable fallbacks lowered to match the new JS-side defaults
   (title 175%->120%, subtitle 120%->100%) that apply when a song has
   no saved settings/fave/defaults of its own. These var(...) fallback
   values only matter for the brief instant before JS sets the actual
   custom properties (or if JS is disabled) - the real default now
   lives in song.js (titleFontSize/subtitleFontSize). The subtitle
   value also governs the artist/writer/key composite line
   (.header-subtitle), which shares the same --subtitle-font-size
   variable.
   ══════════════════════════════════════════════ */

/* ══════════════════════════════════════════════
   song.css — promoted from song-test.css (2026-09-03, per Jim), after
   the CSS-token/theme-switching work (see the styling audit) was
   built and proven out there. This is now the live file.
   ══════════════════════════════════════════════ */

/* ══════════════════════════════════════════════
   song.css — extracted from song.php's original embedded <style>
   blocks (2026-07-30, maintainability/Cloudflare-caching pass, per
   Jim). This is the external stylesheet for song.php.
   Combines, in original relative order:
     1. OpenDyslexic @font-face rules (was a small <style> block in
        <head>, right after the Google Fonts <link> tags)
     2. The main stylesheet (was the big <style> block in <head> —
        :root design tokens, layout, components, responsive rules)
     3. Settings-dropdown styles (was a SEPARATE <style> block sitting
        mid-BODY, after the settings-bar HTML — consolidated in here
        too, since a style tag's location in the DOM doesn't change
        which rules apply, only parse order, and cascade order is
        preserved by keeping this file's sections in their original
        relative order)
   NOT included here (deliberately left inline in song.php):
     - The tiny <style> block inside the early-exit "song not found"
       error page branch — separate, minor code path, not worth the
       complexity of an external file for ~25 lines only ever seen on
       an error condition.
     - The one-line `<style>:root{--paper-width:<?php echo $_pw; ?>;}
       </style>` — a genuinely per-request PHP-computed value, can't
       be static.
     - A <style> block far down in the JS, inside the chord-diagram
       download popup's own HTML template string — that's a
       DIFFERENT document (a separate popup window), not this page.

   
   ══════════════════════════════════════════════ */

/* ── OpenDyslexic @font-face (was <head>, before Google Fonts) ── */
@font-face {
  font-family: 'OpenDyslexic';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://cdn.jsdelivr.net/fontsource/fonts/opendyslexic@latest/latin-400-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'OpenDyslexic';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(https://cdn.jsdelivr.net/fontsource/fonts/opendyslexic@latest/latin-700-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'OpenDyslexic';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(https://cdn.jsdelivr.net/fontsource/fonts/opendyslexic@latest/latin-400-italic.woff2) format('woff2');
}

/* ── Self-hosted Google Fonts : replaces the fonts.googleapis.com <link>
   tags that used to be in <head> — same OFL-licensed font files
   Google Fonts itself serves (sourced via Fontsource, same as
   OpenDyslexic above), just hosted directly rather than fetched from
   a third party. Eliminates that dependency entirely instead of just
   deferring it. REQUIRES these 14 .woff2 files to actually exist in a
   fonts/ directory on the server (same level as img/) — filenames
   below, all lowercase with hyphens. ── */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/playfair-display-latin-400-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/playfair-display-latin-600-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/playfair-display-latin-400-italic.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/dm-mono-latin-400-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/dm-mono-latin-500-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(fonts/dm-sans-latin-300-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/dm-sans-latin-400-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/dm-sans-latin-500-normal.woff2) format('woff2');
}
/* ADDED (2026-09-15, per Jim: a narrow/condensed lyrics-font option
   like Arial Narrow). Roboto Condensed, self-hosted the same way as
   every other font here (Fontsource's own OFL-licensed files) —
   fonts/roboto-condensed-latin-*.woff2 need uploading alongside the
   existing font files. Both weights included so the existing
   lyricsBold toggle (--lyrics-weight: 400/700) works correctly with
   this font, same as every other lyrics-font option. */
@font-face {
  font-family: 'Roboto Condensed';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/roboto-condensed-latin-400-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Roboto Condensed';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/roboto-condensed-latin-700-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/atkinson-hyperlegible-latin-400-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/atkinson-hyperlegible-latin-700-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/atkinson-hyperlegible-latin-400-italic.woff2) format('woff2');
}
/* Comic Neue: fallback for the "Comic Sans" lyrics-font option. Comic
   Sans MS isn't installed on Android/iOS, so without this the font
   stack falls through to the generic 'cursive' keyword, which renders
   inconsistently. Comic Neue is an open, embeddable lookalike that
   renders consistently everywhere. (Comment moved here from <head>,
   where it used to sit next to the Google Fonts <link> for this.) */
@font-face {
  font-family: 'Comic Neue';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/comic-neue-latin-400-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Comic Neue';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/comic-neue-latin-700-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'Comic Neue';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/comic-neue-latin-400-italic.woff2) format('woff2');
}

/* ── Main stylesheet (was <head>'s big <style> block) ── */

  #newsModal{position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:600;
    display:flex;align-items:center;justify-content:center;padding:20px;}
  #newsModal .news-box{background:var(--surface);border-radius:14px;
    padding:24px 24px 20px;max-width:480px;width:100%;
    box-shadow:0 12px 40px rgba(0,0,0,0.25);position:relative;}
  #newsModal .news-title{font-family:'Playfair Display',serif;font-size:1.25em;
    color:var(--text);margin-bottom:12px;}
  #newsModal .news-body{font-size:14px;line-height:1.7;color:var(--text);margin-bottom:20px;}
  #newsModal .news-body a{color:var(--accent);}
  #newsModal .news-dismiss{background:var(--accent);color:var(--text-on-accent);border:none;
    padding:10px 22px;border-radius:8px;cursor:pointer;font-size:14px;
    font-weight:600;font-family:inherit;}
  #newsModal .news-dismiss:hover{opacity:0.9;}
  #newsModal .news-close{position:absolute;top:14px;right:16px;background:none;
    border:none;font-size:18px;cursor:pointer;color:var(--muted);line-height:1;}
  #newsModal .news-close:hover{color:var(--text);}

  /* Mobile A4/Letter width warning (2026-09-11, per Jim) — a small,
     dismissible banner, deliberately NOT a blocking modal like
     #newsModal above. Shows every single visit the condition is true
     — no dismiss-persistence/cookie at all, per Jim: "people forget
     and people get lazy", so a one-time-only warning would stop
     protecting someone who genuinely didn't mean to keep losing
     content on their phone. Fixed to the bottom of the viewport
     (rather than the top, where it could obscure the site banner/
     settings bar) — z-index above normal content but below
     #newsModal, so if both somehow trigger on the same visit, the
     news modal (already a deliberate, admin-authored message) still
     takes priority visually. */
  #widthWarningBanner{position:fixed;left:0;right:0;bottom:0;z-index:590;
    background:var(--surface);border-top:2px solid var(--accent);
    box-shadow:0 -4px 16px rgba(0,0,0,0.15);padding:12px 14px;
    display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
  #widthWarningBanner .ww-text{flex:1;min-width:180px;font-size:13px;
    line-height:1.4;color:var(--text);}
  #widthWarningBanner .ww-actions{display:flex;gap:8px;flex-shrink:0;}
  #widthWarningBanner .ww-switch{background:var(--accent);color:var(--text-on-accent);
    border:none;padding:8px 14px;border-radius:7px;cursor:pointer;font-size:13px;
    font-weight:600;font-family:inherit;white-space:nowrap;}
  #widthWarningBanner .ww-switch:hover{opacity:0.9;}
  #widthWarningBanner .ww-dismiss{background:none;border:1px solid var(--border);
    color:var(--muted);padding:8px 14px;border-radius:7px;cursor:pointer;
    font-size:13px;font-family:inherit;white-space:nowrap;}
  #widthWarningBanner .ww-dismiss:hover{color:var(--text);border-color:var(--text);}

  :root {
    --bg: #f5f2ec;
    --surface: #ffffff;
    --surface2: #ede9e1;
    --border: #d8d2c8;
    --chord-color: #cc2222;
    --accent: #cc2222;
    --accent2: #2e8a72;
    --text: #1a1814;
    --muted: #7a7670;
    --danger: #b84a38;
    --app-max: 100%;          /* full width by default */
    --app-padding: 0px;       /* no extra padding at app level — content has its own */
    --content-max: 620px;
    /* --paper-width deliberately has NO default set here (2026-09-03,
       per Jim's continued flash report) — it used to be set to `none`
       right in this block, but since this file loads AFTER song.php's
       own early inline <style> block (search that file for
       "--paper-width"), this default — despite matching specificity —
       won the cascade purely by coming later in source order, silently
       overwriting the server-rendered correct value the moment this
       stylesheet finished loading (which, since CSS is render-blocking,
       is typically before first paint anyway — so the flash wasn't a
       brief post-load flicker, it was the page's actual FIRST render).
       .header/.song-body's own var(--paper-width, 100%) already
       supplies its own fallback for any page that never sets this
       variable at all, so nothing is lost by not defaulting it here —
       only the clobbering of song.php's own correct value stops. */

    /* ── Traffic-light button/status tokens (2026-09-02, per Jim) ──
       --accent2 (green) and --danger (a muted brick-red, distinct
       from --accent's brighter red) already existed and already carry
       exactly the right semantics for "ok"/"important" — reused
       directly rather than adding redundant new names that just
       alias them. --warning is the one genuinely missing tier; chosen
       to match the existing palette's muted/earthy character (see
       --danger's own muting) rather than a bright pure orange.
       *-bg variants are a single consistent tint (10% opacity) for
       alert/status boxes, replacing what the styling audit found as
       hand-typed rgba() literals at four different, inconsistent
       opacities (0.08/0.10/0.12/0.15) for the same colors. Not yet
       applied to any button or alert box in this pass — that's a
       separate classification pass (which button/message counts as
       ok/warning/danger) once this token set itself is confirmed. */
    --warning: #b8722e;
    --accent-bg:  rgba(204,34,34,0.10);
    --accent2-bg: rgba(46,138,114,0.10);
    --warning-bg: rgba(184,114,46,0.10);
    --danger-bg:  rgba(184,74,56,0.10);

    /* ── Canonical chord-picker colors (2026-09-02, per Jim) — Blue
       and Black previously had two different values in circulation
       across files (styling audit finding). These are song.css's own
       actual values (both its top-bar and hamburger chord-color
       pickers already agree on these), kept as plain reference here
       for whoever later converges the other files' pickers onto one
       value — not yet wired into the picker buttons themselves, which
       still use inline hex values, not var(). */
    --chord-blue:  #2255aa;
    --chord-black: #222222;

    /* Text color for content sitting directly on an --accent/--accent2
       background (buttons, banners) — 2026-09-02, per Jim, found
       during the hardcoded-color sweep. Currently safe as a hardcoded
       #fff in every one of its 4 uses (accent colors are all
       medium-to-dark today, so white text has always had good
       contrast), but tokenizing it now protects against a future
       theme where that might not hold — e.g. a light/pastel theme
       where white text on a light accent would be illegible. */
    --text-on-accent: #fff;
  }

  /* ── Proof-of-concept alternate theme (2026-09-02, per Jim) —
     activated via ?uitheme=sample (admin-only, see song.php).
     Not meant to be a real second theme, just enough of a visible,
     comprehensive override to confirm every token above actually gets
     picked up everywhere it should before building a real one (mono,
     then later 'cowboy'). Deliberately named [data-theme], not
     reusing the existing .mono-theme class — that's an unrelated,
     existing per-song "monochrome chord diagrams" feature (see
     showMonochrome in song.js), not a site theme, and conflating
     the two would make them unable to be set independently of each
     other. */
  [data-theme="sample"] {
    --bg: #1a1f26;
    --surface: #232933;
    --surface2: #2c333f;
    --border: #3a4250;
    --text: #e8e6e1;
    --muted: #9aa3b0;
    --accent: #4a9eff;
    --accent-bg: rgba(74,158,255,0.10);
    --accent2: #4ac98a;
    --warning: #e0a030;
    --danger: #e0524a;
  }

  /* Approximates the colour scheme of the live legacy site
     (ozbcoz.com/Songs/index.php and song.php?ID=2000, per Jim's
     screenshots, 2026-09-03) — the teal nav band as --accent, plain
     white background, near-black text. Deliberately colour only, not
     structure, per Jim's own framing. --chord-color is left
     untouched: the default #cc2222 already closely matches the red
     chord names shown in the legacy screenshots. --accent2/--warning/
     --danger are also left at their defaults — the legacy site never
     shows a clear equivalent for any of these across the two pages
     Jim shared, so there was nothing concrete to match rather than
     guess at. */
  [data-theme="legacy"] {
    --bg: #ffffff;
    --surface: #ffffff;
    --surface2: #f2f2f2;
    --border: #cccccc;
    --text: #1c1c1c;
    --muted: #707070;
    --accent: #2e9b93;
    --accent-bg: rgba(46,155,147,0.10);
  }

  /* Same palette as [data-theme="legacy"] above, but with a beige page
     background — matching the 'beige' option in BG_COLOUR_OPTIONS,
     api_keys.php, and song.css's own base :root --bg default (#f5f2ec)
     — and a very light grey content background baked in as this
     theme's own default (#f2f2f2 — reuses legacy's own --surface2
     value, so it stays visually consistent with the rest of the
     palette; also deliberately lighter than the 'lightgrey' Settings
     background-override option, #e8e8e8, per Jim's ask). --surface
     (banners/dropdowns/cards) is left at legacy's own white,
     unchanged — only the page body and each module's own content
     element differ from legacy. Since --content-bg is set here at the
     theme level rather than via an inline Settings override, a user's
     own explicit pageBg/contentBg choice in Settings still takes
     priority over it (inline style beats a class-based rule) — this
     is just the theme's own default when no override is chosen.
     Added 2026-09-06, per Jim. */
  [data-theme="tealbeige"] {
    --bg: #f5f2ec;
    --surface: #ffffff;
    --surface2: #f2f2f2;
    --border: #cccccc;
    --text: #1c1c1c;
    --muted: #707070;
    --accent: #2e9b93;
    --accent-bg: rgba(46,155,147,0.10);
    --content-bg: #f2f2f2;
  }

  body.mono-theme {
    --chord-color: #333333;
  }

  /* Scoped directly to the song content and the edit screen (2026-09-07,
     per Jim's bug report) — was previously applied to every direct
     child of <body> except #contactPanel/#contactOverlay/.filter-exempt,
     which grayscaled the entire page: site banner, settings-bar,
     hamburger menu, the PayPal donate widget, ads, all of it. The
     intent was always "the song and the edit panels only" — #songContentWrap
     is the main song display, #editScreen is the whole editing UI
     (its Edit/Preview/Chords tabs, the textarea, and the live preview
     are all nested inside it, so scoping the parent covers all three
     without listing each separately).
     Extended (2026-09-07, same session) — #headerTitle (the song
     title itself — sits before #songScreen even starts, so the first
     pass missed it), #diagPosBar (the chord-position Top/Bottom/Right/
     Hide buttons AND the scroll start/stop/speed-slider controls — a
     sibling of #songContentWrap, not a descendant), and #scrollPopupShell
     (the scroll controls' own separate floating container on mobile —
     toggleScrollPopup() physically relocates #scrollControlsGroup's
     DOM node in here via appendChild, so both of its possible parent
     locations need covering or the same bug just reappears on phones).
     Further refined (2026-09-07, per Ed's testing) — the two Ezoic ad
     placeholders are direct children of #songContentWrap (a top ad
     and a bottom ad), so applying the filter to the whole container
     was greyscaling the ads too — not just visually undesirable, but
     a real risk of an ad-supplier audit flagging altered ad rendering.
     filter can't be selectively "un-applied" to a descendant once set
     on an ancestor — the browser composites the whole filtered
     subtree as one unit — so the fix targets #songContentWrap's direct
     children individually instead of the container as a whole,
     excluding .noPrint (which both ad wrapper divs already carry, no
     new class needed). Confirmed via source check: .noPrint appears
     nowhere else inside #songContentWrap, so this exclusion is
     ads-only, not accidentally sweeping in real song content too. */
  body.mono-theme #songContentWrap > *:not(.noPrint),
  body.mono-theme #editScreen,
  body.mono-theme #headerTitle,
  body.mono-theme #diagPosBar,
  body.mono-theme #scrollPopupShell {
    filter: grayscale(1);
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }

  body {
    font-family: 'DM Sans', sans-serif;
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;
    min-height: 100dvh;
    overflow-x: hidden;
    margin-left: 16px; /* clearance for gatekeeper consent button (mobile) */
  }

  /* Ezoic CCPA privacy-preferences banner — REMOVED (2026-09-15, per
     Jim's follow-up bug report and Ezoic's own tech confirmation): the
     position:fixed override below was the actual CAUSE of a new, worse
     problem — pinning the banner to the viewport bottom on every single
     pageview (not just when consent is actually being requested),
     overlaying a large chunk of page content including the under-song
     ad slot, for many US visitors. Ezoic's own explanation: normally,
     once a visitor has already made a choice, the banner sits quietly
     in normal document flow at the end of body — invisible unless
     actually scrolled to — and this override defeated that by removing
     it from flow entirely, every time.
     Replaced with a JS fix instead — see moveEzoicPrivacyBanner() in
     song.js — which actively relocates the banner element itself in
     the DOM once Ezoic injects it, rather than trying to control its
     position with CSS alone. This sidesteps the layout question
     entirely (whatever turns out to be setting body/#app to
     display:flex — still not conclusively identified; see the removed
     comment's own history if this needs revisiting) since the banner
     now sits in normal, un-overridden document flow whatever the
     answer turns out to be. */

  /* ══════════════════════════════════════════
     APP SHELL
  ══════════════════════════════════════════ */
  #app {
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    width: 100%;
    position: relative;
    min-width: 0;
    overflow: hidden;
  }

  /* ══════════════════════════════════════════
     HEADER
  ══════════════════════════════════════════ */
  /* ── Header ── */
  .header {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    padding: 10px 14px 8px;
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    overflow: hidden;
    min-width: 0;
  }
  .header > * + * { margin-top: 3px; }
  /* Logo space — flex always, hidden on mobile via mobile media query */
  .header-back {
    background: none; border: none; color: var(--accent); cursor: pointer;
    font-size: 22px; padding: 0; line-height: 1; flex-shrink: 0;
  }
  .header-title {
    font-family: Helvetica, Arial, sans-serif;
    font-size: var(--title-font-size, 1.2em); color: var(--accent);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    line-height: 1.2; font-weight: var(--heading-weight, 400);
  }
  .header-song-subtitle {
    font-size: var(--subtitle-font-size, 1.0em); color: var(--muted);
    letter-spacing: 0.3px; line-height: 1.4;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-style: italic; font-weight: var(--heading-weight, 400);
  }
  .header-subtitle {
    font-size: var(--subtitle-font-size, 1.0em); color: var(--muted);
    letter-spacing: 0.3px; line-height: 1.4;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-weight: var(--heading-weight, 400);
  }

  /* ══════════════════════════════════════════
     SITE BANNER — logo, site title, userid, Donate, Vis toggle.
     Sits above everything else; the per-song header below it now only
     carries the song's own title/subtitle/meta line.
     ══════════════════════════════════════════ */
  .site-banner {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; padding: 6px 14px; flex-shrink: 0; flex-wrap: wrap;
    background: var(--surface); border-bottom: 1px solid var(--border);
  }
  .site-banner-brand { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; }
  .site-banner-logo { flex-shrink: 0; display: block; }
  .site-banner-title {
    font-family: Arial, sans-serif; font-size: 1.05em;
    color: var(--accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .site-banner-actions { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; justify-content: flex-end; flex-shrink: 0; }
  @media(max-width:767px) {
    /* Force banner into a single non-wrapping row on mobile */
    .site-banner { flex-wrap: nowrap; padding: 5px 10px; gap: 6px; }
    .site-banner-actions .sb-btn-label { display: none; }
    .site-banner-title { font-size: 0.9em; }
  }
  .site-banner-actions .auth-link {
    font-size: 12px; color: var(--text); text-decoration: none; /* was
      hardcoded #4a4640 (2026-09-02, per Jim) — same class of bug as
      .sb-btn, found in the same sweep. */
    padding: 3px 8px; border-radius: 5px; white-space: nowrap;
    border: 1px solid var(--border); background: var(--surface2);
  }
  .site-banner-actions .auth-link:hover { color: var(--text); border-color: var(--accent); }

  /* Donate — currency dropdown + lazily-rendered PayPal button (only the
     selected currency's button is ever instantiated, see renderDonateButton()) */
  .donate-widget { display: flex; align-items: center; gap: 6px; }
  .donate-currency-select {
    font-size: 11px; padding: 3px 4px; border-radius: 5px;
    border: 1px solid var(--border); background: var(--surface2); color: var(--text);
  }
  .donate-button-target { display: flex; align-items: center; min-height: 26px; }
  .donate-button-target img { max-height: 26px; width: auto; display: block; }

  /* Theme selector (2026-09-04, per Jim) — reuses .donate-currency-select's
     base sizing/shape, but deliberately draws the eye with the site's
     own accent colour, so it doesn't blend into the neutral currency
     dropdown next to it. Shared here in song.css (rather than each
     page's own inline styles) since #themeSelectBanner is the same ID
     on every page. */
  #themeSelectBanner {
    border: 1px solid var(--accent);
    color: var(--accent);
    font-weight: 600;
  }
  #themeSelectBanner:hover { background: var(--accent-bg); }

  /* Same treatment for the hamburger menu's own Theme section header
     (2026-09-04, per Jim) — .ham-section-header is shared by many
     different menu items, so this needs its own, more specific class
     rather than styling that shared selector directly. Note: every
     .ham-section-header already defaults to var(--accent) text, so a
     text-colour rule here would be invisible against the others —
     a background tint is what actually makes this one stand out. */
  .theme-ham-header { background: var(--accent-bg); border-radius: 6px; padding-left: 6px; padding-right: 6px; }

  /* Scroll line — the relocated mobile header scroll controls (same IDs,
     same JS), now their own always-visible row instead of mobile-only and
     bundled into the song header. */
  .scroll-line {
    align-items: center; gap: 6px; flex-shrink: 0;
    padding: 5px 14px; background: var(--surface); border-bottom: 1px solid var(--border);
  }

  /* Mobile menu row — Edit/Hamburger triggers occupy this row on mobile,
     where the desktop-only .settings-bar (the menu line) is hidden, so
     there's always exactly one "menu line" regardless of viewport. */
  .mobile-menu-row {
    display: flex; align-items: center; justify-content: flex-end; gap: 6px;
    padding: 6px 14px; background: var(--surface); border-bottom: 1px solid var(--border);
  }
  .icon-btn {
    background: none; border: 1px solid var(--border); color: var(--muted);
    width: 34px; height: 34px; border-radius: 8px; cursor: pointer;
    font-size: 16px; display: flex; align-items: center; justify-content: center;
    transition: all 0.15s; flex-shrink: 0;
  }
  .icon-btn:hover, .icon-btn.active {
    border-color: var(--accent); color: var(--accent);
    background: rgba(200,169,110,0.08);
  }

  /* Desktop-only header buttons hidden on mobile */
  .desktop-only { display: none; }
  /* Mobile second header row — donate, fave, contact */
  .mobile-action-row {
    display: flex; align-items: center; gap: 8px; padding: 5px 10px;
    background: var(--surface); border-bottom: 1px solid var(--border);
    flex-shrink: 0;
  }
  .mobile-action-row .donate-widget { flex: 1; }
  .mobile-action-row .icon-btn { flex-shrink: 0; }

  /* ══════════════════════════════════════════
     SCREENS
  ══════════════════════════════════════════ */
  .screen { display: none; flex: 1; min-height: 0; overflow: hidden; }
  .screen.active { display: flex; flex-direction: column; }
  /* Library and edit screens scroll themselves; song screen scrolls via song-body */
  #editScreen.active    { overflow: hidden; }
  #songScreen.active    { overflow: hidden; } /* song-body is the scroller */

  /* ══════════════════════════════════════════
     LIBRARY SCREEN
  ══════════════════════════════════════════ */


  /* Library header bar (desktop: contains + button) */




  /* ══════════════════════════════════════════
     SONG SCREEN — mobile layout
  ══════════════════════════════════════════ */
  .instrument-bar {
    display: none; /* moved into hamburger on mobile */
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    flex-shrink: 0;
  }
  .instrument-selector {
    display: flex; align-items: center;
    background: var(--surface2); border: 1px solid var(--border);
    border-radius: 6px; padding: 5px 10px; cursor: pointer;
    transition: border-color 0.15s;
  }
  .instrument-selector > * + * { margin-left: 8px; }
  .instrument-selector:hover { border-color: var(--accent); }
  .instrument-selector-icon { font-size: 15px; flex-shrink: 0; }
  .instrument-selector-info { flex: 1; min-width: 0; }
  .instrument-selector-name { font-size: 12px; font-weight: 500; color: var(--text); }
  .instrument-selector-tuning { font-size: 10px; color: var(--muted); font-family: 'DM Mono', monospace; }
  .instrument-selector-chevron { color: var(--muted); font-size: 10px; flex-shrink: 0; }

  /* Song body — mobile: flex column; desktop: overridden to block below */
  .song-body {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    flex-direction: column;
  }

  .song-content, #songContentWrap {
    padding: 16px 18px 2em;
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    min-height: 0;
    /* Content-background override (2026-09-04, per Jim) — this
       element never had a background of its own before (it just
       inherited body's), so 'transparent' is the correct fallback:
       leaves song.php looking exactly as it does today when no
       override is set, and --content-bg (set globally, inline on
       <html>, only when the user has actually chosen a content
       background override) takes over cleanly when it is. See
       api_keys.php's resolve_bg_overrides() for the full mechanism. */
    background: var(--content-bg, transparent);
  }


  #songContent { min-height: 60vh; }

  /* Left column — lyrics, piano, second instrument */
  .song-col {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* No overflow here — song-body scrolls */
  }

  .section-label {
    font-size: 10px; font-weight: 400; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--accent2); margin: 18px 0 10px; padding-left: 2px;
    display: var(--section-heading-display, block);
  }
  .section-label:first-child { margin-top: 0; }

  /* Chorus sections get left border indent per spec */
  .section-tab {
    overflow-x: auto;
    line-height: 1.5;
  }
  .section-tab .lyric-line .lyric-text,
  .section-tab .lyric-line .chord-token-mini .lyric-text,
  .section-tab .lyric-line .chord-token-stack .lyric-text {
    font-family: Courier, 'Courier New', monospace !important;
    /* CHANGED (2026-09-16, per Jim's bug report): was a hardcoded
       10pt !important, completely independent of --song-text-size (the
       user's own chosen lyrics size) — looked substantially smaller
       than the rest of the song whenever that size was set above 10pt,
       which is most of the time, since 10pt is quite small as a lyrics
       size to begin with. Now scales with it, 2pt smaller, same
       reasoning as before (a slightly more compact size still helps
       fret-number columns stay aligned without wrapping) but tracking
       the user's actual choice instead of a fixed value. 2pt was
       Jim's own starting point, adjustable if it needs revisiting. */
    font-size: calc(var(--song-text-size, 14pt) - 2pt) !important;
    font-weight: bold !important;
  }
  .section-chorus {
    border-left: 5px solid #999;
    margin: 1em 0 1.0em;
    padding-left: 12px;
  }
  /* Instrumental solo sections get their colour from inline style */
  /* Line spacing controlled via --lyric-line-spacing (line-height) and
     --lyric-line-gap (margin-bottom between lines, 0 at default 1.2) */
  .lyric-line { margin-bottom: var(--lyric-line-gap, 0em); line-height: var(--lyric-line-spacing, 1.2); }
  .chord-token { position: relative; display: inline-block; }

  /* Mini-diagram mode: chord name + small diagram stacked above lyric */
  .chord-token-mini {
    display: inline-block;
    text-align: left;
    vertical-align: bottom;
    line-height: 1;
    margin-right: 2px;
  }
  /* Wraps just the chord name + diagram, sized to the name's own width
     (the diagram is taken out of flow below, via position:absolute, so
     it never stretches this wrapper) — this is what lets the diagram
     centre on the name specifically, rather than on the token's full
     width (which is usually set by the lyric text below and was the
     original cause of names/diagrams drifting off to one side). */
  .chord-token-mini .chord-name-diag-wrap {
    position: relative;
    display: inline-block;
  }
  .chord-token-mini .chord-name {
    font-size: var(--song-chord-size, var(--song-text-size, 14pt));
    padding-right: 0;
    text-align: left;
    line-height: 1.2;
    display: block;
  }
  .chord-token-mini .mini-diag-img {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    /* top offset (chord-name's own rendered height) and the wrap's
       padding-bottom (reserving room for the diagram below that, so
       the lyric line underneath doesn't overlap it) are both set
       inline per-token from JS, since both depend on the current
       chord font size and diagram size. */
  }
  .chord-token-mini .lyric-text {
    white-space: pre-wrap; white-space-collapse: preserve; text-wrap-mode: wrap;
    display: block;
    line-height: var(--lyric-line-spacing, 1.2);
  }

  /* Above-lyrics mode: stacked token layout */
  .chord-line {
    margin-bottom: 0;
    line-height: 1;
  }
  .chord-token-stack {
    display: inline-block;
    line-height: 1;
    vertical-align: bottom;
  }
  .chord-token-stack .chord-name {
    padding-right: 6px;
    white-space: pre;
    display: block;
    line-height: 1.2;
  }
  .chord-token-stack .lyric-text {
    white-space: pre-wrap; white-space-collapse: preserve; text-wrap-mode: wrap;
    display: block;
    line-height: var(--lyric-line-spacing, 1.2);
  }
  /* Chord font fallback chain (2026, per Jim): --song-chord-font (set
     only when a song has an explicit {cf:}/{chordfont:} directive) ->
     --lyrics-font (whatever the user's lyrics font selection is —
     Helvetica/Arial/Comic Sans/Low vision/Dyslexia) -> DM Mono as a
     final safety net. Chords now default to matching the lyrics font
     rather than always defaulting to DM Mono regardless of what the
     user picked for lyrics. */
  .chord-name-empty {
    display: block;
    line-height: 1.2;
    font-family: var(--song-chord-font, var(--lyrics-font, 'DM Mono'), monospace);
    font-size: var(--song-chord-size, var(--song-text-size, 14pt));
    visibility: hidden;
  }
  .chord-name {
    font-family: var(--song-chord-font, var(--lyrics-font, 'DM Mono'), monospace);
    font-size: var(--song-chord-size, var(--song-text-size, 14pt)); font-weight: var(--chord-weight, 500);
    color: var(--song-chord-colour, var(--chord-color)); cursor: pointer; transition: color 0.15s;
    display: inline-block; padding-right: 8px;
  }
  .chord-name:hover { opacity: 0.75; text-decoration: underline; }
  .chord-inline {
    font-family: var(--song-chord-font, var(--lyrics-font, 'DM Mono'), monospace);
    font-size: var(--song-chord-size, var(--song-text-size, 14pt)); font-weight: var(--chord-weight, 500);
    color: var(--song-chord-colour, var(--chord-color)); cursor: pointer; transition: color 0.15s;
    display: inline; vertical-align: baseline; line-height: 1;
  }
  .chord-inline:hover { opacity: 0.75; text-decoration: underline; }
  /* Vis-impaired mode — spoken-word chord substitution, always red/bold
     regardless of the song's chord colour setting or print media rules,
     since the colour itself is part of what makes it stand out for
     low-vision reading. */
  .chord-vis {
    font-family: var(--song-chord-font, var(--lyrics-font, 'DM Mono'), monospace);
    font-size: var(--song-chord-size, var(--song-text-size, 14pt));
    color: red !important; font-weight: 700 !important;
    display: inline; vertical-align: baseline; line-height: 1;
    white-space: nowrap; cursor: default;
  }
  /* Vis-impaired mode — under-meta-line chord list */
  .header-vis-chordlist {
    font-size: 13px; color: var(--text); margin-top: 6px;
    line-height: 1.6;
  }
  .vis-chord-entry { margin-right: 18px; white-space: nowrap; }
  .vis-chord-entry strong { font-weight: 700; }
  .lyric-text {
    font-size: var(--song-text-size, 14pt);
    color: var(--song-text-colour, var(--text));
    font-family: var(--song-text-font, var(--lyrics-font, Helvetica, Arial, sans-serif));
    line-height: var(--lyric-line-spacing, 1.2);
    font-weight: var(--lyrics-weight, 400);
    display: inline; padding-right: 2px;
    white-space: pre-wrap; white-space-collapse: preserve; text-wrap-mode: wrap;
  }

  /* Chord diagrams — mobile: below content, scrollable */
  .chord-diagram-section {
    border-top: 1px solid var(--border);
    padding: 6px 8px;
    flex-shrink: 0;
    background: var(--surface);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .chord-diagrams-grid {
    display: grid;
    grid-template-columns: repeat(2, max-content);
    gap: 4px;
  }
  /* Beats the mobile media query's flex-wrap:nowrap !important on the
     same class (ID+class specificity wins even against another
     !important rule) — see song-php-documentation.md for why this
     exists. */
  #chordDiagramsInlineBottom .chord-diagrams-grid {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: wrap !important;
    width: auto !important;
  }
  .chord-diagram-wrap { display: flex; flex-direction: column; align-items: center; }
  .chord-diagram-wrap > * + * { margin-top: 1px; }
  /* Hidden (2026-09-14, per Jim/user feedback) — buildChordSVG() now
     bakes the chord name into the diagram SVG itself by default (see
     song-test.js), so this separate label sitting above the sidebar
     diagram grid (and the second-instrument diagram grid, same class)
     is now redundant and would double the name up. Mini-diagram mode
     is the one exception to the SVG change — its own separate
     .chord-name label stays untouched, a different class from this
     one, not affected by this rule. */
  .chord-diagram-name { display: none; font-family: 'DM Mono', monospace; font-size: 11px; font-weight: 700; color: var(--chord-color); text-align: center; }

  /* ══════════════════════════════════════════
     SHARED MODAL BASE
  ══════════════════════════════════════════ */
  /* Modals sit toward the top on desktop (easy to miss and skip past
     at the bottom); a bottom sheet is kept below the 767px mobile
     breakpoint, where it's the more standard, thumb-reachable
     pattern. */
  .modal-overlay {
    display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.55);
    z-index: 200; align-items: flex-start; justify-content: center;
    padding-top: 8vh;
  }
  .modal-overlay.open { display: flex; }
  .modal {
    background: var(--surface); border-radius: 12px;
    width: 100%; max-width: 480px; padding: 10px 12px 20px 36px;
    border-top: 1px solid var(--border);
    max-height: 70vh; overflow-y: auto;
  }
  .modal-handle { width: 32px; height: 3px; background: var(--border); border-radius: 2px; margin: 0 auto 8px; }
  .modal-title { font-family: 'Playfair Display', serif; font-size: 14px; color: var(--text); margin-bottom: 8px; }
  .modal-chord-large { display: flex; flex-direction: column; align-items: center; }
  .modal-chord-large > * + * { margin-top: 8px; }

  /* Instrument picker restyled to match Settings' look and feel
     (2026, per Jim) — scoped to these two ids only, doesn't touch the
     shared .modal-overlay/.modal base other modals (e.g. Info) still
     use unchanged. No backdrop, matching Settings' own dropdown feel
     (a light floating panel, not a heavy modal). #instModalBox is
     position:fixed (required so the drag mechanism's own top/left
     writes work) rather than relying on the overlay's flex centering
     — left:50%/margin-left gives it a sensible starting position
     without conflicting with how dragging repositions it afterward. */
  #instModal { background: transparent; }
  #instModalBox {
    position: fixed; top: 8vh; left: 50%; margin-left: -160px;
    min-width: 310px; max-width: 340px; max-height: 82vh; overflow-y: auto;
  }

  /* ══════════════════════════════════════════
     HAMBURGER MENU (mobile bottom sheet)
  ══════════════════════════════════════════ */
  /* Hamburger accordion */
  .ham-section { border-top: 1px solid var(--border); margin-top: 2px; }
  .ham-section:first-child { border-top: none; margin-top: 0; }

  /* scroll-mode class kept for future use */
  .ham-section-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 6px 0 5px; cursor: pointer; user-select: none;
    font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
    color: var(--accent); font-weight: 700;
    border-bottom: 1px solid var(--border);
  }
  .ham-section-header:hover { opacity: 0.8; }
  .ham-section-arrow { font-size: 10px; transition: transform 0.2s; }
  .ham-section.open .ham-section-arrow { transform: rotate(180deg); }
  .ham-section-body { display: none; padding-bottom: 4px; }
  .ham-section.open .ham-section-body { display: block; }
  /* Legacy - keep for non-accordion use */
  .menu-row {
    display: flex; align-items: center; justify-content: space-between;
    padding: 5px 0;
  }
  /* Default (row) instances get horizontal sibling spacing. Instances with
     an inline flex-direction:column override (see HTML) need vertical
     spacing instead, matching their own inline gap value — targeted via
     attribute selectors below, since a class-level margin can't
     conditionally follow an inline style. */
  .menu-row > * + * { margin-left: 12px; }
  .menu-row[style*="column"] > * + * { margin-left: 0; }
  .menu-row[style*="gap:8px"] > * + * { margin-top: 8px; }
  .menu-row[style*="gap:6px"] > * + * { margin-top: 6px; }
  .menu-action-row {
    display: flex; padding: 4px 0;
  }
  .menu-action-row > * + * { margin-left: 8px; }
  .menu-action-btn {
    flex: 1; padding: 6px 8px; background: var(--surface2);
    border: 1px solid var(--border); color: var(--text);
    border-radius: 6px; cursor: pointer; font-size: 12px;
    font-family: 'DM Sans', sans-serif; text-align: center;
    transition: all 0.15s;
  }
  .menu-action-btn:hover { border-color: var(--accent); color: var(--accent); }
  .menu-file-btn {
    display: flex; align-items: center; cursor: pointer;
    padding: 6px 10px; background: var(--surface2);
    border: 1px solid var(--border); border-radius: 6px;
    width: 100%; margin: 3px 0;
  }
  .menu-file-btn > * + * { margin-left: 8px; }
  .menu-file-btn span { font-size: 12px; color: var(--text); flex: 1; }
  .menu-file-btn em { font-size: 11px; color: var(--accent); font-style: normal; }

  /* Instrument picker */
  .inst-group-label {
    font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase;
    color: var(--muted); padding: 8px 0 4px; font-weight: 500;
  }
  .inst-group-label:first-child { padding-top: 0; }
  .inst-option {
    display: flex; align-items: center;
    padding: 6px 10px; border-radius: 6px; cursor: pointer;
    transition: background 0.12s; margin-bottom: 1px;
  }
  .inst-option > * + * { margin-left: 10px; }
  .inst-option:hover { background: var(--surface2); }
  .inst-option.active { background: rgba(200,169,110,0.12); }
  .inst-option-icon { font-size: 16px; width: 24px; text-align: center; flex-shrink: 0; }
  .inst-option-info { flex: 1; }
  .inst-option-name { font-size: 12px; font-weight: 500; color: var(--text); }
  .inst-option-tuning { font-size: 10px; color: var(--muted); font-family: 'DM Mono', monospace; margin-top: 1px; }
  .inst-option-check { color: var(--accent); font-size: 14px; }

  /* Compact variant for second instrument picker */
  .inst-option-sm {
    display: flex; align-items: center;
    padding: 0 4px; border-radius: 3px; cursor: pointer;
    transition: background 0.12s; line-height: 1.6;
  }
  .inst-option-sm > * + * { margin-left: 4px; }
  .inst-option-sm:hover { background: var(--surface2); }
  .inst-option-sm.active { background: rgba(200,169,110,0.12); }
  .inst-option-sm-name { font-size: 10px; font-weight: 500; color: var(--text); }
  .inst-option-sm-tuning { font-size: 9px; color: var(--muted); font-family: 'DM Mono', monospace; }
  .inst-option-sm-check { color: var(--accent); font-size: 10px; margin-left: auto; }

  /* ══════════════════════════════════════════
     SHARED CONTROLS (used in menus + panels)
  ══════════════════════════════════════════ */

  .toggle-switch {
    width: 40px; height: 22px; border-radius: 11px; border: none;
    cursor: pointer; position: relative; background: var(--border);
    transition: background 0.2s; flex-shrink: 0;
  }
  .toggle-thumb {
    position: absolute; top: 3px; left: 3px;
    width: 16px; height: 16px; border-radius: 50%;
    background: #888; transition: all 0.2s;
  }
  /* Vis-impaired mode disables a few layout controls that have no visible
     effect while diagrams aren't shown (brackets, finger numbers, notes,
     left-hand, diagram columns) — greyed out rather than hidden, so their
     last-set state stays visible and they re-enable cleanly on toggle-off. */
  .toggle-switch:disabled, .size-btn:disabled {
    opacity: 0.4; cursor: not-allowed;
  }
  /* Items hidden entirely in Vis mode (Edit, most of the Files menu) —
     toggled via body.vis-mode rather than per-element JS, see toggleVisMode(). */
  body.vis-mode .vis-hide { display: none !important; }

  .font-control { display: flex; align-items: center; }
  .font-control > * + * { margin-left: 8px; }
  .font-control button {
    background: var(--surface2); border: 1px solid var(--border); color: var(--text);
    width: 26px; height: 26px; border-radius: 5px; font-size: 15px; cursor: pointer;
  }

  .size-btn {
    padding: 4px 8px; font-size: 11px; background: var(--surface2);
    border: 1px solid var(--border); color: var(--muted);
    border-radius: 6px; cursor: pointer; transition: all 0.15s;
  }

  .divider { height: 1px; background: var(--border); margin: 14px 0; }
  /* overflow-wrap:anywhere added to all three (2026-09-14, per Jim's
     bug report — a recurrence of the earlier print-blank-page issue,
     this time triggered by a long, unbroken string — a URL — inside a
     {c:} comment). A browser can't break a word mid-character by
     default, so a string with no natural break points (no spaces) that
     doesn't fit in the remaining space on a page gets pushed to the
     next page/line wholesale, leaving a gap behind it rather than
     partially filling the space. overflow-wrap:anywhere forces a break
     wherever needed to prevent that overflow — preferring natural
     points like a slash or dash first (already the default browser
     behaviour at those characters), falling back to a mid-word break
     only as a last resort, exactly matching what Jim asked for. Chosen
     over the older word-wrap/word-break:break-word pairing since
     anywhere is the modern property that's also correctly accounted
     for when the browser calculates this element's own minimum
     content size — the older pairing can still let a long unbroken
     string force an element wider than intended in some layout
     contexts, even though it displays wrapped once rendered. */
  .comment-line { font-size: var(--song-text-size, 14pt); color: var(--muted); font-weight: var(--comment-weight, 400); line-height: var(--lyric-line-spacing, 1.2); margin-bottom: calc(var(--lyric-line-spacing, 1.2) * 0.4em); padding-left: 1.2em; overflow-wrap: anywhere; }
  .comment-italic-line { font-size: var(--song-text-size, 14pt); color: var(--muted); font-style: italic; font-weight: var(--comment-weight, 400); line-height: var(--lyric-line-spacing, 1.2); margin-bottom: calc(var(--lyric-line-spacing, 1.2) * 0.4em); padding-left: 1.2em; overflow-wrap: anywhere; }
  .comment-box-line { font-size: var(--song-text-size, 14pt); color: var(--text); font-weight: var(--comment-weight, 400); line-height: var(--lyric-line-spacing, 1.2); margin: 0.4em 0; padding: 0.4em 0.8em; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; display: inline-block; overflow-wrap: anywhere; max-width: 100%; }

  /* ── Piano diagram section ── */
  .piano-section {
    border-top: 1px solid var(--border);
    padding: 12px 14px 16px;
    background: var(--surface);
    flex-shrink: 0;
    display: none; /* hidden unless showPiano is true */
  }
  .piano-section.visible { display: block; }
  .piano-section-header {
    font-size: 10px; letter-spacing: 1px; text-transform: uppercase;
    color: var(--muted); margin-bottom: 6px; font-weight: 700;
  }
  .piano-diagrams-grid {
    display: flex; flex-wrap: wrap; align-items: flex-start;
    margin: -3px;
  }
  .piano-diagrams-grid > * { margin: 3px; }
  .piano-diagram-wrap {
    display: flex; flex-direction: column; align-items: center;
  }
  .piano-diagram-wrap > * + * { margin-top: 4px; }
  .highlight-line {
    font-size: 13px; font-weight: 500; margin-bottom: 8px;
    background: rgba(200,169,110,0.12); border-left: 3px solid var(--accent);
    padding: 4px 8px; border-radius: 0 4px 4px 0; color: var(--text);
  }
  .column-break { break-after: column; height: 0; }

  /* ══════════════════════════════════════════
     SLIDE-IN PANELS (desktop)
  ══════════════════════════════════════════ */
  /* Dim overlay behind slide panel */

  /* ══════════════════════════════════════════
     EDITOR SCREEN
  ══════════════════════════════════════════ */

  /* Edit/Preview tab bar (mobile) */
  .edit-tabs {
    display: flex;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    flex-shrink: 0;
  }
  .edit-tab {
    flex: 1; padding: 10px; text-align: center;
    font-size: 13px; font-family: 'DM Sans', sans-serif;
    background: none; border: none; color: var(--muted);
    cursor: pointer; border-bottom: 2px solid transparent;
    transition: all 0.15s;
  }
  .edit-tab.active { color: var(--accent); border-bottom-color: var(--accent); }

  /* Editor body: textarea + preview split */
  .edit-body {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }
  /* Swaps ChordPro/Preview left-right on desktop only — mobile shows
     them one at a time via tabs (a hidden-class toggle, not flex
     order), so reordering wouldn't be visible there. */
  .edit-body.panes-swapped .edit-pane { order: 2; }
  .edit-body.panes-swapped .preview-pane { order: 1; }

  /* Textarea pane */
  .edit-pane {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
  }
  .edit-pane.hidden { display: none; }
  .edit-pane-title { display: none; }

  .edit-textarea {
    flex: 1;
    width: 100%;
    padding: 16px 18px;
    font-family: 'DM Mono', monospace;
    font-size: 13px;
    line-height: 1.6;
    color: var(--text);
    background: var(--bg);
    border: none;
    outline: none;
    resize: none;
    min-height: 0;
  }
  .edit-textarea::selection { background: rgba(204,34,34,0.2); }

  /* Preview pane */
  .preview-pane {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    border-left: 1px solid var(--border);
    overflow-y: auto;
  }
  .preview-pane.hidden { display: none; }


  /* ══════════════════════════════════════════
     KEY PICKER
  ══════════════════════════════════════════ */
  .key-picker {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 5px;
    margin: 8px 0 4px;
  }
  .key-btn {
    padding: 6px 2px;
    font-size: 12px;
    font-family: 'DM Mono', monospace;
    background: var(--surface2);
    border: 1px solid var(--border);
    color: var(--muted);
    border-radius: 6px;
    cursor: pointer;
    text-align: center;
    transition: all 0.12s;
    white-space: nowrap;
  }
  .key-btn:hover { border-color: var(--accent); color: var(--accent); }
  .key-btn.active {
    background: var(--accent-bg);
    border-color: var(--accent);
    color: var(--accent);
    font-weight: 600;
  }
  .key-section-label {
    font-size: 10px; letter-spacing: 1px; text-transform: uppercase;
    color: var(--muted); margin: 10px 0 6px;
  }
  .transpose-status {
    text-align: center; margin-top: 8px;
    font-family: 'DM Mono', monospace; font-size: 13px;
    color: var(--accent); min-height: var(--lyric-line-height-em, 1.2em);
  }
  /* Within .transpose-status: the original key is just an inherited fact
     about the song (never something the user picked), so it gets the
     same neutral treatment as any other settings value/label — --text,
     not a theme or chord colour. The current key, only shown once it
     differs from the original, IS the user's active choice — same
     "this is what's currently selected" reasoning as scrollSpeedValue,
     so it gets --accent, matching the un-differentiated "+N semitones"
     fallback text above (2026-09-03, per Jim, decoupling both from
     --chord-color, same reasoning as the key-picker/action-button
     highlights earlier this session). */
  .key-original {
    color: var(--text); opacity: 0.45;
    border: 1px solid currentColor; border-radius: 4px; padding: 0 4px;
  }
  .key-current {
    color: var(--accent); font-weight: 700;
    border: 1.5px solid currentColor; border-radius: 4px; padding: 0 4px;
  }

  /* ══════════════════════════════════════════
     LOGIN / AUTH HEADER
  ══════════════════════════════════════════ */
  .auth-link {
    font-size: 13px;
    color: var(--text); /* was hardcoded #4a4640 (2026-09-02, per Jim)
      — same class of bug as .sb-btn, found in the same sweep. */
    text-decoration: none;
    white-space: nowrap;
    padding: 4px 8px;
    border-radius: 6px;
    border: 1px solid transparent;
    transition: all 0.15s;
    flex-shrink: 0;
  }
  .auth-link:hover {
    border-color: var(--border);
    color: var(--text);
    background: var(--surface2);
  }
  .auth-link.logged-in {
    color: var(--accent2);
  }
  .auth-link.logged-in:hover {
    border-color: var(--accent2);
    background: rgba(46,138,114,0.08);
  }

  /* ══════════════════════════════════════════
     SCROLLBAR
  ══════════════════════════════════════════ */
  ::-webkit-scrollbar { width: 4px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }

  /* ══════════════════════════════════════════
     SETTINGS BAR (desktop only)
  ══════════════════════════════════════════ */
  .settings-bar {
    display: none; /* hidden on mobile */
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    padding: 0 12px;
    flex-shrink: 0;
    align-items: center;
    gap: 2px;
    overflow-x: auto;
    overflow-y: hidden;
    min-height: 40px;
  }

  /* Instrument button in settings bar */
  .sb-instrument {
    display: flex; align-items: center;
    padding: 5px 10px; border-radius: 6px;
    background: none; border: 1px solid transparent;
    cursor: pointer; color: var(--text);
    font-family: 'DM Sans', sans-serif;
    transition: all 0.15s; flex-shrink: 0;
    min-width: 0;
  }
  .sb-instrument > * + * { margin-left: 6px; }
  .sb-instrument:hover { border-color: var(--border); background: var(--surface2); }
  .sb-instrument-icon { font-size: 16px; flex-shrink: 0; }
  .sb-instrument-text { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
  .sb-instrument-name { font-size: 13px; font-weight: 500; white-space: nowrap; }
  .sb-instrument-tuning { font-size: 10px; color: var(--muted); font-family: 'DM Mono', monospace; white-space: nowrap; }
  .sb-chevron { font-size: 10px; color: var(--muted); flex-shrink: 0; margin-left: 2px; }

  .sb-divider { width: 1px; background: var(--border); height: 20px; margin: 0 4px; flex-shrink: 0; }

  /* Dropdown buttons in settings bar */
  .sb-btn {
    display: flex; align-items: center;
    padding: 5px 4px; border-radius: 6px;
    background: none; border: 1px solid transparent;
    cursor: pointer; color: var(--text); /* was hardcoded #4a4640
      (2026-09-02, per Jim) — found via the [data-theme="sample"] test
      itself: this never responded to theme changes at all, so it
      stayed dark under the new theme's dark background and became
      nearly unreadable. Exactly the class of bug the styling audit
      was looking for, just now confirmed hands-on. */
    font-family: 'DM Sans', sans-serif; font-size: 13px;
    transition: all 0.15s; white-space: nowrap; flex-shrink: 0;
    text-decoration: none; /* (2026-09-01, per Jim) — every other
      .sb-btn in this file is a real <button> (never underlined
      regardless of this rule), but Help is now an <a class="sb-btn">
      (myfaq/ link), the only one — without this, it was the sole
      element actually exposed to the missing rule. faves.php's and
      index.php's own .sb-btn definitions already had this. */
  }
  .sb-btn > * + * { margin-left: 3px; }
  .sb-btn:hover, .sb-btn.open { border-color: var(--border); background: var(--surface2); color: var(--text); }
  .sb-btn-label { }
  .sb-btn-icon { font-size: 14px; }

  /* CHANGE KEY button (2026, per Jim) — a very important, easily-
     overlooked feature, moved next to the instrument selector and
     given the same visibility treatment Edit already has. Keeps
     .sb-btn's own base/hover/open-state styling (this only overrides
     the label/icon colour and text treatment on top of it) — the
     collapse-exemption itself is a separate, matching change in
     collapseSettingsBar()'s own selector in song.js. Explicit color
     on the label itself (not relying on inheritance) is what keeps it
     red even during hover/open, when .sb-btn's own rule above would
     otherwise switch it to var(--text). */
  .sb-key-highlight .sb-btn-label { color: var(--accent); font-weight: 700; text-transform: uppercase; letter-spacing: 0.3px; }
  .sb-key-highlight .sb-btn-icon { color: var(--accent); }
  .sb-key-highlight:hover { border-color: var(--accent); }

  /* Spacer pushes edit button to right */
  .sb-spacer { flex: 1; }

  /* Edit icon button in settings bar. Deliberately NOT part of the
     .sb-btn collapse system (see collapseSettingsBar() JS below) —
     keeps its full text as long as possible, and is bold/uppercase to
     draw attention to it specifically. */
  .sb-edit-btn {
    display: flex; align-items: center;
    padding: 5px 8px; border-radius: 6px;
    background: none; border: 1px solid transparent;
    cursor: pointer; color: var(--muted); font-size: 12px;
    font-family: 'DM Sans', sans-serif;
    font-weight: 700; text-transform: uppercase;
    transition: all 0.15s; flex-shrink: 0; white-space: nowrap;
  }
  .sb-edit-btn > * + * { margin-left: 4px; }
  .sb-edit-btn:hover { border-color: var(--accent); color: var(--accent); background: rgba(200,169,110,0.08); }

  /* Dropdown panels — confirmed as the reference standard (2026-09-02,
     per Jim), already matching this shape/sizing in the majority of
     files per the styling audit (5 of 10). No change needed here;
     this comment exists so whoever migrates faves.php/index.php/
     terms.php/thanks.php's own diverging .sb-dropdown variants knows
     which values they're converging onto. */
  .sb-dropdown {
    position: fixed;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(0,0,0,0.12);
    z-index: 250;
    padding: 8px;
    min-width: 200px;
    max-width: 300px;
    display: none;
  }
  .sb-dropdown.open { display: block; }
  .sb-dropdown-title {
    font-size: 9px; letter-spacing: 1.2px; text-transform: uppercase;
    color: var(--muted); font-weight: 600;
    padding: 5px 6px; margin: 0;
    cursor: pointer; user-select: none;
    display: flex; align-items: center; justify-content: space-between;
    border-top: 1px solid var(--border);
  }
  .sb-dropdown-title:first-child { border-top: none; }
  .sb-dropdown-title:hover { color: var(--accent); }
  .sb-dropdown-title::after { content: '▸'; font-size: 9px; transition: transform 0.15s; }
  .sb-dropdown-title.open::after { transform: rotate(90deg); }
  .sb-row {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: 6px;
  }
  .sb-row > * + * { margin-left: 8px; }
  .sb-row[style*="column"] > * + * { margin-left: 0; margin-top: 6px; }
  .sb-row-label { font-size: 12px; color: var(--text); white-space: nowrap; }

  /* Songs dropdown items */
  .sb-songs-item {
    display: flex; align-items: center;
    padding: 5px 8px; border-radius: 5px;
    font-size: 12px; color: var(--text); text-decoration: none;
    transition: background 0.12s; cursor: pointer;
    margin-bottom: 1px;
  }
  .sb-songs-item > * + * { margin-left: 6px; }
  .sb-songs-item:hover { background: var(--surface2); color: var(--accent); }
  /* Compact variant for Files dropdown */
  .sb-files-item {
    display: flex; align-items: center;
    padding: 5px 10px; border-radius: 6px;
    font-size: 12px; color: var(--text); text-decoration: none;
    transition: background 0.12s; cursor: pointer;
    white-space: nowrap;
  }
  .sb-files-item > * + * { margin-left: 7px; }
  .sb-files-item:hover { background: var(--surface2); color: var(--accent); }
  /* Mobile hamburger file links */
  .ham-file-link {
    display: flex; align-items: center;
    padding: 4px 0; font-size: 12px;
    color: var(--text); text-decoration: none;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
  }
  .ham-file-link > * + * { margin-left: 8px; }
  .ham-file-link:last-of-type { border-bottom: none; }
  .ham-file-link:hover { color: var(--accent); }
  .ham-file-icon { font-size: 13px; width: 18px; text-align: center; flex-shrink: 0; }

  /* Dim overlay for dropdowns */
  .sb-overlay {
    display: none; position: fixed; inset: 0; z-index: 249;
  }
  .sb-overlay.open { display: block; }

  /* Graceful collapse: hide tuning at medium width */
  @media (max-width: 860px) {
    .sb-instrument-tuning { display: none; }
  }

  .sb-label-hidden .sb-btn-label { display: none; }
  .sb-label-hidden { padding: 5px 6px; }
  /* Applied by collapseSettingsBar() only for the brief duration of its
     measurement pass (2026-07-26 bug fix) — .sb-btn's own
     "transition: all 0.15s" meant width changes from collapsing a
     label didn't register in the very next synchronous measurement,
     since the transition hadn't actually started animating yet.
     Turning transitions off during measurement makes each change
     register immediately; it's removed again right after. */
  .sb-no-transition, .sb-no-transition * { transition: none !important; }
  @media (max-width: 900px) {
    .sb-instrument-name { max-width: 60px; overflow: hidden; text-overflow: ellipsis; }
  }

  /* ══════════════════════════════════════════
     DESKTOP — ≥ 768px
  ══════════════════════════════════════════ */
@media (max-width: 767px) {
    /* Mobile keeps the bottom-sheet pattern (see .modal-overlay above
       for why desktop doesn't). */
    .modal-overlay { align-items: flex-end; padding-top: 0; }
    .modal { border-radius: 12px 12px 0 0; }

    /* Ensure header stays column on mobile */
    .header { flex-direction: column !important; align-items: stretch !important; padding: 10px 14px 8px !important; }
    .header-text-col { display: contents; }

    /* Smaller icon buttons on mobile */
    .icon-btn { width: 30px; height: 30px; font-size: 14px; border-radius: 6px; }

    /* On mobile, subtitle-row is a column so subtitle and buttons stack */
    .header-subtitle-row { flex-direction: column; align-items: flex-start; }
    .header-subtitle-row > * + * { margin-left: 0; margin-top: 4px; }

    /* Mobile: clean flex column — no float */
    .song-body {
      display: flex !important;
      flex-direction: column;
      flex: 1;
      min-height: 0;
      overflow-y: auto;
      overflow-x: hidden;
    }
    .song-col { display: flex; flex-direction: column; width: 100%; min-width: 0; }

    /* Float sidebar hidden on mobile — chords shown in top slot */
    #chordDiagramsFloat { display: none !important; }

    /* Position bar hidden on mobile */
    #diagPosBar { display: none !important; }

    /* Chord diagrams: horizontal scroll strip */
    .chord-diagram-section {
      float: none !important;
      position: static !important;
      width: 100% !important;
      max-width: 100% !important;
      border-left: none;
      border-bottom: 1px solid var(--border);
      overflow-x: auto;
      overflow-y: hidden;
      padding: 10px 12px;
      background: var(--surface);
      -webkit-overflow-scrolling: touch;
    }
    .chord-diagrams-grid {
      display: flex !important;
      flex-direction: row !important;
      flex-wrap: nowrap !important;
      width: max-content;
      align-items: flex-start;
    }
    .chord-diagrams-grid > * + * { margin-left: 4px; }
    .chord-diagram-wrap { flex-shrink: 0; }

    /* Full width content, tighter padding. No inner scroll — song-body scrolls. */
    #songContentWrap { padding: 12px 14px; overflow-y: visible; overflow-x: visible; height: auto; flex: none; min-height: 0; }
    /* Matches this context's own top padding (12px, not desktop's 16px) —
       see the universal -16px rule near the other #chordDiagramsTop rules. */
    #chordDiagramsTop { margin-top: -12px; }

    /* Mobile scroll popup shell: allow wrapping so it doesn't overflow a
       narrow phone's width, since it's position:fixed with no fixed
       width of its own. */
    #scrollPopupShell { max-width: 90vw; }
  }

    @media (min-width: 768px) {
/* Desktop: always show both panes side by side */
    .edit-tabs { display: none; }
    .edit-pane          { display: flex !important; border-right: 1px solid var(--border); }
    #previewPane        { display: flex !important; flex-direction: column; }
    /* chordsPane is handled by the slide panel — keep hidden on desktop */
    #chordsPane         { display: none !important; }
    /* Column titles, shown only on desktop — mobile already has the
       Edit/Preview/Chords tabs above serving the same purpose (see the
       base .edit-pane-title rule for that). */
    .edit-pane-title {
      display: block; flex-shrink: 0;
      padding: 8px 18px; font-size: 11px; font-weight: 600;
      letter-spacing: 1px; text-transform: uppercase;
      color: var(--text); background: var(--surface2); /* color was
        hardcoded #4a4640 (2026-09-02, per Jim) — same class of bug as
        .sb-btn, found in the same sweep. */
      border-bottom: 1px solid var(--border);
    }

#app { max-width: 100%; width: 100%; }
/* Paper width constrains only the song content, not banner/settings bar */
/* This rule is also duplicated inline in song.php's own early
   <style> block (2026-09-02, per Jim) — that copy is what actually
   fixed the flash-to-fullscreen bug, since this external copy alone
   left a real window (while this file downloads/parses) where
   .header had no width constraint at all. Keep both in sync if this
   rule ever changes. */
.header, .song-body {
  width: 100%;
  max-width: var(--paper-width, 100%);
  margin-left: auto;
  margin-right: auto;
  box-sizing: border-box;
}
/* Force header to fill available width before max-width kicks in */
.header { align-self: stretch; }

    /* Desktop header: flex row — see .header-text-col */
    /* header-title font size controlled by --title-font-size CSS variable */
    body { margin-left: 13px; /* clearance for gatekeeper consent button (desktop) */ }
    /* Row 2: subtitle left, buttons right — flex row */
    .header-subtitle-row {
      display: flex;
      align-items: center;
      min-width: 0;
    }
    .header-subtitle-row > * + * { margin-left: 8px; }
    .header-song-subtitle {
      width: 100%;
      font-size: var(--subtitle-font-size, 1.0em); letter-spacing: 0.5px;
      font-style: italic; color: var(--muted);
    }
    .header-subtitle {
      flex: 1; min-width: 0;
      font-size: var(--subtitle-font-size, 1.0em); letter-spacing: 0.5px;
    }
    /* Desktop header: flex row — logo left, text column fills remaining space */
    .header {
      flex-direction: row;
      align-items: center;
      padding: 8px 14px;
    }
    /* Text column sits next to logo, fills remaining width */
    .header-text-col {
      display: flex;
      flex-direction: column;
      flex: 1;
      min-width: 0;
    }
    .header-text-col > * + * { margin-top: 2px; }

    /* Show settings bar, hide mobile instrument bar */
    .settings-bar { display: flex; }
    .instrument-bar { display: none; }

    /* Header: hide mobile-only items on desktop, show desktop-only items */
    .mobile-only { display: none !important; }
    .desktop-only { display: flex !important; }
    .mobile-action-row { display: none !important; }


    /* Song body: keep flex but column, so song-col fills height correctly.
       Float works inside #songContentWrap which is a block element. */
    .song-body {
      display: flex;
      flex-direction: column;
      flex: 1;
      min-height: 0;
      overflow: hidden;
    }

    /* song-col: flex column so top/bottom diagram slots and the scroller share space correctly */
    .song-col { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; overflow: hidden; }
    /* songContentWrap: flex-grows to fill remaining space and scrolls */
    #songContentWrap { flex: 1; min-height: 0; display: block; position: relative; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; }

    /* Wider modals on desktop so finger buttons fit on one line */
    .modal { border-radius: 12px; max-height: 90vh; max-width: 580px; }

    /* Chord diagram sidebar — floated right inside song-content.
       Text wraps around it naturally and expands to full width once float ends. */
    .chord-diagram-section {
      float: right;
      clear: right;
      width: fit-content;
      max-width: 320px;
      border-top: none;
      border-left: 1px solid var(--border);
      background: var(--surface);
      margin-left: 4px;
      margin-right: 0;
    }

    /* Clearfix so songContentWrap height contains the float */
    #songContentWrap::after {
      content: '';
      display: table;
      clear: both;
    }

    /* Hide diagram column header on desktop — controls in settings bar */

    .chord-diagrams-grid {
      display: grid;
      grid-template-columns: repeat(2, max-content);
      gap: 4px;
      padding: 4px;
    }
  }

  /* Thin scrollbar on chord diagram strips */
  #chordDiagramsTop { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
  #chordDiagramsTop::-webkit-scrollbar { height: 4px; }
  #chordDiagramsTop::-webkit-scrollbar-track { background: transparent; }
  #chordDiagramsTop::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }

  /* Pull the chord block up into #songContentWrap's own top padding (16px)
     so it sits flush against the header when visible — visually connected,
     though structurally still a separate, independently-scrolling element
     (a manual scroll reveals this, as expected). Only #chordDiagramsTop
     itself is affected; everything else (ads, lyrics) keeps its normal
     top spacing when chords aren't in the 'top' position. */
  #chordDiagramsTop { margin-top: -16px; }

  /* Top chord slot sticky during auto-scroll — stays at top of the scroll
     container's viewport while the song scrolls underneath. .scroll-sticky
     is added by JS when auto-scroll starts, removed on stop. This must be
     UNIVERSAL (not scoped to a media query) — a rule scoped only to the
     mobile @media block has no effect at all outside that width range,
     even when the class is correctly applied by JS. */
  #chordDiagramsTop.scroll-sticky { position: sticky; top: -16px; z-index: 10; }
  /* top:-16px (not 0) matches #chordDiagramsTop's own margin-top:-16px
     used in normal flow. Without this, sticky positioning sticks to
     #songContentWrap's PADDING edge (inset 16px by its own
     padding-top), and the negative margin that correctly pulls the
     element flush in normal document flow doesn't carry over once
     sticky. Mobile uses -12px margin instead — if a similar gap shows up
     there, the mobile media query's own #chordDiagramsTop rule would
     need the matching top:-12px override. */

  /* Chord hover tooltip */
  .chord-tooltip {
    position: fixed;
    z-index: 500;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.2);
    pointer-events: none;
    display: none;
    transition: opacity 0.1s;
  }
  .chord-tooltip.visible { display: block; }

  /* ── Right-click context menu ── */
  .ctx-menu {
    position: fixed;
    z-index: 600;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 6px 0;
    box-shadow: 0 4px 20px rgba(0,0,0,0.2);
    min-width: 200px;
    display: none;
  }
  .ctx-menu.open { display: block; }
  .ctx-menu-item {
    padding: 9px 16px;
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
    transition: background 0.1s;
  }
  .ctx-menu-item:hover { background: var(--surface2); }
  .ctx-menu-item.danger { color: var(--danger); }
  .ctx-menu-divider { height: 1px; background: var(--border); margin: 4px 0; }

  /* ── Chord voicing alternatives overlay ── */

  /* ── Page break ({np}/{new_page} ChordPro directive) — added
     2026-08-13, ported from pdfsong.php, where the equivalent rule
     already existed and worked correctly. song.css never had this at
     all: the dotted line rendered fine on screen (from the inline
     style on the <hr> itself, in song.js's own markup for this line
     type — not dependent on any class here), but nothing anywhere in
     this file ever told the browser's print engine this element
     should force an actual page break, so {np} only ever showed as a
     visual divider, never actually broke the page, specifically when
     using the browser's own print (Ctrl+P) rather than the separate
     Api2Pdf/pdfsong.php "Create PDF" pipeline, which already had this
     rule and worked fine. break-after is the modern CSS Fragmentation
     property; page-break-after is the older, still-widely-supported
     one — declared alongside it for broader browser coverage. */
  .page-break {
    display: block;
    page-break-after: always;
    break-after: page;
  }

  /* ══════════════════════════════════════════════
     PRINT STYLES
  ══════════════════════════════════════════ */
  @media print {

    /* {np}: keep the page-break behaviour, but the dotted line itself
       is a screen-only editing/preview aid — collapse it to nothing on
       the actual printed page, matching pdfsong.php's own established
       behaviour for the same element. */
    .page-break {
      page-break-after: always !important;
      break-after: page !important;
      height: 0 !important;
      margin: 0 !important;
      padding: 0 !important;
      border: none !important;
    }
    .page-break hr {
      display: none !important;
    }

    /* --paper-width is a screen-viewing convenience only (approximates
       A4 at 96dpi) — has nothing to do with an actual print
       destination's real page size, so it's deliberately not used
       here; fill 100% of whatever the print page actually is instead. */
    .header, .song-body {
      width: 100% !important;
      max-width: 100% !important;
    }

    #songContentWrap, .song-content { overflow: visible !important; height: auto !important; max-height: none !important; }

    .song-col.has-sidebar #chordDiagramsFloat { display: block !important; }
    .song-col.has-sidebar .chord-diagram-section {
      float: right !important;
      clear: right !important;
      position: static !important;
      width: fit-content !important;
      max-width: 320px !important;
      border-top: none !important;
      border-left: 1px solid var(--border) !important;
      border-bottom: none !important;
      margin-left: 4px !important;
      margin-right: 0 !important;
      overflow: visible !important;
      padding: 4px !important;
    }
    .song-col.has-sidebar .chord-diagrams-grid {
      display: grid !important;
      /* CHANGED (2026-09-17, per Jim's bug report): was hardcoded to
         repeat(2, ...), silently ignoring the Settings column-count
         picker in print even though it worked correctly on screen.
         Now reads the same --diagram-cols variable song.js sets on
         every render, defaulting to 2 only if that's somehow unset. */
      grid-template-columns: repeat(var(--diagram-cols, 2), max-content) !important;
      flex-direction: unset !important;
      flex-wrap: unset !important;
      width: auto !important;
      gap: 4px !important;
    }

    /* FIXED (2026-09-16, per Jim's bug report — an iPhone user's print
       preview showed no chord diagrams at all, on every song, diagram
       position "top"). Root cause: on any viewport under 767px wide —
       true for every iPhone — a separate, ordinary CSS media query
       (@media (max-width: 767px), not this print block) forces
       .chord-diagrams-grid into a single, non-wrapping row of
       unbounded width (flex-wrap:nowrap !important; width:max-content)
       specifically so it can be scrolled horizontally on-screen, and
       JS sets overflow-x:auto/overflow-y:hidden as an inline style on
       #chordDiagramsTop to make that scrolling work. That media query
       is a pure viewport-width check — it has no awareness of print at
       all, and evaluating it against the device's own screen width
       rather than the print page's width appears to be exactly what's
       happening here (a real, documented cross-engine inconsistency,
       not unique to this codebase). Print has no scrolling at all,
       so whatever doesn't fit in that single, narrow visible strip
       was never clipped-but-reachable the way it is on-screen — it
       simply never printed. A JS-side fix already exists
       (window.__printingNow, forcing the non-mobile code path during
       print — see applyDiagramPosition()'s own detailed comment in
       song.js) but depends on beforeprint/matchMedia firing at all,
       which has its own separate, already-documented gaps specifically
       when print is triggered via a mobile OS/share-sheet action
       rather than a page's own window.print() call — and even where
       that JS fix does fire correctly, it only ever touched the
       affected elements' inline styles, never this separate, unrelated
       viewport-width media query, which would still apply regardless.
       This CSS-only override doesn't depend on either mechanism —
       parallels the identical fix already established for the right/
       sidebar case immediately above, just for the top position. */
    #chordDiagramsTop {
      overflow-x: visible !important;
      overflow-y: visible !important;
    }
    #chordDiagramsTop .chord-diagrams-grid {
      flex-wrap: wrap !important;
      width: auto !important;
    }

    /* Hide ONLY UI chrome — menus, login, edit controls */
    .settings-bar,
    .instrument-bar,
    .edit-tabs,
    .modal-overlay,
    .sb-dropdown,
    .sb-overlay,
    #editScreen,
    #hamburgerModal,
    #chordModal,
    #instModal,
    #infoModal,
    .auth-link,
    #editBtn,
    #sbEditBtn,
    #sbFaveBtn,
    #mobFaveBtn,
    #autoSaveStatus,
    #mobAutoSaveBtn,
    #mobScrollPopupBtn,
    #mobChordsToggleBtn,
    .mobile-action-row,
    .mobile-only,
    #infoBtn,
    #hamburgerBtn,
    #backBtn,
    #diagPosBar,
    .ctx-menu,
    .chord-tooltip,
    /* Ad slots already carry class="noPrint" (see #ezoic-pub-ad-placeholder-101/102
       above) but the rule that actually hides them in print was missing —
       that's the bug. .noPrint is the general-purpose convention for this
       across the suite, so use it for any future ad/non-print content too. */
    .noPrint { display: none !important; }

    /* Ezoic's own auto-injected California/CCPA privacy notice —
       the attribute-selector attempt tried here originally matched
       nothing (confirmed via beta tester Ed's own console diagnostic:
       the container has no id/class at all, only the "Reject All"
       button inside it does), so the fix moved to song.js instead —
       hideEzoicPrivacyNoticeForPrint(), hooked into the existing
       beforeprint/matchMedia mechanism — which finds the element via
       that button's id and tags its actual container with .noPrint
       directly, the same class the rule above already handles.

       2026-08-31 update, per Jim: Ezoic support confirmed the
       container IS directly targetable after all, just not by id —
       via this data-attribute selector instead. Added here as the
       primary, direct fix; hideEzoicPrivacyNoticeForPrint() in
       song.js is left in place untouched as a fallback for now
       (redundant with this rule, not harmful — hiding an
       already-hidden element has no visible effect), pending
       confirmation from Ed before deciding whether it's still
       needed. */
    /* Gated on .ezoic-css-fix-active (added by song.js, unless the
       temporary ?ezoic_test=js_only diagnostic param is present — see
       that file's own comment) so this rule can be isolated from the
       JS-based mechanism below for Ed's A/B test. Remove the class
       requirement (just keep the bare attribute selector) once a
       mechanism is confirmed and the diagnostic scaffolding is
       cleaned up. */
    html.ezoic-css-fix-active [data-ezoic-role="privacy-policy"] { display: none !important; }

    /* First attempt at the "notice-sized blank space still visible after
       print" follow-up report (2026-09-07, per Jim/Ed) — display:none
       on the notice itself only removes THAT element from layout, not
       whatever wrapper Ezoic puts around it; if that wrapper carries
       its own padding/min-height/border, it keeps occupying space even
       with its content hidden inside it. This collapses the immediate
       parent instead, via :has() (well-supported everywhere including
       Safari since 15.4, so no fallback needed here).
       NOT YET CONFIRMED which ancestor level is actually the real
       culprit — Ed is doing the DevTools ancestor walk (Emulate CSS
       media type: print, inspect the blank area, check each ancestor's
       computed height/padding) to confirm. If the real wrapper turns
       out to be two levels up rather than the immediate parent, this
       specific selector won't catch it and will need adjusting once
       that's confirmed. */
    /* REVERTED (2026-09-07, per Jim) — the :has()-based attempt below
       truncated a real print preview on a live test (without the
       notice even present to test against). Most likely cause: the
       selector *:has(> [data-ezoic-role="privacy-policy"]) matches ANY
       element with the notice as a direct child — if Ezoic injects the
       notice straight onto <body> (a common pattern for overlays,
       specifically to escape any parent's stacking context), this
       would try to display:none the entire <body>, hiding the whole
       page rather than just the notice's own wrapper. Left here,
       commented out, rather than deleted outright, so the exact attempt
       is preserved for whoever picks this back up once Ed's DevTools
       ancestor-walk confirms the real, specific wrapper element —  at
       which point this should become a precise selector for that
       element specifically, not a blind "any ancestor" match.
    html.ezoic-css-fix-active *:has(> [data-ezoic-role="privacy-policy"]) { display: none !important; }
    */

    /* Keep header but hide the back/edit buttons within it */
    .header { position: static; border-bottom: 1px solid #ccc; padding: 8px 16px; }
    .header-title { font-size: 18pt; color: var(--accent) !important; }
    .header-subtitle { font-size: 10pt; color: #555; }

    /* Reset app shell for print */
    body { background: white; color: black; }
    #app { max-width: 100%; height: auto; overflow: visible; }
    /* display:block, not flex (2026-09-07, per Jim/Ed's {np} test — a
       reliable, on-demand reproduction of the pagination bug, unlike
       the original "sometimes happens" framing) — same reasoning as
       .song-body's own flex removal below, just one level higher up
       the ancestor chain. That earlier fix didn't resolve the
       pagination bug on its own; #songScreen (song-body's own direct
       parent) was left untouched at the time, still flex. A forced
       page-break-after happening on a descendant of a still-flex
       container is exactly the kind of case Chrome's flex+print
       fragmentation bugs are known to trip on, and the size of the
       gap Jim measured (~29 lines, not a small spacer) fits "a whole
       flex container's sizing went wrong" far better than the
       already-existing .song-body::after 8mm spacer theory.
       Confirmed safe to drop flex here for the same reason as
       .song-body: flex-direction:column only mattered for on-screen
       layout, and height:auto/overflow:visible (both already present)
       don't depend on flex at all.
       NOT YET CONFIRMED to fix the actual bug — testing against Jim's
       {np} reproduction case now. If this doesn't resolve it either,
       both flex-removal attempts become safe, low-risk cleanup
       regardless (neither serves any real purpose for print), but the
       real culprit is still open. */
    #songScreen { display: block !important; height: auto; overflow: visible; }
    /* display:block, not flex (2026-08-31, per Jim/Ed) — flex has
       well-documented, longstanding print-pagination bugs in Chrome
       (and historically Firefox): browsers can mis-measure content
       height and clip/truncate elements near page boundaries inside a
       flex container, a known issue referenced across multiple
       independent browser bug trackers, not something specific to
       this page. Very likely the actual root cause behind BOTH
       existing band-aids below (.song-body::after's min-height spacer,
       and .chord-diagram-wrap's break-inside:avoid having been removed
       elsewhere in this block) - two separate patches for what may be
       the same underlying problem.
       Confirmed safe to drop flex here: flex's only real purpose on
       .song-body was enabling on-screen scrolling (flex:1; min-height:
       0; overflow:hidden - see the non-print rule above), and every
       property that behaviour depended on is ALREADY explicitly
       overridden for print, at every level of this chain
       (#songContentWrap/.song-content further down get the identical
       overflow:visible/height:auto treatment). The chord-diagram
       sidebar layout doesn't depend on this either - it's a plain CSS
       float "inside #songContentWrap which is a block element" (see
       that element's own comment below), unaffected by .song-body's
       own display mode.
       Left the existing ::after spacer and the break-inside:avoid
       removal in place for now rather than also removing them in the
       same change - if this alone resolves the pagination bugs, both
       become safe to clean up as a confirmed follow-up; changing
       everything at once would make it impossible to tell which
       change actually fixed (or didn't fix) the problem from a single
       round of testing. */
    .song-body { display: block !important; overflow: visible; height: auto; }
    /* Trailing space after all printable content (2026, per Jim -
       CONFIRMED fixes the truncation bug). Hypothesis: Chromium's
       print engine can miscalculate total document height when a
       flex/SVG-heavy element is the very last thing in the document,
       clipping it at the final page boundary rather than correctly
       extending to a new page - margin-dependent, matching what Jim
       found. This deliberately reproduces the same "something follows
       the diagrams" effect Jim found fixed it naturally (adding a
       second-instrument section after the chords) for the case where
       nothing else genuinely follows them.
       15mm confirmed WORKING (fixed the truncation), 3mm confirmed
       TOO LITTLE (bottom of diagram still cut off) - 8mm is a middle
       ground, not yet confirmed either way. Trade-off is real either
       direction: too little risks the truncation bug returning, too
       much risks pushing an already-tight, borderline-fitting song
       onto an extra page it didn't need before. If 8mm isn't enough,
       move closer to 15mm rather than guessing further from scratch.
       min-height rather than padding-bottom, since padding on a flex
       column would just be additional space inside the flex box, not
       guaranteed to render as trailing content in every browser's
       fragmentation calculation the same way. */
    .song-body::after { content: ''; display: block; min-height: 8mm; }
    .song-col { overflow: visible; min-height: 0; }

    /* Keep chord colours in print */
    .chord-name, .chord-inline { color: var(--chord-color) !important; }
    /* Exclude chord names from PDF text layer so copy-paste extracts
       only lyrics. Chrome honours user-select:none in its PDF text layer. */
    .chord-name, .chord-inline, .chord-name-empty { user-select: none; }
    .section-label { border-bottom: 1pt solid #eee; padding-bottom: 2pt; margin-bottom: 4pt; }

    /* Chord diagram section keeps its colours */
    .chord-diagram-section { border: none; overflow: visible; }
    .chord-diagram-name { color: var(--chord-color) !important; }

    /* Float sidebar: only show if actually visible */
    #chordDiagramsFloat[style*="display:none"] { display: none !important; }

    /* Piano: only show when visible and has content */
    .piano-section.visible:not(:empty) { display: block !important; }
    #pianoGrid:empty + * { display: none !important; }

    /* Second instrument: only show if visible (class toggled by JS) */
    #secondInstSection { display: none !important; }
    #secondInstSection.print-visible { display: block !important; }

    /* Avoid page breaks inside content */
    /* break-inside is the modern CSS Fragmentation property;
       page-break-inside is the older, still-widely-supported one —
       declared alongside it for maximum browser coverage now that the
       flex-container gap above (which was the actual cause of these
       being silently ignored for chord diagrams) is fixed. */
    .lyric-line { page-break-inside: avoid; break-inside: avoid; }
    .section-label { page-break-after: avoid; break-after: avoid; }
    /* .chord-diagram-wrap's own break-inside:avoid removed (2026, per
       Jim's precise, reproducible finding — default Edge print margins
       (15mm/1.5cm, matching this file's own @page rule below) truncate
       chord diagrams mid-grid; custom margins with more available page
       height don't). Hypothesis: when a browser can't actually honour
       break-inside:avoid (the element is taller than the remaining
       page space), some Chromium print implementations clip the
       content at the page boundary instead of correctly pushing the
       whole element to the next page - matching both the margin-
       dependent reproduction and the exact visual symptom (a hard
       clip mid-diagram, with blank page space below where the next
       page's content should have started). Removing the avoid hint
       lets normal fragmentation apply instead - a diagram might
       occasionally split across a page boundary in some cases, but
       that's a far less severe failure than losing half its content
       silently. Not yet confirmed against a real print test. */

    /* CHANGED (2026-09-17, per Jim): @page margins were stacking with
       the browser print dialog's own margin preset and distorting
       output — Jim's fix is telling users to set the dialog's margin
       preset to "None" so the browser contributes nothing, and we
       supply the actual margin ourselves at the body-element level
       instead, since that applies unconditionally regardless of the
       dialog's setting. Left is a fixed 1cm default (matching
       pdfsong.php's own measured default). Right stays a CSS variable
       for the "Add Margin/Wide Margin to Right" toggles: 0 default,
       1cm, or 1.5cm. NOTE: the old 1.5cm top/bottom @page protection
       against the chord-diagram print-truncation bug is no longer in
       effect once users pick "None" — not addressed here, since Jim
       only asked about left/right; flagged for a follow-up if that
       bug resurfaces. */
    body { margin-left: 1cm !important; margin-right: var(--print-right-margin, 0) !important; }
  }

  /* ── Utility classes replacing inline flex+gap styles throughout the HTML
     (gap fallback for browsers without flex gap support, pre-Safari 14.1 /
     iOS 14.5). Each inline style="display:flex;...gap:Npx..." attribute in
     the markup has a matching class="gflex-..." added alongside it. ── */
  .gflex-c10 > * + * { margin-top: 10px; }
  .gflex-r12-wrap { margin: -6px; }
  .gflex-r12-wrap > * { margin: 6px; }
  .gflex-r14 > * + * { margin-left: 14px; }
  .gflex-r16 > * + * { margin-left: 16px; }
  .sb-row-tight { display: flex; align-items: center; margin-bottom: 6px; }
  .sb-row-tight > * + * { margin-left: 8px; }
  .gflex-c3 > * + * { margin-top: 3px; }
  .gflex-r4 > * + * { margin-left: 4px; }
  .gflex-r5-wrap { margin: -2.5px; }
  .gflex-r5-wrap > * { margin: 2.5px; }
  .gflex-c6 > * + * { margin-top: 6px; }
  .gflex-r6 > * + * { margin-left: 6px; }
  .gflex-r6-wrap { margin: -3px; }
  .gflex-r6-wrap > * { margin: 3px; }
  .gflex-r8 > * + * { margin-left: 8px; }
  .gflex-r8-wrap { margin: -4px; }
  .gflex-r8-wrap > * { margin: 4px; }

  label.gflex-r8 > input[type="checkbox"] { margin-right: 8px; }

/* ── Settings dropdown styles (was a <style> block mid-BODY) ── */
  /* ── Settings dropdown — polished legacy style ── */

  /* Dropdown container override */
  #sbViewDrop {
    border-radius: 10px !important;
    overflow: hidden;
    box-shadow: 0 8px 32px rgba(0,0,0,0.18) !important;
    border: 1px solid var(--border) !important;
    padding: 0 !important;
  }

  /* Header ribbon */
  .leg-header {
    background: var(--accent);
    color: var(--text-on-accent);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    padding: 7px 12px;
    display: flex;
    align-items: center;
    gap: 6px;
  }

  /* Section divider label */
  .leg-section {
    background: var(--surface2);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    padding: 4px 10px;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--muted);
    margin-top: 2px;
  }

  /* Row — selectble (has sub-menu) */
  .leg-row {
    display: flex;
    align-items: center;
    padding: 0 12px;
    height: 38px;
    border-bottom: 1px solid var(--border);
    gap: 8px;
    background: var(--surface);
    transition: background 0.1s;
    user-select: none;
  }
  .leg-row-select { cursor: pointer; }
  .leg-row-select:hover { background: rgba(204,34,34,0.05); }
  .leg-row-select:hover .leg-label { color: var(--accent); }
  .leg-row-select.open { background: rgba(204,34,34,0.07); }
  .leg-row-select.open .leg-label { color: var(--accent); }

  .leg-label {
    flex: 1;
    font-size: 12.5px;
    color: var(--text);
    white-space: nowrap;
    transition: color 0.1s;
  }
  .leg-val {
    font-size: 12px;
    color: var(--accent);
    font-weight: 700;
    white-space: nowrap;
  }
  .leg-arrow {
    font-size: 10px;
    color: var(--muted);
    transition: transform 0.2s;
    margin-left: 2px;
    flex-shrink: 0;
  }
  .leg-row-select.open .leg-arrow { transform: rotate(90deg); color: var(--accent); }

  /* Sub-menu panel */
  .leg-sub {
    display: none !important;
    background: var(--surface2);
    border-bottom: 1px solid var(--border);
    padding: 8px 12px;
  }
  .leg-sub.open {
    display: block !important;
    animation: legSlideDown 0.15s ease;
  }
  .leg-sub-btns {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
  }
  .leg-sub-btn {
    padding: 4px 11px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    font-size: 12px;
    font-family: 'DM Sans', sans-serif;
    transition: all 0.1s;
    white-space: nowrap;
  }
  .leg-sub-btn:hover {
    border-color: var(--accent);
    color: var(--accent);
    background: rgba(204,34,34,0.06);
  }
  .leg-sub-btn.leg-active {
    background: rgba(204,34,34,0.12);
    border-color: var(--accent);
    color: var(--accent);
    font-weight: 600;
  }

  /* Checkbox rows */
  .leg-check {
    display: flex !important;
    align-items: center;
    gap: 9px;
    padding: 0 12px;
    height: 36px;
    border-bottom: 1px solid var(--border) !important;
    font-size: 12.5px;
    color: var(--text) !important;
    cursor: pointer;
    width: 100%;
    box-sizing: border-box;
    background: var(--surface);
    transition: background 0.1s;
    user-select: none;
  }
  .leg-check * { color: var(--text) !important; }
  .leg-check:hover { background: rgba(204,34,34,0.05); }
  .leg-check input[type="checkbox"] {
    width: 15px;
    height: 15px;
    accent-color: var(--accent);
    cursor: pointer;
    flex-shrink: 0;
    margin: 0;
  }
  /* ADDED (2026-09-18, per Jim: "theme-ify the Files dropdown like the
     Settings dropdown"). The Files panel's checkbox rows were switched
     from the plainer .opt-checkbox-row to this same .leg-check family
     Settings already uses, for a consistent look (row height, hover
     highlight, border between items, themed checkbox accent colour).
     One wrinkle: .leg-check * above forces every child to var(--text)
     with !important, for the Settings rows this class was designed
     for (none of which have secondary text) — but several Files rows
     have a deliberately de-emphasised "*(PDF only)" annotation that
     would otherwise lose its distinct muted colour entirely. Two
     classes beats one for specificity, so this cleanly wins back the
     muted colour for just these notes without touching .leg-check's
     own behaviour anywhere else it's already used. Replaces an
     identical inline style repeated 26 times — same consolidation
     .opt-checkbox-row itself already did for its own repeated pattern. */
  .leg-check .leg-check-note {
    color: var(--muted) !important;
    font-size: 9px;
    font-weight: 400;
    opacity: 0.75;
    margin-left: 4px;
  }

  /* Footer buttons */
  .leg-footer {
    padding: 10px 12px;
    background: var(--surface2);
    border-top: 2px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 7px;
  }
  .leg-footer-row { display: flex; gap: 7px; }
  .leg-save-btn {
    flex: 1;
    padding: 8px 10px;
    background: var(--accent);
    color: var(--text-on-accent);
    border: none;
    border-radius: 7px;
    cursor: pointer;
    font-size: 12px;
    font-family: 'DM Sans', sans-serif;
    font-weight: 600;
    transition: opacity 0.15s;
  }
  .leg-save-btn:hover { opacity: 0.88; }
  .leg-remove-btn {
    flex: 1;
    padding: 8px 10px;
    background: transparent;
    color: var(--danger);
    border: 1px solid var(--danger);
    border-radius: 7px;
    cursor: pointer;
    font-size: 12px;
    font-family: 'DM Sans', sans-serif;
    transition: all 0.15s;
  }
  .leg-remove-btn:hover { background: rgba(184,74,56,0.08); }
  .leg-defaults-btn {
    width: 100%;
    padding: 8px 10px;
    background: transparent;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 7px;
    cursor: pointer;
    font-size: 12px;
    font-family: 'DM Sans', sans-serif;
    transition: all 0.15s;
  }
  .leg-defaults-btn:hover { border-color: var(--accent); color: var(--accent); }

  @keyframes legSlideDown {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  /* Unsaved-changes badge — deliberately attention-grabbing, not a
     passive status dot. Pulses only while genuinely unsaved; the saved
     (green) state stays static. */
  @keyframes faveSavedPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(204,34,34,0.5); }
    50%      { box-shadow: 0 0 0 5px rgba(204,34,34,0); }
  }
  #faveSavedDot.unsaved, #mobFaveSavedDot.unsaved { animation: faveSavedPulse 1.4s ease-in-out infinite; }

/* Consolidated repeated inline style: the exact same style="..."
   combination was repeated inline 24 times across Files-menu/settings
   checkbox rows (identical string, byte for byte). One class now, one
   place to adjust the look — kept alongside the existing gflex-r8
   class each row already had (the flex-gap Safari fallback), not
   replacing it. */
.opt-checkbox-row {
  display: flex; align-items: center; font-size: 12px;
  color: var(--text); cursor: pointer; padding: 2px 0;
}

/* Chord-conversion confirmation banner — see song.php for why this
   replaced a blocking native confirm(). */
.chord-convert-banner {
  background: var(--surface2);
  border: 1px solid var(--accent2);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 10px;
  font-size: 13px;
}
.chord-convert-banner-text {
  color: var(--text);
  margin-bottom: 8px;
  line-height: 1.4;
}
.chord-convert-banner-btns {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.chord-convert-banner-btns .btn-primary,
.chord-convert-banner-btns .btn-secondary {
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid var(--border);
}
.chord-convert-banner-btns .btn-primary {
  background: var(--accent2);
  color: var(--text-on-accent);
  border-color: var(--accent2);
}
.chord-convert-banner-btns .btn-secondary {
  background: var(--surface);
  color: var(--text);
}

/* ══════════════════════════════════════════════
   Long-press action tooltips (2026, per Jim) — see the matching JS
   (showActionTooltip/attachActionTooltips in song.js) for the full
   explanation. A small floating label shown near a top-area icon
   after a held touch, giving mobile users the equivalent of what
   desktop already gets for free from native title-attribute hover
   tooltips.
   ══════════════════════════════════════════════ */
.action-tooltip-bubble {
  position: fixed;
  z-index: 900;
  background: var(--text);
  color: var(--bg);
  font-family: 'DM Sans', sans-serif;
  font-size: 12px;
  font-weight: 500;
  padding: 6px 10px;
  border-radius: 6px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.25);
  pointer-events: none;
  max-width: 220px;
  line-height: 1.3;
  animation: actionTooltipFadeIn 0.12s ease-out;
}
@keyframes actionTooltipFadeIn {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: translateY(0); }
}
