﻿        /* --- Theming System ---
           Dark/light only now (Sepia mode and the old 18-option "Color
           Theme" accent picker were removed - see accent-* below). Switch
           mechanism is :root[data-theme="light"], matching the root CoA
           Apps site exactly (rather than this app's old body.light-mode
           class), so the same .theme-toggle-group/.theme-toggle-btn
           component and its shared "coa-theme" localStorage key work
           identically here. */
        :root {
            color-scheme: dark;
            /* User Interface Size (Settings > App Sizing Options) - set in
               JS (applyUiScale() in app.js) from the ui-size-select value:
               0.85 (Small), 1 (Medium - the app's existing look, unchanged),
               1.15 (Large). Every UI-chrome font-size in this file that
               should respond to it is written as
               "calc(Xrem * var(--ui-scale))" instead of a plain rem value.
               Passage text (#verse-text and its .size-*/heading styles)
               and the toolbar (--toolbar-scale below - independently
               adjustable, not part of this) are deliberately untouched by
               this one. */
            --ui-scale: 1;
            /* Dampened version of --ui-scale, anchored at 1 (Medium is
               identical either way) - moves 1/3 as much as --ui-scale in
               either direction (was 1/2), per explicit request. Only the
               Search/Menu buttons use this (see
               #search-btn/#account-settings-btn below); everything else
               still reads --ui-scale directly. */
            --ui-scale-dampened: calc(1 + (var(--ui-scale) - 1) * 0.3333);
            /* Chapter-nav strip's Prev/Next arrows and audio button - own
               scale, separate from --ui-scale-dampened above so tuning one
               never moves the other. Tracks --ui-scale at full rate, but
               clamped between 0.75 (its Small size - a floor, so it can't
               keep shrinking as far as the reference text's own emergency
               shrink-to-fit does at Extra Small/on narrow screens) and 1
               (its Medium size - a ceiling, so it never grows past that at
               Large/Extra Large). The strip must always fit the viewport,
               and the reference text is the one that actually flexes to
               make room for that - not these icons, which stay fixed
               between this floor and ceiling regardless of available
               space. */
            --chapter-nav-icon-scale: clamp(0.75, var(--ui-scale), 1);
            /* Toolbar Size (Settings > App Sizing Options) - set in JS
               (applyToolbarScale() in app.js) from the toolbar-size-select
               value: 0.4 (Extra Small), 0.6 (Small), 0.7 (Medium -
               default), 0.85 (Large), 1 (Extra Large). Only
               .toolbar-btn/.toolbar-icon (the bottom Play/Scroll/Notes/Mic
               bar) read this - everything else's size still comes from
               --ui-scale above, independent of this one. Matches the
               Medium default exactly, so a brand-new user (nothing in
               localStorage yet, so the flash-prevention inline <head>
               script in app.html has nothing to apply) never sees a
               flash from this base value to the real default. */
            --toolbar-scale: 0.7;
            /* Default (Dark Mode) */
            /* Matches the root CoA Apps site's --bg-color exactly (was pure
               black before). */
            --bg-body: #121212;
            --bg-card: #111827; /* gray-900 */
            --bg-expanded: #1F2937; /* Lighter for expanded state */
            --bg-input: #1F2937; /* gray-800 */
            --bg-hover: #374151; /* gray-700 */
            --text-main: #FFFFFF;
            --text-muted: #9CA3AF; /* gray-400 */
            /* Deliberate middle tier between the two above, for supporting
               text that shouldn't shout as loud as --text-main but was too
               faint at --text-muted (Stats labels and captions). */
            --text-secondary: #D1D5DB; /* gray-300 */
            /* Passage text color options (Settings > Text Options > Text
               Color) - three explicit tiers, dark mode: pure white (max) ->
               gray-200 (soft) -> gray-300 (muted). --text-color-max matches
               --text-main exactly here (both pure white) - it only needs to
               diverge from --text-main in light mode (see below). */
            --text-color-max: #FFFFFF;
            --text-color-soft: #E5E7EB; /* gray-200 */
            --text-color-muted: #D1D5DB; /* gray-300 */
            /* Highlight opacity (Settings > Passage Appearance > Highlight
               Colors Brightness) - Default here; Soft/Bright/Very Bright
               override this via a class on #verse-text (see
               applyHighlightBrightness() in app.js). Consumed by the
               rgb(from ...) rules above instead of each --highlight-X
               variable's own baked-in alpha. Same value in both themes for
               every tier, so none of these need a light-mode override. */
            --highlight-alpha: 0.35;
            /* Matches the root CoA Apps site's --border-light exactly, so
               every border here (modals, cards, dividers) reads as the same
               gray as the rest of the site, not this app's own darker gray. */
            --border-color: #aaaaaa;

            /* Button Defaults (Dark) */
            --btn-bg: #111827; /* Very Dark Gray */
            --btn-hover: #1F2937; /* Dark Gray */
            --btn-text: #FFFFFF;
            --btn-icon: #FFFFFF;

            /* Mic Button Defaults (Dark) */
            --mic-bg: #1F2937;
            --mic-text: #FFFFFF;
            --mic-border: var(--accent-primary);

            /* CoA Apps accent colors (pink/green/yellow) - same hex values
               as the root site's --accent-pink/--accent-green/--focus-color,
               replacing the old single yellow reused for every accent
               purpose. Pink is the general-purpose border/icon/text accent,
               green is the secondary/bolder accent, yellow is reserved for
               focus rings only (matches sitewide convention). */
            --accent-primary: #f472b6;
            /* Slightly lighter green (was #4ade80/green-400) - green-300,
               one step lighter, already used elsewhere in the app as
               --highlight-green so it's a known-good shade here too. */
            --accent-secondary: #86efac;
            --accent-focus: #fdfd96;
            --error-color: #f87171;

            /* User-applied verse highlights (Verse Actions modal) - 20
               presets, translucent so the verse text stays readable
               underneath. Bright-on-dark tints here, deep-on-light tints in
               the light override below.
               PRE-0.35-NORMALIZATION RECORD (dark mode), kept here to
               revert to if the flat 0.35 below doesn't work out -
               everything was 0.3 except red/gray/dark-purple (0.35),
               dark-blue/dark-green (0.4), and dark-teal (0.45), each
               bumped individually because the color read too weak/washed-
               out at plain 0.3 against a dark background:
                 yellow:245,255,0,0.3   green:74,222,128,0.3
                 pink:244,114,182,0.3   blue:96,165,250,0.3
                 orange:251,146,60,0.3  purple:192,132,252,0.3
                 red:220,38,38,0.35     teal:45,212,191,0.3
                 cyan:34,211,238,0.3    gray:156,163,175,0.35
                 brown:217,119,6,0.3    magenta:217,70,239,0.3
                 lavender:216,180,254,0.3      dark-purple:147,51,234,0.35
                 light-pink:251,207,232,0.3    dark-pink:244,63,165,0.3
                 dark-red:248,113,113,0.3      dark-teal:19,78,74,0.45
                 dark-blue:30,64,175,0.4       dark-green:21,128,61,0.4 */
            --highlight-yellow: rgba(245, 255, 0, 0.3);
            --highlight-green: rgba(134, 239, 172, 0.3); /* green-300 - a bit lighter than the previous green-400 */
            --highlight-pink: rgba(244, 114, 182, 0.3);
            --highlight-blue: rgba(96, 165, 250, 0.3);
            --highlight-orange: rgba(251, 146, 60, 0.3);
            --highlight-purple: rgba(192, 132, 252, 0.3);
            --highlight-red: rgba(220, 38, 38, 0.3);
            --highlight-teal: rgba(45, 212, 191, 0.3);
            --highlight-cyan: rgba(34, 211, 238, 0.3);
            --highlight-gray: rgba(156, 163, 175, 0.3);
            --highlight-brown: rgba(217, 119, 6, 0.3);
            --highlight-magenta: rgba(217, 70, 239, 0.3);
            --highlight-lavender: rgba(182, 160, 252, 0.3);
            --highlight-dark-purple: rgba(126, 34, 206, 0.3); /* purple-700 - a tad darker than purple-600 */
            --highlight-light-pink: rgba(249, 168, 212, 0.3);
            --highlight-dark-pink: rgba(219, 39, 119, 0.3); /* pink-600 - a tad darker than the previous shade */
            --highlight-dark-red: rgba(127, 29, 29, 0.3); /* was a lighter red than --highlight-red itself (backwards from the name) - now genuinely darker, matching every other dark-* pair and light mode's own already-correct dark-red value */
            --highlight-dark-teal: rgba(17, 94, 89, 0.3); /* teal-800 - a tad bit lighter than before (teal-900) */
            --highlight-dark-blue: rgba(30, 64, 175, 0.3);
            --highlight-dark-green: rgba(21, 128, 61, 0.3);
            --highlight-light-blue: rgba(191, 219, 254, 0.3); /* blue-200 - paler than --highlight-blue (blue-400), same "light-" convention as --highlight-light-pink */
            --highlight-bright-green: rgba(34, 197, 94, 0.3); /* green-500 - a vivid, saturated green between --highlight-green (green-400) and --highlight-dark-green (green-700) */
            --highlight-light-yellow: rgba(254, 240, 138, 0.3); /* yellow-200 - the previous near-white value (255,255,224) was too close to white to read as yellow at all once blended at low alpha over a dark background; this stays visibly yellow-toned */
            --highlight-dark-yellow: rgba(212, 175, 55, 0.3); /* a true gold tone (was yellow-600, more amber/orange-leaning and a touch darker) */

            /* Search-term highlighting (Search results/Type Command "Search
               for a word/phrase") - a fixed, vibrant yellow, deliberately
               NOT var(--highlight-yellow) (excluded from the Highlight
               Colors setting and from any general "yellow"/"green" UI
               color tweaks; kept as its own single dedicated value). Dark
               mode needs a deep, saturated gold rather than a pale/bright
               yellow specifically because the marked text itself stays
               --text-main (white here) - a bright yellow background would
               make white text nearly unreadable on top of it. This shade
               (#8f6b00) is the most saturated/vibrant gold that still
               clears WCAG AA (4.92:1) against white text. */
            --search-highlight-bg: #8f6b00;

            /* Desktop Page Width Variable */
            --desktop-width: 75%;
        }

        /* Light Mode Overrides */
        :root[data-theme="light"] {
            color-scheme: light;
            /* Matches the root CoA Apps site's --bg-color exactly. */
            --bg-body: #f5f5f5;
            --bg-card: #FFFFFF;
            --bg-expanded: #F9FAFB; /* Slightly off-white for expanded */
            --bg-input: #FFFFFF;
            --bg-hover: #E5E7EB; /* gray-200 */
            --text-main: #111827; /* gray-900 */
            --text-muted: #4B5563; /* gray-600 */
            --text-secondary: #374151; /* gray-700 */
            /* Highest Contrast is pure black here - deliberately darker
               than --text-main (gray-900, used everywhere else in the
               app's UI) so all three tiers stay visually distinct from
               each other in the passage text specifically: black (max) ->
               gray-800 (soft) -> gray-700 (muted). */
            --text-color-max: #000000;
            --text-color-soft: #1F2937; /* gray-800 */
            --text-color-muted: #374151; /* gray-700 */
            /* Light mode reads slightly more opaque than dark mode at the
               same alpha (light backgrounds show highlight tint more
               readily), so each Highlight Colors Brightness tier gets its
               own lower alpha here. */
            --highlight-alpha: 0.3;
            --border-color: #444444;

            /* Button Overrides (Light) */
            --btn-bg: #F3F4F6; /* Light Gray */
            --btn-hover: #E5E7EB; /* Slightly Less Light Gray */
            --btn-text: #111827;
            --btn-icon: #000000; /* Black Icon */

            /* Mic Button Overrides (Light) - Now matches btn-bg */
            --mic-bg: #F3F4F6;
            --mic-text: #000000;
            --mic-border: var(--accent-primary);

            --accent-primary: #800d48;
            /* Slightly lighter green (was #097732) - green-700, one step
               lighter, already used elsewhere in the app as
               --highlight-dark-green so it's a known-good shade here too. */
            --accent-secondary: #15803d;
            --accent-focus: #9f9f03;
            --error-color: #b91c1c;

            /* PRE-0.35-NORMALIZATION RECORD (light mode), kept here to
               revert to if the flat 0.35 below doesn't work out -
               everything was 0.3 except yellow/dark-teal (0.35), and
               lavender/light-pink (0.25, pulled DOWN instead of up - a
               pale color at the normal 0.3 read too strong/opaque against
               a light background):
                 yellow:171,179,0,0.35        green:9,119,50,0.3
                 pink:128,13,72,0.3           blue:29,78,216,0.3
                 orange:194,65,12,0.3         purple:126,34,206,0.3
                 red:153,27,27,0.3            teal:15,118,110,0.3
                 cyan:14,116,144,0.3          gray:75,85,99,0.3
                 brown:120,53,15,0.3          magenta:134,25,143,0.3
                 lavender:109,40,217,0.25     dark-purple:88,28,135,0.3
                 light-pink:219,39,119,0.25   dark-pink:157,23,77,0.3
                 dark-red:127,29,29,0.3       dark-teal:17,94,89,0.35
                 dark-blue:30,58,138,0.3      dark-green:20,83,45,0.3 */
            --highlight-yellow: rgba(171, 179, 0, 0.2);
            --highlight-green: rgba(34, 197, 94, 0.2); /* green-500 - lighter/brighter than before, and now correctly lighter than "Green" (bright-green, green-600) too - the old value was actually darker than it, backwards */
            --highlight-pink: rgba(219, 39, 119, 0.2); /* pink-600 - previous pink-700 read as too close to dark-pink; shifted the whole family a step lighter (light-pink/pink) for real separation at every step */
            --highlight-blue: rgba(29, 78, 216, 0.2);
            --highlight-orange: rgba(194, 65, 12, 0.2);
            --highlight-purple: rgba(147, 51, 234, 0.2); /* purple-600 - previous value read as nearly identical to lavender; shifted the whole family a step lighter (lavender/purple) for real separation at every step */
            --highlight-red: rgba(153, 27, 27, 0.2);
            --highlight-teal: rgba(15, 118, 110, 0.2);
            --highlight-cyan: rgba(14, 116, 144, 0.2);
            --highlight-gray: rgba(75, 85, 99, 0.2);
            --highlight-brown: rgba(120, 53, 15, 0.2);
            --highlight-magenta: rgba(134, 25, 143, 0.2);
            --highlight-lavender: rgba(192, 132, 252, 0.2); /* purple-400 - lighter than the new --highlight-purple, clearly separated */
            --highlight-dark-purple: rgba(74, 20, 115, 0.2); /* a tad darker than purple-900 */
            --highlight-light-pink: rgba(244, 114, 182, 0.2); /* pink-400 - lighter than the new --highlight-pink, clearly separated */
            --highlight-dark-pink: rgba(131, 24, 67, 0.2); /* pink-900 - a tad darker than pink-800 */
            --highlight-dark-red: rgba(127, 29, 29, 0.2);
            --highlight-dark-teal: rgba(22, 108, 102, 0.2); /* a tad bit lighter than before (teal-800), still clearly darker than plain teal (teal-700) */
            --highlight-dark-blue: rgba(30, 58, 138, 0.2);
            --highlight-dark-green: rgba(20, 83, 45, 0.2);
            --highlight-light-blue: rgba(59, 130, 246, 0.2); /* blue-500 - brighter/lighter than --highlight-blue (blue-700), same "light-" convention as --highlight-light-pink */
            --highlight-bright-green: rgba(22, 163, 74, 0.2); /* green-600 - a vivid, saturated green between --highlight-green and --highlight-dark-green */
            --highlight-light-yellow: rgba(253, 224, 71, 0.2); /* yellow-300 - previous yellow-500 read as darker than --highlight-yellow itself instead of lighter; this is clearly brighter */
            --highlight-dark-yellow: rgba(184, 134, 11, 0.2); /* a true gold tone (was yellow-800) - a bit lighter and less brown/amber-leaning */

            /* Search-term highlighting - a fixed, vibrant yellow (see the
               dark-mode declaration above for why this is its own
               variable). Near-black text (--text-main here) reads great
               on a bright yellow, so light mode can afford to be much
               more vivid than dark mode's deep gold - 12.71:1 contrast. */
            --search-highlight-bg: #fdd835;
        }

        /* Tailwind's preflight used to apply this globally - without it,
           any bordered/padded element defaults to content-box and renders
           wider than its declared width/100% (border+padding get added on
           top instead of included), which is exactly why the passage card
           was overflowing wider than the header bar on mobile. */
        *, *::before, *::after {
            box-sizing: border-box;
        }
        /* Removes the default blue flash mobile browsers paint over any
           tappable element on touch (verses, buttons, links, everywhere) -
           purely a WebKit/Chromium touch-feedback color, not a focus
           indicator, so removing it doesn't affect keyboard focus rings or
           screen reader behavior at all. */
        html {
            -webkit-tap-highlight-color: transparent;
        }

        /* Status message color-coding (Paused/Reading/errors) - real,
           semantic class names, not old Tailwind color-utility lookalikes.
           updateStatus(msg, color) in app.js passes one of these. */
        .status-warning { color: var(--accent-focus) !important; }
        .status-success { color: var(--accent-secondary) !important; }
        .status-error { color: var(--error-color) !important; }
        .status-muted { color: var(--text-muted) !important; }

        /* Theme Utility Classes */
        .accent-text { color: var(--accent-primary) !important; }
        .accent-text-dim { color: var(--text-main) !important; } 
        .accent-text-bold { color: var(--accent-secondary) !important; }
        
        .accent-border { border-color: var(--accent-primary) !important; }
        .accent-border-bold { border-color: var(--accent-secondary) !important; }
        .accent-border-t { border-top-color: var(--accent-focus) !important; }
        /* A handful of text fields/textareas (manual-input, verse-note-
           input, hn-search-input, fallback-input) carry .accent-border
           explicitly - element+class beats the plain class above so these
           fall in line with every other field's yellow border too. */
        input.accent-border, textarea.accent-border { border-color: var(--accent-focus) !important; }

        /* The passage reference at the top ("John 1") reads as plain
           white/black (--text-main) instead of the .accent-text-bold
           green it inherited by default - an ID selector to win over that
           !important utility class without touching its other user (the
           Welcome modal's "Important Note!" label). */
        #reference-header { color: var(--text-main) !important; }

        .accent-ring:focus-visible {
            outline-color: var(--accent-focus) !important;
        }

        /* Was a Tailwind utility with no hand-written fallback - now that
           Tailwind is gone, every class="hidden" toggle across the page
           (scripture-display, full-chapter-btn, the welcome/fallback/
           copyright modals, etc.) needs this to actually work. !important
           because it must reliably win regardless of whatever else styles
           the element's display. */
        .hidden { display: none !important; }
        /* Same story as .hidden above - the welcome/fallback modals toggle
           between class="hidden" and class="flex" in app.js, and .flex was
           also a pure Tailwind utility with nothing behind it now. */
        .flex { display: flex !important; }

        /* Screen-reader-only announcer text (#sr-announcer) - was silently
           relying on Tailwind's .sr-only utility, which left it visible on
           screen once the CDN script was removed. */
        .sr-only {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0, 0, 0, 0);
            white-space: nowrap;
            border: 0;
        }

        /* --- Accessibility & High Contrast ---
           Matches the CoA Apps sitewide focus ring exactly (root style.css/
           Access-a-Lyrics: *:focus-visible { outline: 3px solid
           var(--focus-color); outline-offset: 3px; }) - was 4px/accent-
           primary(pink)/:focus (not :focus-visible) before. */
        :focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 3px;
        }
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            position: relative;
            padding-top: 0 !important;
            background-color: var(--bg-body);
            color: var(--text-main);
            font-family: 'Verdana', 'Segoe UI', sans-serif;
            /* Sitewide floor for body text - matches the dropdowns' own
               19px (select { font-size: 1.1875rem } below), so nothing
               (notes, highlights, list items, modal copy) reads smaller
               than the dropdowns do. Anything that needs to be bigger
               still overrides this; small deliberate metadata (dates,
               badges) still overrides it smaller on purpose. rem here
               (1.1875rem = 19px against the 16px root) rather than a
               flat px value so this - and everything that inherits it -
               respects the browser/OS's own text-size preference, and so
               a future root-level UI scale multiplier can affect it too;
               computed size is identical to the old 19px right now. */
            font-size: calc(1.3rem * var(--ui-scale));
            transition: background-color 0.3s, color 0.3s;
        }
        /* Large Touch Targets */
        .touch-target {
            min-height: 80px; 
            min-width: 80px;
            touch-action: manipulation;
            cursor: pointer;
            -webkit-tap-highlight-color: transparent;
            user-select: none;
            transition: transform 0.1s, background-color 0.2s;
        }
        .touch-target:active {
            transform: scale(0.95);
        }
        /* Overrides .touch-target's 80px minimum so this button matches the
           44px size of the CoA Apps site-wide mobile hamburger (.nav-toggle
           in the root style.css). #search-btn shares this - both buttons in
           .app-top-bar-actions need to match each other exactly, not just
           the site-wide hamburger. */
        #account-settings-btn,
        #search-btn {
            /* Scales with User Interface Size at HALF the normal rate
               (--ui-scale-dampened, not --ui-scale directly) - 44px is
               still the Medium/default look, unchanged, per explicit
               request. */
            width: calc(44px * var(--ui-scale-dampened));
            height: calc(44px * var(--ui-scale-dampened));
            min-width: calc(44px * var(--ui-scale-dampened));
            min-height: calc(44px * var(--ui-scale-dampened));
            display: flex;
            align-items: center;
            justify-content: center;
            border-width: 2px;
            border-style: solid;
            border-radius: 8px;
            cursor: pointer;
            flex-shrink: 0;
        }
        #account-settings-btn svg,
        #search-btn svg {
            width: calc(22px * var(--ui-scale-dampened));
            height: calc(22px * var(--ui-scale-dampened));
        }
        /* Visible "Menu"/"Search" text next to the icon - desktop only
           (matches the existing min-width:768px "desktop" breakpoint used
           elsewhere in this file). Hidden on narrower/touch-first
           viewports to keep these two buttons compact there; the
           accessible name (aria-label="Menu"/"Search" on the buttons
           themselves) stays exactly the same on every viewport, so
           screen reader users get the identical, simpler name either way
           regardless of whether this text happens to be visible. */
        .btn-desktop-label {
            display: none;
        }
        @media (min-width: 768px) {
            #account-settings-btn,
            #search-btn {
                width: auto;
                padding: 0 16px;
            }
            .btn-desktop-label {
                display: inline;
                margin-left: 8px;
                font-weight: 700;
                /* Never had a font-size at all before - buttons don't
                   inherit one by default, so this was silently stuck at
                   the browser's own default, not scaling with anything.
                   Damped at half the normal --ui-scale rate (own inline
                   calc, not the shared --ui-scale-dampened var used
                   elsewhere - that one's at 1/3 now), per explicit
                   request. */
                font-size: calc(0.95rem * (1 + (var(--ui-scale) - 1) * 0.5));
            }
        }

        /* Unified Button Theme Class */
        .btn-theme {
            background-color: var(--btn-bg);
            color: var(--btn-text);
            /* Border color is handled by specific utilities (accent-border) */
        }
        /* filter:brightness (not a var(--btn-hover) color swap anymore)
           is a real "brightening" effect - it scales whatever's already
           there (background, border, icon) up together instead of
           replacing the background with a fixed color, and automatically
           keeps working the same way in both themes. Per explicit
           feedback this needed to be MORE intense than the original
           var(--btn-hover) swap, not a different kind of effect (a pink
           fill was tried and reverted - too different from what was
           actually wanted). */
        .btn-theme:hover, .btn-theme:focus-visible {
            filter: brightness(1.4);
        }
        .btn-theme svg {
            color: var(--btn-icon);
            stroke: var(--btn-icon);
        }
        /* Specific override for filled icons like Play/Pause */
        .btn-theme svg[fill="currentColor"] {
            fill: var(--btn-icon);
        }

        /* ACTIVE STATE (Listening / Reading) 
            1. Red Background
            2. White Icon/Text
            3. Theme Color Border (Crucial)
        */
        .state-active-red {
            background-color: #DC2626 !important; /* Red 600 */
            color: #FFFFFF !important;
            border-color: var(--accent-primary) !important; /* Force theme border */
        }
        /* No fill override here - the mic icon is stroke-only
           (fill="none" on the SVG itself); forcing fill:#FFFFFF was
           turning its outline into a solid silhouette, changing its
           apparent shape between idle and listening states. */
        .state-active-red svg {
            color: #FFFFFF !important;
            stroke: #FFFFFF !important;
        }

        /* Bottom toolbar - Play/Pause, Bible Navigation, Highlights & Notes,
           Microphone. Same equal-width-flex-buttons/minimal-padding idea as
           Access-a-Lyrics' sticky bottom bar, just 4 buttons instead of 5.
           Fixed (not sticky) to the viewport bottom - unlike Access-a-Lyrics'
           bar, this app's content above it can be very short before a
           passage loads, and a sticky bar just sits at wherever that short
           content ends rather than the actual bottom of the screen, causing
           a visible jump down once a passage loads and pushes it further
           down the page. Fixed avoids that entirely at the cost of no
           longer scrolling away past the footer - #app-container's bottom
           padding below is sized to keep the footer clear of it instead. */
        .app-toolbar {
            position: fixed;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 40;
            display: flex;
            /* Was 6px (and 7px before that) - tightened per explicit
               request, since the buttons themselves got bigger with the
               Large default. */
            gap: 4px;
            /* Matches #app-container's own 700px cap below, so the two
               stay the same width and stay aligned with each other on
               wide screens - the narrower cap also leaves more gutter on
               the left for Verse Actions to fit into (see its own
               left-anchored positioning further down this file). */
            max-width: 700px;
            margin: 0 auto;
            /* Bottom padding includes the device's safe-area inset so the
               buttons stay clear of the phone's home-indicator/gesture bar
               instead of sitting right underneath it (env() resolves to 0 on
               devices without one, so this is a no-op there). */
            padding: 2px 24px calc(10px + env(safe-area-inset-bottom, 0px));
            background-color: var(--bg-body);
            /* Same Chromium position:fixed/sticky jitter fix as
               .app-top-bar, for the same reason. */
            will-change: transform;
            transform: translateY(0);
            /* Slower + an ease-out curve (was 0.25s ease, which read as
               closer to an instant snap than an actual slide) - per
               explicit request for a more noticeable slide effect. */
            transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
        }
        /* Auto-hide on scroll (Other App Options > Hide Bottom Toolbar on
           Scroll - see initAutoHideBar() in app.js), same pattern as
           .app-top-bar.header-hidden but sliding down instead of up, since
           this bar is anchored to the bottom edge instead of the top. */
        .app-toolbar.toolbar-hidden { transform: translateY(100%); }
        .app-toolbar.toolbar-hidden:focus-within { transform: translateY(0); }
        @media (prefers-reduced-motion: reduce) {
            .app-toolbar { transition: none; }
        }
        /* Floating Auto Scroll button (Passage Appearance setting, off by
           default) - a tall, skinny rectangle anchored to the right edge
           like a scrollbar thumb, roughly half the viewport height and
           vertically centered. Pink at rest, green while actively
           scrolling (see startAutoScroll()/stopAutoScroll() in app.js).
           Sits above the top bar/toolbar (z-40) but below every modal
           (z-60+), so an open modal correctly covers it. */
        #auto-scroll-btn {
            position: fixed;
            top: 50%;
            right: 2px;
            transform: translateY(-50%);
            z-index: 45;
            width: 21px;
            height: 50vh;
            max-height: 420px;
            min-height: 200px;
            display: flex;
            align-items: center;
            justify-content: center;
            border: 2px solid var(--accent-primary);
            border-radius: 8px;
            background-color: var(--accent-primary);
            color: #FFFFFF;
            opacity: 0.2;
            cursor: pointer;
        }
        #auto-scroll-btn[hidden] {
            display: none;
        }
        #auto-scroll-btn[aria-pressed="true"] {
            border-color: var(--accent-secondary);
            background-color: var(--accent-secondary);
            color: #FFFFFF;
        }
        .toolbar-btn {
            flex: 1 1 0;
            /* Without this, flex:1 1 0 can't shrink a button below its
               icon's own natural size (flex items default to min-width:
               auto, which floors shrinking at the content's intrinsic
               size) - however many buttons are in the toolbar (4, 5, 6+
               via the beta Toolbar Icon Count preview), this is what lets
               them actually squeeze down to fit and stay equal widths,
               instead of the widest one (Bible Navigation's bigger icon)
               refusing to shrink as much as the rest. */
            min-width: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            /* px (not rem), scaled by its own --toolbar-scale (Settings >
               App Sizing Options > Toolbar Size) rather than the general
               --ui-scale everything else in this file uses - these are
               icon-only buttons with no text label, so there's no
               font-size to drive their size the way calc(Xrem *
               var(--ui-scale)) does elsewhere, and the user asked for
               toolbar size to be independently adjustable from the rest
               of the UI's size. */
            min-height: calc(60px * var(--toolbar-scale));
            padding: 2px 1px;
            border-width: 2px;
            border-style: solid;
            border-radius: 12px;
            cursor: pointer;
            overflow: visible;
        }
        /* Slight yellow-tinted background, per explicit request - same
           color-mix convention used elsewhere (e.g. .app-btn-primary). */
        @supports (background-color: color-mix(in srgb, red, blue)) {
            .toolbar-btn {
                background-color: color-mix(in srgb, var(--accent-focus) 15%, var(--btn-bg));
            }
        }
        /* Same [hidden]-vs-author-origin-display fix used elsewhere in this
           file (#verse-range-add-btn, .more-colors-toggle-btn, etc.) -
           .toolbar-btn's own unconditional display:flex (author-origin)
           otherwise beats the browser's default [hidden]{display:none}
           (user-agent origin) regardless of specificity, so toggling
           .hidden in JS (see applyToolbarIconCount() in app.js) did nothing
           - a placeholder that had ever been shown once would never hide
           again afterward. */
        .toolbar-btn[hidden] {
            display: none;
        }
        /* width:100% (capped by max-width) instead of a flat fixed width -
           lets the icon shrink along with its button once there isn't room
           for every button at full size (see .toolbar-btn's min-width: 0
           above), rather than forcing an overflow. height:auto preserves
           each icon's own natural aspect ratio as it shrinks - these are
           all square source icons already, so this doesn't change
           anything at full size, only once space gets tight. */
        .toolbar-icon {
            width: 100%;
            max-width: calc(46px * var(--toolbar-scale));
            height: auto;
            display: block;
            pointer-events: none;
        }
        /* Beta-only placeholder icon (see #toolbar-placeholder-btn/-2 in
           app.html) - a plain filled circle, same shrink-to-fit treatment
           as .toolbar-icon above so it squeezes down along with its button
           too. aspect-ratio:1 (a real icon's height:auto instead relies on
           its own source image/viewBox proportions - a plain <span> has
           none, so this is what keeps it circular as it shrinks). */
        .toolbar-placeholder-icon {
            width: 100%;
            max-width: calc(28px * var(--toolbar-scale));
            aspect-ratio: 1;
            border-radius: 50%;
            background-color: var(--accent-primary);
            display: block;
            pointer-events: none;
        }
        /* Play/Pause - white (near-black in light mode, via var(--text-main))
           triangle icon when idle, pink pause icon when playing. */
        .play-pause-btn {
            background-color: var(--bg-card);
            /* Yellow again (was green, was yellow before that), per
               explicit request. */
            border-color: var(--accent-focus);
            color: var(--text-main);
        }
        @supports (background-color: color-mix(in srgb, red, blue)) {
            .play-pause-btn {
                background-color: color-mix(in srgb, var(--accent-focus) 15%, var(--bg-card));
            }
        }
        .play-pause-btn[aria-pressed="true"] {
            color: #FFFFFF;
            background-color: #DC2626;
        }
        .play-pause-btn:hover, .play-pause-btn:focus-visible {
            filter: brightness(1.1);
        }
        /* All four toolbar buttons share the same yellow border again
           (was green, was yellow before that) - the icons themselves are
           what tell them apart, not the border color. ID selectors needed
           to beat .accent-border's !important. */
        #toolbar-notebook-btn,
        #toolbar-bible-nav-btn,
        #toolbar-highlights-btn,
        #toolbar-mic-btn {
            border-color: var(--accent-focus) !important;
        }
        /* Reading-plan/tracker placeholder icon - always --text-main (was
           green). #toolbar-bible-nav-btn .bible-nav-book-icon (ID + class,
           rather than the class alone) is needed to beat .btn-theme svg's
           own color/stroke rules just above (which use --btn-icon, not
           --text-main - close but not identical in light mode) - both
           color AND stroke need overriding here, not just color, since
           .btn-theme svg sets stroke directly rather than leaving it to
           inherit via currentColor. */
        #toolbar-bible-nav-btn .bible-nav-book-icon {
            color: var(--text-main);
            stroke: var(--text-main);
        }

        /* Copyright link - matches Access-a-Lyrics' .site-footer a exactly
           (focus-color, underlined, text-color on hover/focus). */
        .copyright-link {
            color: var(--accent-focus);
            text-decoration: underline;
            cursor: pointer;
        }
        .copyright-link:hover, .copyright-link:focus-visible {
            color: var(--text-main);
        }

        /* Form Elements - text field size matches Access-a-Lyrics' .field
           input[type=text] exactly (10px/12px padding, 17px font, no fixed
           height - the field's height comes from that padding/font-size
           like Access-a-Lyrics, not a hardcoded value). */
        select, input, textarea {
            background-color: var(--bg-input);
            color: var(--text-main);
            border-width: 2px;
            border-style: solid;
            /* Yellow, matching the bottom toolbar's own button borders
               (--accent-focus) - was pink (--accent-primary). */
            border-color: var(--accent-focus);
            padding: 10px 12px;
            /* Bumped up from the sitewide 1.1875rem base (was 17px before
               that), per explicit request to increase text fields/
               dropdowns specifically - no longer matches body{}'s own
               size exactly. rem, not px - see the body{} comment for why. */
            font-size: calc(1.3rem * var(--ui-scale));
            border-radius: 0.5rem;
            width: 100%;
            font-family: inherit;
        }
        input::placeholder, textarea::placeholder { color: var(--text-muted); }
        
        /* Animations */
        @keyframes pulse-ring {
            0% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); }
            70% { box-shadow: 0 0 0 20px rgba(220, 38, 38, 0); }
            100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); }
        }
        .listening-animation {
            animation: pulse-ring 2s infinite;
        }
        
        /* Helper for dynamic backgrounds in Tailwind classes */
        .bg-dynamic-card { background-color: var(--bg-card); }
        .bg-dynamic-body { background-color: var(--bg-body); }
        .border-dynamic { border-color: var(--border-color); }
        .hover-dynamic:hover { background-color: var(--bg-hover); }
        
        /* Background removed per explicit request - an open accordion no
           longer gets a tinted card background, just the extra margin
           below it. */
        details[open] {
            margin-bottom: 2rem; /* Added margin for separation when expanded */
        }

        /* NEW: Thicker border when expanded */
        details[open] > summary {
            border-bottom-width: 4px !important;
        }
        
        /* Unified Tinted Background Class (Used for Buttons and Expanded Sections) */
        .bg-theme-tint {
             background-color: var(--bg-card);
        }

        @supports (background-color: color-mix(in srgb, red, blue)) {
            /* Apply tint to these specific elements */
            .bg-theme-tint {
                background-color: color-mix(in srgb, var(--accent-primary) 15%, var(--bg-card)) !important;
            }
        }

        /* Button State Overrides */
        
        /* 1. Hover Restore for "Go" button: 
           Forces the background back to the button hover color on hover, 
           overriding the tint !important from the @supports block */
        .hover-restore:hover, .hover-restore:focus-visible {
            background-color: var(--btn-hover) !important;
        }

        /* 2. Hover effect for Welcome Button */
        .hover-brightness:hover {
            filter: brightness(1.15);
        }

        .translate-y-full { transform: translateY(100%); }
        .translate-y-0 { transform: translateY(0); }

        /* Details Marker Hide */
        details > summary { list-style: none; }
        details > summary::-webkit-details-marker { display: none; }
        
        /* Scroll Offset */
        .scroll-mt-4 { scroll-margin-top: 1rem; }
        
        /* "Currently reading" marker - a yellow left-edge accent bar via an
           absolutely-positioned ::before (not a real border-left, which
           would shift the verse's padding/content by the border's width
           each time this class toggles on/off while Play auto-scrolls
           through a passage, and not an inset box-shadow, which can't
           extend past the paragraph's own left edge - this needs to sit
           2px outside it). Positioned/sized elements never participate in
           layout, so the verse's content never moves regardless of state.
           The faint background tint is deliberately low-opacity gray
           (not a real color) so it doesn't compete with the
           user-highlight-* colors below when both apply at once - CSS
           cascade order (user-highlight-* declared after this) means a
           real highlight color always wins over this tint anyway. */
        .verse-highlight {
            border-radius: 4px;
            background-color: rgba(128, 128, 128, 0.4);
        }
        /* Doesn't cover ESV verses grouped inline in a shared paragraph
           (span[data-verse-num]) - those can start mid-line (see the
           plain-inline note on #verse-text span[data-verse-num] below), so
           a bar anchored to the verse itself would float wherever that
           happens to be instead of hugging the passage's left edge. Their
           bar is .verse-paragraph-active::before instead, further down. */
        p.verse-highlight::before,
        h3.verse-highlight::before,
        h4.verse-highlight::before {
            content: "";
            position: absolute;
            top: 0;
            bottom: 0;
            left: -6px;
            width: 4px;
            background-color: var(--accent-focus);
            border-radius: 4px;
        }

        /* User-applied verse highlights (Verse Actions modal) - applied to
           the same <p> #verse-highlight uses for the "currently reading"
           marker, so they share its padding/border-radius automatically.
           rgb(from ...) (relative color syntax) pulls just the R/G/B out
           of each --highlight-X variable and re-applies opacity via
           --highlight-alpha instead of whatever alpha that variable itself
           was declared with - this is what lets Settings > Passage
           Appearance > Highlight Colors Brightness (see
           applyHighlightBrightness() in app.js) adjust opacity at runtime
           without needing 4 entire duplicate copies of every color. */
        .user-highlight-yellow { background-color: rgb(from var(--highlight-yellow) r g b / var(--highlight-alpha)); }
        .user-highlight-green { background-color: rgb(from var(--highlight-green) r g b / var(--highlight-alpha)); }
        .user-highlight-pink { background-color: rgb(from var(--highlight-pink) r g b / var(--highlight-alpha)); }
        .user-highlight-blue { background-color: rgb(from var(--highlight-blue) r g b / var(--highlight-alpha)); }
        .user-highlight-orange { background-color: rgb(from var(--highlight-orange) r g b / var(--highlight-alpha)); }
        .user-highlight-purple { background-color: rgb(from var(--highlight-purple) r g b / var(--highlight-alpha)); }
        .user-highlight-red { background-color: rgb(from var(--highlight-red) r g b / var(--highlight-alpha)); }
        .user-highlight-teal { background-color: rgb(from var(--highlight-teal) r g b / var(--highlight-alpha)); }
        .user-highlight-cyan { background-color: rgb(from var(--highlight-cyan) r g b / var(--highlight-alpha)); }
        .user-highlight-gray { background-color: rgb(from var(--highlight-gray) r g b / var(--highlight-alpha)); }
        .user-highlight-brown { background-color: rgb(from var(--highlight-brown) r g b / var(--highlight-alpha)); }
        .user-highlight-magenta { background-color: rgb(from var(--highlight-magenta) r g b / var(--highlight-alpha)); }
        .user-highlight-lavender { background-color: rgb(from var(--highlight-lavender) r g b / var(--highlight-alpha)); }
        .user-highlight-dark-purple { background-color: rgb(from var(--highlight-dark-purple) r g b / var(--highlight-alpha)); }
        .user-highlight-light-pink { background-color: rgb(from var(--highlight-light-pink) r g b / var(--highlight-alpha)); }
        .user-highlight-dark-pink { background-color: rgb(from var(--highlight-dark-pink) r g b / var(--highlight-alpha)); }
        .user-highlight-dark-red { background-color: rgb(from var(--highlight-dark-red) r g b / var(--highlight-alpha)); }
        .user-highlight-dark-teal { background-color: rgb(from var(--highlight-dark-teal) r g b / var(--highlight-alpha)); }
        .user-highlight-dark-blue { background-color: rgb(from var(--highlight-dark-blue) r g b / var(--highlight-alpha)); }
        .user-highlight-dark-green { background-color: rgb(from var(--highlight-dark-green) r g b / var(--highlight-alpha)); }
        .user-highlight-light-blue { background-color: rgb(from var(--highlight-light-blue) r g b / var(--highlight-alpha)); }
        .user-highlight-bright-green { background-color: rgb(from var(--highlight-bright-green) r g b / var(--highlight-alpha)); }
        .user-highlight-light-yellow { background-color: rgb(from var(--highlight-light-yellow) r g b / var(--highlight-alpha)); }
        .user-highlight-dark-yellow { background-color: rgb(from var(--highlight-dark-yellow) r g b / var(--highlight-alpha)); }

        /* "Currently reading" (.verse-highlight) landing on an already-
           highlighted verse - .user-highlight-* above shares this same
           element and, being declared later, otherwise wins the cascade
           outright: the reading indicator's background silently became
           just "whatever this highlight color looks like right now,"
           with no separate signal that it's also the verse being read -
           and it changed brightness right along with Settings > Passage
           Appearance > Highlight Colors Brightness, which has nothing to
           do with reading state. This combo gets its own fixed opacity
           instead (same as plain .verse-highlight's own tint) - same hue
           so it's still clear which color is highlighted, just no longer
           tied to that setting. Two classes beats .user-highlight-* on
           specificity alone, so no !important needed. */
        .verse-highlight.user-highlight-yellow { background-color: rgb(from var(--highlight-yellow) r g b / 0.4); }
        .verse-highlight.user-highlight-green { background-color: rgb(from var(--highlight-green) r g b / 0.4); }
        .verse-highlight.user-highlight-pink { background-color: rgb(from var(--highlight-pink) r g b / 0.4); }
        .verse-highlight.user-highlight-blue { background-color: rgb(from var(--highlight-blue) r g b / 0.4); }
        .verse-highlight.user-highlight-orange { background-color: rgb(from var(--highlight-orange) r g b / 0.4); }
        .verse-highlight.user-highlight-purple { background-color: rgb(from var(--highlight-purple) r g b / 0.4); }
        .verse-highlight.user-highlight-red { background-color: rgb(from var(--highlight-red) r g b / 0.4); }
        .verse-highlight.user-highlight-teal { background-color: rgb(from var(--highlight-teal) r g b / 0.4); }
        .verse-highlight.user-highlight-cyan { background-color: rgb(from var(--highlight-cyan) r g b / 0.4); }
        .verse-highlight.user-highlight-gray { background-color: rgb(from var(--highlight-gray) r g b / 0.4); }
        .verse-highlight.user-highlight-brown { background-color: rgb(from var(--highlight-brown) r g b / 0.4); }
        .verse-highlight.user-highlight-magenta { background-color: rgb(from var(--highlight-magenta) r g b / 0.4); }
        .verse-highlight.user-highlight-lavender { background-color: rgb(from var(--highlight-lavender) r g b / 0.4); }
        .verse-highlight.user-highlight-dark-purple { background-color: rgb(from var(--highlight-dark-purple) r g b / 0.4); }
        .verse-highlight.user-highlight-light-pink { background-color: rgb(from var(--highlight-light-pink) r g b / 0.4); }
        .verse-highlight.user-highlight-dark-pink { background-color: rgb(from var(--highlight-dark-pink) r g b / 0.4); }
        .verse-highlight.user-highlight-dark-red { background-color: rgb(from var(--highlight-dark-red) r g b / 0.4); }
        .verse-highlight.user-highlight-dark-teal { background-color: rgb(from var(--highlight-dark-teal) r g b / 0.4); }
        .verse-highlight.user-highlight-dark-blue { background-color: rgb(from var(--highlight-dark-blue) r g b / 0.4); }
        .verse-highlight.user-highlight-dark-green { background-color: rgb(from var(--highlight-dark-green) r g b / 0.4); }
        .verse-highlight.user-highlight-light-blue { background-color: rgb(from var(--highlight-light-blue) r g b / 0.4); }
        .verse-highlight.user-highlight-bright-green { background-color: rgb(from var(--highlight-bright-green) r g b / 0.4); }
        .verse-highlight.user-highlight-light-yellow { background-color: rgb(from var(--highlight-light-yellow) r g b / 0.4); }
        .verse-highlight.user-highlight-dark-yellow { background-color: rgb(from var(--highlight-dark-yellow) r g b / 0.4); }

        /* Highlight Colors Brightness tiers (Settings > Passage
           Appearance) - override --highlight-alpha (0.35 dark / 0.3 light
           default) via a class toggled on the root element (<html>) by
           applyHighlightBrightness() in app.js - was scoped to #verse-text
           only, so anywhere else .user-highlight-* renders (e.g. the
           Highlights & Notes browser's Highlights tab) never picked up
           Soft/Bright, always showing Default's alpha regardless of the
           actual setting. Same color as Default/Soft either way, just a
           different opacity - light mode uses slightly lower alpha per
           tier than dark mode. */
        :root.highlight-brightness-soft { --highlight-alpha: 0.25; }
        :root.highlight-brightness-bright { --highlight-alpha: 0.45; }
        :root[data-theme="light"].highlight-brightness-soft { --highlight-alpha: 0.2; }
        :root[data-theme="light"].highlight-brightness-bright { --highlight-alpha: 0.4; }


        /* Small note indicator appended after a verse's text when it has a
           saved note - "sr-only" text alongside it (in the JS that builds
           it) announces this to screen readers too, since it's added
           content rather than replacing the verse's own accessible name. */
        .verse-note-indicator {
            margin-left: 0.35em;
            color: var(--accent-focus);
            font-style: normal;
        }
        /* Selah (Psalms) - built as its own line, indented here to set it
           apart from the poetry lines around it, matching how it's
           typically set in print. Two paths build it: appendVerseText()/
           isolateSelah() in app.js for any non-ESV translation (a real
           <br> before it), and splitSelahFromLines()/
           createPoetryVerseElement() for the ESV poetry-layout case,
           where it also gets .poetry-row-indent - the two indents stack,
           giving Selah a bit more indent than an ordinary indented line. */
        .selah-line {
            display: inline-block;
            margin-left: 2em;
            font-style: italic;
            font-weight: bold;
        }

        /* Dynamic Paragraph Spacing */
        #verse-text p {
            margin-bottom: var(--para-spacing, 1rem);
        }

        /* Passage display - width is NOT restrained beyond #app-container's
           own max-width (700px - see #app-container below for why this is
           narrower than Access-a-Lyrics' matching .app-shell/.lyrics-pane
           900px). width:100% + main's align-items:center is what actually
           centers this card within the page; text-align:left is explicit
           so the card being centered never implies its own text should be
           too. */
        /* This element's real background color is always set inline by
           applyPassageBg() in app.js (the Passage Background setting),
           which wins over anything set here regardless of specificity -
           .bg-dynamic-card's --bg-card (in the markup's class list) only
           ever shows for a brief instant before that JS runs. No border/
           shadow/rounding on mobile - removes the "boxed" look entirely,
           per explicit request. Restored on desktop (see the media query
           below). */
        #scripture-display {
            width: 100%;
            box-sizing: border-box;
            padding: 28px 24px 40px;
            text-align: left;
        }
        @media (min-width: 768px) {
            #scripture-display {
                /* No border on desktop either now (was 2px solid
                   var(--border-color)), just shadow/rounding to still
                   read as its own area. */
                border-radius: 12px;
                box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            }
        }
        /* Was margin-bottom: 1.5rem + padding-bottom: 1rem (40px of gap
           between the reference and the first verse) back when this row
           also held the Previous/Next buttons. Those are gone, so the
           <h2> is the only child left and that much space just read as a
           hole under the title. */
        #scripture-display .passage-top-row {
            display: flex;
            flex-direction: column;
            margin-bottom: 0.6rem;
            padding-bottom: 0;
        }
        #scripture-display .passage-nav-row {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            justify-content: center;
            gap: 12px;
            width: 100%;
        }
        /* Status/dictation feedback text - updateStatus() in app.js
           regenerates #status-area's own className/innerHTML with Tailwind
           classes on every call (flex flex-col items-center justify-center,
           text-xl sm:text-2xl, etc.), all dead now. Targeting the IDs/
           structure directly here means it stays centered regardless of
           whatever className JS assigns each time. */
        /* No margin-bottom here - main's own flex "gap: 24px" already
           creates the entire header-to-passage-card gap on its own; a
           margin here on top of that gap was just double-counting the same
           space, making it look padded even with nothing (or one short
           line) in #status-area/#dictation-feedback below. */
        #main-header { text-align: center; }
        #status-area {
            display: flex;
            flex-direction: column;
            align-items: center;
            text-align: center;
            font-weight: 500;
            /* Tall enough for the two-line transient+persistent stack
               (div:first-child + div:last-child below) to not visibly jump
               the passage card when it appears, but not so tall that a
               single short line (e.g. "Loading...") leaves obvious dead
               air below it before the passage card starts. */
            min-height: 2rem;
            font-size: calc(1.3rem * var(--ui-scale));
        }
        #status-area div:first-child:not(:only-child) {
            font-size: calc(1.3rem * var(--ui-scale));
            opacity: 0.9;
            margin-bottom: 0.25rem;
        }
        #status-area div:last-child {
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 700;
        }
        #dictation-feedback {
            margin-top: 0.5rem;
            font-style: italic;
            font-size: calc(1.3rem * var(--ui-scale));
            text-align: center;
        }
        /* Both areas sit empty (no text/children at all) whenever there's
           nothing to report - #status-area between messages (see
           updateStatus()'s idle/DEFAULT_INSTRUCTION case in app.js, which
           clears it to "" rather than showing permanent instruction text),
           #dictation-feedback until the first voice command is heard.
           :empty only matches true no-children emptiness (not even a
           whitespace text node), which both idle states above actually are -
           collapsing them here removes #status-area's min-height and
           #dictation-feedback's margin-top too, so neither leaves an
           awkward blank gap while unused. */
        #status-area:empty,
        #dictation-feedback:empty {
            display: none;
        }

        #manual-input-container {
            width: 100%;
            max-width: 28rem;
            display: flex;
            gap: 8px;
        }
        #manual-input-container input { flex-grow: 1; }
        /* Search field + Go button - yellow border instead of the default
           pink accent-border. */
        #manual-input-container input,
        #manual-input-container button {
            border-color: var(--accent-focus) !important;
        }
        @supports (background-color: color-mix(in srgb, red, blue)) {
            /* Yellow tint (was inheriting .app-btn-primary's pink tint,
               mismatched against this button's own yellow border), per
               explicit request. */
            #manual-input-container button {
                background-color: color-mix(in srgb, var(--accent-focus) 15%, var(--bg-card)) !important;
            }
            #manual-input-container button:hover,
            #manual-input-container button:focus-visible {
                background-color: color-mix(in srgb, var(--accent-focus) 28%, var(--bg-card)) !important;
            }
        }

        /* Search Results (Type Command dialog - see performBibleSearch()
           in app.js). #search-tab-group reuses .hn-tab-group/.hn-tab as-is
           (same look as the Highlights & Notes tabs), per explicit
           request, rather than a second, differently-styled tab
           component. */
        #search-results-heading {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            letter-spacing: 0.04em;
            text-transform: uppercase;
            color: var(--text-main);
            margin: 20px 0 4px;
        }
        #search-results-meta {
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-muted);
            margin: 0 0 12px;
        }
        .search-result-item {
            display: block;
            width: 100%;
            text-align: left;
            background: transparent;
            border: none;
            border-bottom: 1px solid var(--divider-color);
            font-family: inherit;
            cursor: pointer;
            color: var(--text-main);
            padding: 12px 4px;
        }
        .search-result-item:last-child {
            border-bottom: none;
        }
        @media (hover: hover) {
            .search-result-item:hover {
                background: var(--bg-hover);
                border-radius: 8px;
            }
        }
        .search-result-ref-row {
            display: flex;
            align-items: baseline;
            justify-content: space-between;
            gap: 8px;
            margin-bottom: 4px;
        }
        /* Matches .hn-item-ref/.hn-note-ref/.search-result-snippet - all
           four (both browser lists' reference+content, and this) unified
           to the same 1.375rem, per explicit request - was 1.1rem. */
        .search-result-ref {
            font-weight: 700;
            color: var(--accent-secondary);
            font-size: calc(1.3rem * var(--ui-scale));
        }
        /* Bigger + text-main (white in dark mode, black in light), per
           explicit request - was a small muted-gray badge before. Border
           added per a later follow-up so it reads as a distinct pill
           badge (e.g. "ESV", "ASV") rather than plain inline text. */
        .search-result-version {
            display: inline-block;
            font-weight: 800;
            color: var(--text-main);
            font-size: calc(1.3rem * var(--ui-scale));
            border: 2px solid var(--border-color);
            border-radius: 999px;
            padding: 1px 10px;
        }
        /* Matches .hn-item-text/.hn-note-text's shared size (both browser
           lists' own verse/note content) - was 1.1rem. */
        .search-result-snippet {
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            color: var(--text-main);
        }
        .search-result-snippet mark {
            background: var(--search-highlight-bg);
            color: var(--text-main);
            border-radius: 2px;
            padding: 0 2px;
        }

        #reference-header {
            text-align: center;
            width: 100%;
            /* Scales with the passage Text Size setting now (was a flat
               1.75rem/2rem) - --passage-text-size is kept in sync with
               #verse-text's own computed font-size by applyTextSize() in
               app.js. 1.1x keeps this bigger than the 0.85em
               section/subheadings below it, per explicit request. */
            font-size: calc(var(--passage-text-size, 30px) * 1.3);
            font-weight: 800;
            margin: 0;
            /* Overrides the smaller shared .scroll-mt-4 (1rem) on this
               element specifically - scrolling here on chapter navigation
               needs enough top clearance to also keep the top Previous/Next
               row (and the sticky top bar above it) visible, not just the
               heading itself. */
            scroll-margin-top: 170px;
        }
        #verse-text {
            overflow-wrap: break-word;
        }
        /* Text Color (Settings > Passage Text Options) - all three tiers get their
           own explicit class now (Highest Contrast included), toggled by
           applyTextColor() in app.js. Highest Contrast used to just rely
           on plain inheritance from body's color: var(--text-main), but in
           light mode --text-color-max is deliberately pure black - a step
           darker than --text-main (gray-900, used everywhere else in the
           app's UI) - so it needs its own rule to actually apply rather
           than silently falling back to --text-main. Scoped to #verse-text
           itself (not body) so this only ever affects passage reading
           text, never the rest of the app's UI. */
        #verse-text.text-color-max {
            color: var(--text-color-max);
        }
        #verse-text.text-color-soft {
            color: var(--text-color-soft);
        }
        #verse-text.text-color-muted {
            color: var(--text-color-muted);
        }
        /* Same three tiers for the Bible Navigation book list, which
           mirrors the passage's appearance settings (see
           mirrorPassageClass() in app.js). Set on the list itself so the
           book buttons inherit it. */
        #bible-nav-list.text-color-max { color: var(--text-color-max); }
        #bible-nav-list.text-color-soft { color: var(--text-color-soft); }
        #bible-nav-list.text-color-muted { color: var(--text-color-muted); }
        #bible-nav-history-list.text-color-max { color: var(--text-color-max); }
        #bible-nav-history-list.text-color-soft { color: var(--text-color-soft); }
        #bible-nav-history-list.text-color-muted { color: var(--text-color-muted); }
        /* Passage Appearance settings (Settings menu) - real semantic class
           names (not the old Tailwind-lookalike leading-*/text-*/font-*
           values), toggled onto #verse-text by applyTextSize()/
           applyLineSpacing()/applyTextWeight()/applyPassageWidth() in
           app.js. Font-size scale/line-height values match what Tailwind's
           text-lg..text-7xl/leading-tight..loose actually were, just under
           new names. */
        .size-tiny { font-size: 0.75rem; }
        .size-xxs { font-size: 1rem; }
        .size-xs { font-size: 1.125rem; }
        .size-sm { font-size: 1.25rem; }
        .size-md { font-size: 1.5rem; }
        .size-lg { font-size: 1.875rem; }
        /* Extra Large shrunk down a bit (was 3rem - too big a jump from
           Large's 1.875rem); Extra Extra Large now sits at that old Extra
           Large value instead, so nothing above it changed size, just its
           label (was "Huge"). Huge is now a new, genuinely bigger step
           above Extra Extra Large. */
        .size-xl { font-size: 2.25rem; }
        .size-xxl { font-size: 3rem; }
        .size-huge { font-size: 4rem; }

        .spacing-tight { line-height: 1.25; }
        .spacing-normal { line-height: 1.5; }
        .spacing-relaxed { line-height: 1.625; }
        .spacing-loose { line-height: 2; }

        .weight-normal { font-weight: 400; }
        .weight-semibold { font-weight: 600; }
        .weight-bold { font-weight: 700; }

        /* Medium/Narrow shifted down a notch (was 2rem/3rem, 4rem/6rem on
           desktop) - Narrow now sits at the old Medium value, and Medium
           is a new, less-narrow value between Wide and the new Narrow. */
        .width-wide { padding-left: 0; padding-right: 0; }
        .width-medium { padding-left: 1rem; padding-right: 1rem; }
        .width-narrow { padding-left: 2rem; padding-right: 2rem; }
        @media (min-width: 768px) {
            .width-medium { padding-left: 2rem; padding-right: 2rem; }
            .width-narrow { padding-left: 4rem; padding-right: 4rem; }
        }
        /* Each verse paragraph in #verse-text is focusable (tabIndex=0, for
           the reading/highlight feature) - small padding+radius so the
           focus ring has breathing room, matching what its dead "rounded
           p-1" Tailwind classes used to give it. */
        #verse-text p {
            /* Left padding is fixed at 14px regardless of .verse-highlight
               state (4px reserved for its ::before accent bar + 10px clear
               gap before the text) so toggling that class on/off during
               Play never shifts the text - only padding-left is asymmetric
               here specifically to reserve that space consistently.
               position:relative so .verse-highlight::before (the "currently
               reading" bar) can anchor to this <p> specifically. */
            position: relative;
            padding: 2px 4px 2px 14px;
            border-radius: 4px;
            /* Extra scroll margin so auto-scrolling to the current verse
               during Play also tends to keep the Previous/Next chapter
               buttons in view too (top row near the start of a passage,
               bottom row near the end), instead of scrolling tight to just
               the verse text and cutting them off. Top value trimmed down
               (was 190px) so the verse itself sits higher on screen after
               each auto-scroll, with less empty space reserved above it. */
            scroll-margin-top: 130px;
            scroll-margin-bottom: 220px;
            cursor: pointer;
        }
        /* Verse number badge - built via createVerseElement()/
           createPoetryVerseElement() in app.js. Targeted by its own class
           rather than :first-child - a Song of Solomon verse split across
           a mid-verse speaker label renders its continuation with no
           number at all (see "segments" in parseEsvHtmlResponse), which
           made the whole continuation's text span the accidental
           first-child and get bolded along with it. No margin-right here -
           the number's own textContent ends with a non-breaking space
           instead, so the number and its verse's first word can never be
           split apart by a line wrap (a CSS margin gap, unlike a real
           space character, isn't itself a valid wrap point, but relying on
           that felt too fragile to trust for "never" - a real
           non-breaking space guarantees it). */
        /* Semi Bold is the default (no class needed) - Verse Number
           Boldness (Settings > Verse Number Options) overrides this via
           .verse-num-weight-regular/-bold below, toggled by
           applyVerseNumWeight() in app.js. */
        #verse-text .verse-num-badge {
            font-weight: 600;
            user-select: none;
        }
        #verse-text.verse-num-weight-regular .verse-num-badge {
            font-weight: 400;
        }
        #verse-text.verse-num-weight-bold .verse-num-badge {
            font-weight: 800;
        }
        /* Verse Numbers: Superscript/Large Superscript (Settings > Text
           Options) - raises and shrinks the number, classic print-Bible
           style, toggled by applyVerseNumStyle() in app.js. em-based
           shrink so it scales with whatever Text Size is set, same
           reasoning as the non-breaking space above keeping the number
           glued to its verse. Large Superscript is the same raised
           position, just noticeably bigger than plain Superscript's size
           (was 1.25x/0.8125em, tuned down to a flat 0.9em per explicit
           follow-up), per explicit request - still reads as a
           superscript, just a bit bigger than the classic print-Bible
           size. */
        #verse-text.verse-num-superscript .verse-num-badge {
            vertical-align: super;
            font-size: 0.65em;
        }
        #verse-text.verse-num-superscript-large .verse-num-badge {
            vertical-align: super;
            font-size: 0.9em;
        }
        /* ESV only - section heading (nested under the passage's own h2
           reference) and the wrapper multiple grouped verses share a
           paragraph inside (see displayScripture()'s ESV layout branch). */
        .passage-heading {
            position: relative;
            font-weight: bold;
            font-size: 0.85em;
            /* var(--accent-secondary) (was a separate hardcoded hex per
               theme) - those had drifted out of sync with the rest of the
               app's green (accent-secondary, the BCV icon, etc.), so this
               now shares the exact same variable everything else does and
               can't drift again. */
            color: var(--accent-secondary);
            margin: 1.25rem 0 0.75rem;
            padding: 2px 4px 2px 14px;
            border-radius: 4px;
            scroll-margin-top: 130px;
            scroll-margin-bottom: 220px;
        }
        /* Crossway's "subheading" tier (psalm/song superscriptions, Psalm
           119's acrostic letters, Song of Solomon's "She"/"He"/"Others"
           speaker labels) - same green treatment as .passage-heading, just
           italic and a touch smaller, so it still reads as a heading
           rather than body text but doesn't compete with a real section
           title. */
        .passage-subheading {
            position: relative;
            font-weight: bold;
            font-style: italic;
            font-size: 0.75em;
            color: var(--accent-secondary);
            margin: 0.75rem 0 0.25rem;
            padding: 2px 4px 2px 14px;
            border-radius: 4px;
            scroll-margin-top: 130px;
            scroll-margin-bottom: 220px;
        }
        /* Same Paragraph Spacing setting (Settings menu) that governs the
           gap between verses for every other translation - see
           "Dynamic Paragraph Spacing" / --para-spacing above. position:
           relative so .verse-paragraph-active::before (the currently-
           reading accent bar for a verse grouped inline in here - see the
           comment there) can anchor to this paragraph's own left edge.
           padding-left applies to every line that starts fresh at this
           paragraph's left edge, so every one gets the same bar
           clearance - not just the paragraph's first verse, but any
           later verse's wrapped continuation line too, since word-wrap
           sends those back to the left edge exactly the same way. 10px
           here + the 4px left padding on #verse-text span[data-verse-num]
           below matches the 14px/12px clear-gap pattern
           p.verse-highlight/.passage-heading already use, so the bar
           sits the same distance from text everywhere. Doesn't affect a
           verse that starts mid-line (right after the previous verse on
           the same line) - padding only matters where a line actually
           touches this edge.
           text-indent is separate - a traditional first-line-of-a-
           paragraph indent, stacking on top of the padding above but
           (unlike padding) only for this paragraph's actual first line,
           same as a book/document paragraph style. */
        .verse-paragraph {
            position: relative;
            text-indent: 1em;
            padding-left: 10px;
            margin-bottom: var(--para-spacing, 1rem);
        }
        /* Poetic books (Job, Psalms, Proverbs, Ecclesiastes, Song of
           Solomon, Lamentations - see POETIC_BOOKS in app.js) - the
           .verse-paragraph counterpart for these, grouping every verse in
           one of Crossway's own paragraphs (a stanza) under the same
           Paragraph Spacing gap, but otherwise doing nothing else - no
           bar-clearance padding or book-style first-line indent, since
           poetry doesn't flow verses inline the way prose does (each one
           is its own real <p>, see .poetry-line below) so neither is
           needed here. */
        .poetry-stanza {
            margin-bottom: var(--para-spacing, 1rem);
        }
        /* Each verse is its own line (unlike prose's inline-flowing
           spans). margin-bottom:0 overrides #verse-text p's own default
           so consecutive verses in the same stanza read as one continuous
           block, with only the wrapping .poetry-stanza's own margin
           marking an actual stanza break. margin-top:0 is needed too -
           #verse-text p never sets a top margin itself, so every <p> was
           still getting the browser's own ~1em default margin-block-start,
           which read as a gap above every single verse (not just between
           stanzas). padding-left is just the same 14px bar-clearance every
           other verse/heading uses - actual line indentation is per-row
           below, not applied to the whole <p>. */
        #verse-text p.poetry-line {
            margin-top: 0;
            margin-bottom: 0;
            padding-left: 14px;
        }
        /* Indentation is a property of each individual printed LINE
           (Crossway's own "line"/"indent line" span classes - see
           createPoetryVerseElement()/parseEsvHtmlResponse() in app.js),
           not "everything after a verse's first line" - a single
           hanging-indent trick on the whole <p> can't reproduce
           Lamentations, which alternates base/indent every line within
           one verse. display:inline-block so padding-left only shifts a
           row's own start, since each row already sits on its own visual
           line via the <br> createPoetryVerseElement() inserts between
           them - a row's own word-wrap continuation lines return to the
           same offset (this row's content box), which is the standard/
           expected hanging behavior for a long line. */
        .poetry-row {
            display: inline;
            overflow-wrap: break-word;
            word-break: break-word;
        }
        /* inline-block only on the INDENTED rows, not every row - an
           inline-block box gets its own soft wrap opportunity immediately
           before it even with no actual space there, which on a narrow
           viewport let the browser break the line between the verse
           number and the verse's first (never indented) row, stranding
           the number alone above it - the same problem createVerseElement
           solves with a non-breaking space, but that trick only works
           between two plain inline runs, not against an inline-block.
           Every indented row is already preceded by a real <br> (see
           createPoetryVerseElement in app.js), which forces its own line
           break regardless of display type, so inline-block is safe here -
           there's nothing for it to wrap away from. */
        .poetry-row-indent {
            display: inline-block;
            max-width: 100%;
            padding-left: 1.5em;
            overflow-wrap: break-word;
            word-break: break-word;
            /* box-sizing:border-box so padding-left is INSIDE the 100%
               max-width cap, not added on top of it - this app never sets
               box-sizing:border-box globally, and the default content-box
               would let a full-width-content row plus 1.5em of padding
               exceed 100% of the container, which is exactly the overflow
               this rule exists to prevent (worse at large text sizes,
               where 1.5em itself gets bigger). */
            box-sizing: border-box;
        }
        /* Currently-reading accent bar for a verse grouped inline in this
           paragraph (counterpart to p.verse-highlight::before/
           h3.verse-highlight::before above, for standalone verses/
           headings). Lives on the paragraph instead of the verse itself,
           since the verse can start mid-line and the bar needs to hug the
           paragraph's left edge regardless of where that is.
           --bar-top/--bar-height are set in JS (setVerseHighlightBar() in
           app.js) from the verse's own bounding rect relative to this
           paragraph's, whenever that verse becomes the one being read. */
        .verse-paragraph-active::before {
            content: "";
            position: absolute;
            top: var(--bar-top, 0);
            height: var(--bar-height, 100%);
            left: -6px;
            width: 4px;
            background-color: var(--accent-focus);
            border-radius: 4px;
        }
        /* Individual verses inside a .verse-paragraph are inline (span, not
           <p>) so consecutive verses read as one continuous flow - verse 2
           can start mid-line right where verse 1's text ends (plus a small
           gap from the padding below), wrapping normally like any other
           run of text, rather than each verse forcing its own row.
           Plain inline, not inline-block - inline-block was tried first,
           but it sizes to the full available width as soon as a verse's
           text is long enough to wrap at all, which pushed every
           following verse down onto a new row instead of continuing
           beside it. Background/highlight color (.verse-highlight,
           .user-highlight-*) still repeats correctly across every wrapped
           line either way; only the currently-reading accent bar is the
           tradeoff of going back to plain inline - it lives on the
           paragraph wrapper instead (.verse-paragraph-active::before
           above), not this element, for exactly that reason. */
        #verse-text span[data-verse-num] {
            padding: 2px 4px;
            border-radius: 4px;
            scroll-margin-top: 130px;
            scroll-margin-bottom: 220px;
            cursor: pointer;
        }
        #full-chapter-btn {
            width: 100%;
            font-size: calc(1.3rem * var(--ui-scale));
            margin-bottom: 24px;
        }
        @media (min-width: 640px) {
            #full-chapter-btn { width: auto; }
        }

        /* Page scaffolding (#app-container/main) - hand-written now that
           Tailwind is gone from this page entirely. #app-container was
           originally a match for Access-a-Lyrics' .app-shell (max-width
           900px, centered, same padding) - now deliberately narrower (see
           max-width below) to leave more gutter for Verse Actions. body's
           own layout properties (flex column, min-height, padding-top:0)
           live in the base "body" rule above - it carries no horizontal/
           bottom padding of its own (that lives on #app-container). */
        #app-container {
            width: 100%;
            /* Narrowed from 900px so wide screens leave more of a gutter
               on the left for Verse Actions to fit into without
               overlapping the passage (see its left-anchored positioning
               further down this file) - .app-toolbar matches this same
               700px so the two stay visually aligned. */
            max-width: 700px;
            margin: 0 auto;
            /* Bottom padding cleared to keep the footer/copyright link out
               from under the now permanently-fixed .app-toolbar (~80px tall,
               plus whatever safe-area inset it added for the home
               indicator/gesture bar). */
            padding: 24px 24px calc(180px + env(safe-area-inset-bottom, 0px));
            display: flex;
            flex-direction: column;
            flex: 1 0 auto;
        }
        /* Desktop gets a bit more room (800px, was 700px on every size) -
           per explicit request, mobile is unaffected (rarely matters
           there anyway, since phone viewports are almost always narrower
           than either cap). .app-toolbar and .sticky-chapter-nav both
           deliberately match this same value everywhere else in the file
           so their buttons stay aligned with the passage's own edges -
           they need the identical override here too, or they'd fall out
           of sync with this container on desktop specifically. */
        @media (min-width: 768px) {
            #app-container,
            .app-toolbar,
            .sticky-chapter-nav {
                max-width: 800px;
            }
        }
        main {
            display: flex;
            flex-direction: column;
            align-items: center;
            width: 100%;
            gap: 24px;
            flex-grow: 1;
        }
        /* On mobile, the passage card (and everything else in the column)
           should sit almost flush with the screen edges - the card's own
           padding (#scripture-display below) still keeps the actual verse
           text comfortably inset, only its border moves closer to the
           edge. The top bar/toolbar get the same reduced side padding so
           they stay visually aligned with the passage instead of framing
           it with a wider margin than the passage itself has. */
        @media (max-width: 639px) {
            #app-container {
                padding-left: 2px;
                padding-right: 2px;
            }
            /* .app-top-bar's copy of this moved to right after its own
               base rule further down - same equal-specificity/source-
               order issue explained there. #app-container above and
               .app-toolbar below are unaffected by that same bug right
               now (not touched here), but would need the identical
               treatment if either ever stops applying too. */
            .app-toolbar {
                padding-left: 2px;
                padding-right: 2px;
            }
        }

        /* "Back to info page" link - mirrors Access-a-Lyrics' own
           .back-to-coa-apps styling exactly (accent-green, 14px, no
           underline until hover/focus). Lives in .app-menu-top-row now
           (top of Settings), so its own margin is reset there - the row
           itself owns the spacing instead. */
        .back-link-app {
            display: inline-block;
            /* Pinned to the pre-tweak green (not var(--accent-secondary))
               - excluded from this session's "make all greens lighter"
               request, same exclusion as the theme toggle/login-signup. */
            color: #4ade80;
            font-weight: 700;
            font-size: calc(1.3rem * var(--ui-scale));
            text-decoration: none;
            margin: 4px 0 18px;
        }
        :root[data-theme="light"] .back-link-app {
            color: #097732;
        }
        /* Theme toggle + CoA Apps link share a row at the top of Settings -
           inline with each other when there's room, wrapping to stack
           (link below the toggle) on narrow viewports. */
        .app-menu-top-row {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 8px 16px;
            margin-bottom: 20px;
        }
        .app-menu-top-row .app-menu-theme-toggle,
        .app-menu-top-row .back-link-app {
            margin: 0;
        }
        .back-link-app:hover,
        .back-link-app:focus-visible {
            color: var(--accent-focus);
            text-decoration: underline;
        }

        /* Inline title logo (H1, main header) - dark/light SVG pair, same
           icon-for-dark/icon-for-light show-one-hide-other convention as the
           product-hero-icon on index.html, keyed off :root[data-theme] same
           as the rest of this app's theming now (see applyMode() in app.js).
           Sized to match Access-a-Lyrics' .app-icon (28px) exactly. */
        .app-title-icon {
            width: 28px;
            height: 28px;
            flex-shrink: 0;
        }
        /* Title text - 20px/800 weight/10px gap to the logo at rest, same
           as Access-a-Lyrics' .site-title. .app-top-bar no longer wraps
           (see flex-wrap:nowrap above) - the search/hamburger buttons need
           to stay pinned top-right always, so the title shrinks instead
           when things don't fit, via the 4.5vw fluid middle term below
           (kicks in under ~440px viewport width; wider than that, clamp
           just caps at the normal 1.25rem). overflow/white-space/min-width
           are a last-resort safety net for anything even the smallest
           clamp size can't fit. */
        h1.app-title-text {
            display: flex;
            align-items: center;
            gap: 10px;
            margin: 0;
            font-size: calc(clamp(0.9rem, 4.5vw, 1.25rem) * var(--ui-scale));
            font-weight: 800;
            color: var(--text-main);
            min-width: 0;
            flex-shrink: 1;
            overflow: hidden;
            white-space: nowrap;
        }
        /* Generic show-one-hide-other dark/light icon swap - shared by the
           title logo above and the toolbar's BCV/Highlights icons below. */
        .icon-for-dark { display: inline; }
        .icon-for-light { display: none; }
        :root[data-theme="light"] .icon-for-dark {
            display: none;
        }
        :root[data-theme="light"] .icon-for-light {
            display: inline;
        }

        /* Top bar (logo, app name, hamburger menu) - styled like Access-a-
           Lyrics' .site-header: sticky, full-width (it's a sibling of
           #app-container now, not nested inside it, so its background spans
           edge to edge), padding 14px 24px, surface background, border-
           bottom. z-index sits below the app menu dialog (z-60/61). */
        .app-top-bar {
            position: sticky;
            top: 0;
            z-index: 40;
            display: flex;
            flex-wrap: nowrap;
            align-items: center;
            justify-content: space-between;
            gap: 16px;
            /* Extra top padding for the status bar (time/battery/signal)
               when this is launched as an installed iPhone app (Safari's
               "Add to Home Screen") - viewport-fit=cover (see app.html)
               lets the page draw under that area in standalone mode,
               unlike a normal Safari tab, where Safari's own chrome
               already keeps content clear of it. env() resolves to 0
               there (and on every other browser/platform), so this is a
               no-op everywhere except that one specific context. */
            padding: calc(7px + env(safe-area-inset-top, 0px)) 24px 7px;
            /* Matches modals (.app-menu-card uses --bg-body), per explicit
               request - was --bg-card. */
            background-color: var(--bg-body);
            border-bottom: 2px solid var(--divider-color);
            /* Kept as a transparent border rather than "border: none" in
               the Top case below, so dropping the line never shifts the
               page by 2px. */
            /* Forces this onto its own compositing layer - a common, safe
               fix for Chromium's known position:sticky jitter/misalignment
               bugs during scroll, independent of whatever the exact trigger
               is on this page. */
            will-change: transform;
            transform: translateY(0);
            /* Slower + an ease-out curve (was 0.25s ease, which read as
               closer to an instant snap than an actual slide) - per
               explicit request for a more noticeable slide effect, same
               tuning as .app-toolbar's own transition. */
            transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
        }
        /* Placed AFTER the base rule above deliberately, not left folded
           into the earlier @media (max-width: 639px) block alongside
           #app-container/.app-toolbar's own padding reduction - that
           block sits earlier in the file, and CSS gives the LATER of two
           equal-specificity rules the win regardless of which one is
           wrapped in a matching media query, so the copy placed there was
           being silently beaten by this rule's own 24px above it (same
           bug already fixed for .sticky-chapter-nav elsewhere in this
           file). This one, coming after, correctly wins on mobile
           instead - less space from the screen edges to the icon/menu
           button, per explicit request. Desktop (the base rule's 24px)
           is untouched. */
        @media (max-width: 639px) {
            .app-top-bar {
                padding-left: 2px;
                padding-right: 2px;
            }
        }
        /* Chapter Nav Strip Position = Top: the strip pins directly beneath
           this bar, so its own top edge and this divider stacked into a
           double line. Only the Top position has that problem - in the
           Bottom position the strip is nowhere near, and the divider is
           the only thing separating the bar from the passage, so it stays.
           The class is toggled on documentElement by applyPositionClass()
           in app.js. */
        :root.chapter-nav-top .app-top-bar {
            border-bottom-color: transparent;
        }
        /* The strip row's own top padding, the other half of the dead space
           between the app title and the arrows beneath it. The top bar's
           side of that gap is now handled by its halved base padding above.
           Keyed off the absence of the
           Bottom class rather than :root.chapter-nav-top so it stays
           correct even in the first frame after a switch, before the root
           class and the strip class have both landed. */
        .sticky-chapter-nav:not(.sticky-chapter-nav-bottom) .sticky-chapter-nav-row {
            padding-top: 6px;
        }
        /* Auto-hide on scroll (app.js toggles this on/off by scroll direction),
           same pattern as Access-a-Lyrics' .site-header.header-hidden.
           Transform, not display/visibility, so the bar stays in the a11y
           tree - screen reader and Tab-key navigation are unaffected either
           way. The :focus-within escape hatch covers the one real gap: a
           sighted keyboard user who tabs into a header control while it's
           scrolled off-screen would otherwise land on an invisible focus
           target. */
        /* Groups the search and hamburger-menu buttons together on the
           right - .app-top-bar's own justify-content:space-between only
           ever expected two children (the title and one button); wrapping
           both buttons here keeps that space-between behaving the same
           (title left, this pair right) instead of spreading three items
           across the bar. */
        .app-top-bar-actions {
            display: flex;
            align-items: center;
            gap: 8px;
            flex-shrink: 0;
        }
        .app-top-bar.header-hidden { transform: translateY(-100%); }
        .app-top-bar.header-hidden:focus-within { transform: translateY(0); }
        @media (prefers-reduced-motion: reduce) {
            .app-top-bar { transition: none; }
            #verse-actions-dialog .app-menu-card { transition: none; }
        }

        /* Sticky chapter-nav strip (beta only, see initStickyChapterNav()
           in app.js) - top offset is a CSS var the same script keeps in
           sync with .app-top-bar's real height, dropping to 0 while the
           auto-hide behavior above has translated the top bar away, so
           this never floats with an empty gap above it. z-index one below
           .app-top-bar (40) so the top bar still wins if they ever
           overlap during the hide/show transition. */
        .sticky-chapter-nav {
            position: sticky;
            top: var(--sticky-nav-top, 64px);
            z-index: 39;
            /* Column layout - .sticky-chapter-nav-row and the audio player
               panel are siblings inside this same sticky/fixed container,
               so the panel inherits whichever positioning mode (top vs
               bottom) is active without needing its own. Order between
               them flips via flex-direction below (Top: row then panel -
               panel opens downward. Bottom: reversed - panel opens
               upward), same principle proven out in the mockup's
               .strip-slot/#bottomSlot trick. */
            display: flex;
            flex-direction: column;
            /* Matches #app-container's/.app-toolbar's own 700px cap so
               this strip lines up with the passage view's actual width
               instead of spanning the full viewport edge to edge. width:
               100% explicit (not just relying on a block-level flex
               container's implicit auto-width filling its parent) -
               #app-container sets this same belt-and-suspenders width
               alongside its own max-width/margin:auto pair. */
            width: 100%;
            max-width: 700px;
            margin: 0 auto;
            background-color: var(--bg-card);
        }
        /* Full-bleed border, per explicit request - the strip's own box
           stays capped at 700px (so the buttons inside it don't move),
           but this pseudo-element ignores that cap entirely (left:50% +
           width:100vw + translateX(-50%) centers a full-viewport-wide box
           regardless of how narrow its own parent is) so just the line
           spans edge to edge on any viewport size. Bottom edge by default
           (Top position - see .sticky-chapter-nav-row above); flipped to
           the top edge for the Bottom position variant below. Positioned
           off of .sticky-chapter-nav itself, not a separate wrapper -
           its own sticky/fixed positioning already establishes the
           containing block this needs. */
        .sticky-chapter-nav::before {
            content: "";
            position: absolute;
            left: 50%;
            width: 100vw;
            transform: translateX(-50%);
            bottom: 0;
            border-bottom: 2px solid var(--divider-color);
            pointer-events: none;
        }
        .sticky-chapter-nav-row {
            display: flex;
            align-items: center;
            /* space-between pushes Previous to the left edge and Next to
               the right edge (per explicit request) - the reference text
               between them lands close to center on its own since both
               buttons are the same width. */
            justify-content: space-between;
            gap: 16px;
            padding: 12px 16px;
        }
        /* Tightened gap around the reference/audio group - toggled by
           fitStickyChapterNavRef() in app.js only when the reference
           doesn't fit at its normal size and normal gap. Tried before the
           font itself shrinks (see that function) - reclaiming space by
           tightening spacing is less noticeable than shrinking legible
           text, so it's the first thing tried, not the last resort. */
        .sticky-chapter-nav-row.tight-gap {
            gap: 8px;
        }
        /* Reference + audio buttons grouped together, per explicit request -
           with Prev/Next equal width, .sticky-chapter-nav-row's own
           space-between (above) already centers this group in the
           remaining space between them, as long as this group itself just
           sizes to its own content rather than stretching. */
        .sticky-chapter-nav-center {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        /* Bottom position (Settings > Experimental Features > Chapter Nav
           Strip Position) - fixed instead of sticky, matching
           .app-toolbar's own left:0/right:0 + max-width/margin:auto
           technique, anchored just above the toolbar via
           --sticky-nav-bottom (toolbar's own current height, kept in
           sync by initStickyChapterNav() for the same reason
           --sticky-nav-top is - the Toolbar Size setting changes it).
           border moves to the top edge - it's now the edge facing away
           from the toolbar, separating this strip from the passage
           content above it instead of below. flex-direction reversed so
           the audio player panel renders above the nav row instead of
           below it - it should open away from the toolbar it's pinned
           above, same as the Top position opens away from the top bar. */
        .sticky-chapter-nav.sticky-chapter-nav-bottom {
            position: fixed;
            top: auto;
            left: 0;
            right: 0;
            bottom: var(--sticky-nav-bottom, 70px);
            flex-direction: column-reverse;
        }
        /* Drawn bottom-up here, so the strips are ordered explicitly:
           from the top, TTB bar, then the player right under it, then the
           minimized strip, then the chapter row - per explicit request
           that the TTB bar sit above the player. (In the top layout the
           source order already reads that way.) */
        .sticky-chapter-nav.sticky-chapter-nav-bottom .sticky-chapter-nav-row { order: 0; }
        .sticky-chapter-nav.sticky-chapter-nav-bottom .audio-player-panel { order: 2; }
        /* Two audio bars (TTB, Bible): the one whose player is open sits
           right on the player; the other above it. Classes throughout,
           not ids - an id here outranked the .ttb-bar-open rule and the
           open bar was not moving. */
        .sticky-chapter-nav.sticky-chapter-nav-bottom .ttb-bar { order: 4; }
        .sticky-chapter-nav.sticky-chapter-nav-bottom .ttb-bar.ttb-bar-open { order: 3; }
        .sticky-chapter-nav.sticky-chapter-nav-bottom .ttb-bar.minimized-bar { order: 1; }
        /* Top layout (drawn top-down): same idea the other way round. */
        .sticky-chapter-nav:not(.sticky-chapter-nav-bottom) .ttb-bar { order: 2; }
        .sticky-chapter-nav:not(.sticky-chapter-nav-bottom) .ttb-bar.ttb-bar-open { order: 3; }
        .sticky-chapter-nav:not(.sticky-chapter-nav-bottom) .ttb-bar.minimized-bar { order: 1; }
        .sticky-chapter-nav:not(.sticky-chapter-nav-bottom) .audio-player-panel { order: 4; }
        /* Flips the full-bleed border (see .sticky-chapter-nav::before
           above) to the top edge instead of the bottom, matching the
           comment above about which edge should have it in this position. */
        .sticky-chapter-nav.sticky-chapter-nav-bottom::before {
            bottom: auto;
            top: 0;
            border-bottom: none;
            border-top: 2px solid var(--divider-color);
        }
        /* Toggled by syncOffset() in app.js only while the toolbar is
           hidden (bottom: 0 above, otherwise). Extra bottom PADDING, not
           a bigger "bottom" offset - repositioning the whole box up by
           the safe-area amount (an earlier attempt) cleared the device's
           home-indicator/gesture bar but left an empty gap below the
           strip that the passage behind it showed through. Padding
           instead keeps the box flush with the true edge while its own
           background still extends all the way down through the safe
           area - same technique .app-toolbar itself already uses for the
           same safe area, just via padding instead of an offset. Scoped
           to only the toolbar-hidden state - the toolbar's own offsetHeight
           (used for --sticky-nav-bottom whenever it's visible) already
           includes this same padding, so adding it again here too would
           double up and push this strip's content up too far. */
        .sticky-chapter-nav.sticky-chapter-nav-bottom.toolbar-hidden-gap {
            padding-bottom: env(safe-area-inset-bottom, 0px);
        }
        /* Top-position equivalent of the rule above - toggled by
           syncOffset() in app.js only while the top bar is auto-hidden
           (top: 0 above, otherwise), so this strip's own background still
           clears the iOS status bar in standalone/home-screen mode even
           when there's no visible top bar above it to do that job. */
        .sticky-chapter-nav.header-hidden-gap {
            padding-top: env(safe-area-inset-top, 0px);
        }
        /* Placed AFTER the base rule above deliberately, not folded into
           the earlier @media (max-width: 639px) block alongside
           #app-container/.app-top-bar/.app-toolbar's own padding
           reduction - that block sits earlier in the file, and CSS gives
           the LATER of two equal-specificity rules the win regardless of
           which one is wrapped in a matching media query, so a copy of
           this override placed there was being silently beaten by the
           base rule's max-width:700px above it. This one, coming after,
           correctly wins on mobile instead. */
        @media (max-width: 639px) {
            .sticky-chapter-nav {
                max-width: 100%;
            }
            .sticky-chapter-nav-row,
            .audio-player-panel {
                padding-left: 2px;
                padding-right: 2px;
            }
        }
        .sticky-chapter-nav[hidden] {
            display: none;
        }
        .sticky-chapter-nav-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            /* Rectangular (wider than tall), per explicit request - the
               arrow icon (was a plain chevron) needs more horizontal room
               than a square button comfortably gives it. Scales with
               --chapter-nav-icon-scale (see its definition near the top
               of this file) - reduced twice (from 36/46px to 32/40px to
               28/36px) to leave the reference room before it has to
               shrink, then nudged back up a step on request; the audio
               button next to it kept its own original size. */
            height: calc(32px * var(--chapter-nav-icon-scale));
            /* Slightly narrower than before (was 52px), per explicit
               request. */
            width: calc(40px * var(--chapter-nav-icon-scale));
            min-height: calc(32px * var(--chapter-nav-icon-scale));
            min-width: calc(40px * var(--chapter-nav-icon-scale));
            padding: 0;
            /* A small 2px gutter on each side, per explicit request - the
               icon inside is fixed-size and centered regardless of
               padding, so this is margin (visible breathing room around
               the button itself) rather than padding (which would be
               invisible here). */
            margin: 0 2px;
            flex-shrink: 0;
            /* No outline here - the arrows carry the strip on their own,
               and a box around each competed with the reference between
               them. Bigger, heavier glyphs instead (see the svg rule). */
            border: none;
            border-radius: 8px;
            background: transparent;
            /* Gray arrows (was --text-main), per explicit request. */
            color: var(--border-color);
            cursor: pointer;
            /* Suppresses the native translucent tap-highlight flash on
               touch (same convention as .touch-target elsewhere) - the
               :hover rule below is already excluded from touch via
               (hover: hover), but this covers the browser's own default
               tap feedback too, which is a separate mechanism. */
            -webkit-tap-highlight-color: transparent;
        }
        /* (hover: hover) only matches devices that support a real hover
           state (mouse/trackpad) - touch devices report hover:none, so a
           tap never triggers this "stuck hover" color flash there, per
           explicit request. :focus-visible (keyboard) is unaffected -
           it's a separate rule below, not gated on hover support. */
        @media (hover: hover) {
            .sticky-chapter-nav-btn:hover {
                filter: brightness(1.4);
            }
        }
        .sticky-chapter-nav-btn:focus-visible {
            filter: brightness(1.4);
        }
        .sticky-chapter-nav-btn svg {
            stroke-width: 4;
            /* Overrides the fixed width/height="28" attributes in the
               markup - CSS wins over an SVG's own presentation
               attributes, same scaling as the button box above (28px, then
               24px, now back up a step at 28px on request). */
            width: calc(28px * var(--chapter-nav-icon-scale));
            height: calc(28px * var(--chapter-nav-icon-scale));
        }
        /* Now a <button> (opens Bible Navigation - see openBibleNav() in
           app.js), not static text, so it needs its own reset on top of
           the look it already had - bigger text plus a border (per
           explicit request) so it visibly reads as tappable rather than
           as a label. Text uses --text-main (white in dark mode, black in
           light) rather than literal white, same reasoning as the audio
           button's icon color - literal white would vanish against the
           light-mode card background. */
        .sticky-chapter-nav-ref {
            font-family: inherit;
            background: transparent;
            /* Green (was pink, was gray before that), per explicit request. */
            border: 2px solid var(--accent-secondary);
            border-radius: 8px;
            padding: 4px 12px;
            cursor: pointer;
            font-weight: 700;
            color: var(--text-main);
            /* Tries to track --ui-scale like everything else, but clamped
               to the Small (0.75)/Large (1.2) User Interface Size range -
               same reasoning as --chapter-nav-icon-scale above, this is
               just the "ideal" starting size. fitStickyChapterNavRef()
               below can still shrink it well below this floor (it has its
               own separate, lower floor) since fitting on one line takes
               priority over hitting this ideal minimum. */
            font-size: calc(1.35rem * clamp(0.75, var(--ui-scale), 1.2));
            /* Forces a single line - fitStickyChapterNavRef() in app.js
               shrinks this font down (via an inline font-size override)
               only as far as needed to still fit that one line next to
               the Prev/Next arrows/audio button (see --chapter-nav-icon-
               scale - capped at Medium size, but otherwise not fixed),
               instead of letting long references wrap or overflow. */
            white-space: nowrap;
            -webkit-tap-highlight-color: transparent;
        }
        @media (hover: hover) {
            .sticky-chapter-nav-ref:hover {
                filter: brightness(1.4);
            }
        }
        .sticky-chapter-nav-ref:focus-visible {
            filter: brightness(1.4);
        }

        /* Speaker/play-audio button (ESV only) - pink outline and pink
           icon, same in idle and playing states. */
        .sticky-chapter-nav-audio-btn {
            /* Square (equal width/height), matching the </> nav buttons,
               per explicit request - was widened to match their old
               left/right-padding look before. Same --chapter-nav-icon-
               scale scaling as the nav buttons - kept at its own original
               36px base size even as the arrows' base size was reduced,
               per explicit request. */
            height: calc(36px * var(--chapter-nav-icon-scale));
            width: calc(36px * var(--chapter-nav-icon-scale));
            min-height: calc(36px * var(--chapter-nav-icon-scale));
            min-width: calc(36px * var(--chapter-nav-icon-scale));
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0;
            /* No border, per explicit request - the icon carries it alone.
               Colour is the state: --text-main at rest, --accent-primary
               once the player is open, so the accent means "this is what is
               open" rather than being on permanently. aria-pressed is
               already kept in sync with panelOpen by render() in
               initAudioPlayer(), so it can drive the styling too rather
               than needing a second class tracking the same thing. */
            border: 2px solid transparent;
            border-radius: 8px;
            background: transparent;
            color: var(--text-main);
            cursor: pointer;
            -webkit-tap-highlight-color: transparent;
        }
        .sticky-chapter-nav-audio-btn[aria-pressed="true"] {
            color: var(--accent-primary);
        }
        .sticky-chapter-nav-audio-btn svg {
            stroke: currentColor;
            /* Overrides the fixed width/height="24" attributes in the
               markup - CSS wins over an SVG's own presentation
               attributes, same scaling as the button box above. */
            width: calc(24px * var(--chapter-nav-icon-scale));
            height: calc(24px * var(--chapter-nav-icon-scale));
        }
        @media (hover: hover) {
            .sticky-chapter-nav-audio-btn:hover {
                filter: brightness(1.4);
            }
        }
        .sticky-chapter-nav-audio-btn:focus-visible {
            filter: brightness(1.4);
        }
        /* Same [hidden]-vs-author-origin-display fix used elsewhere in this
           file - .sticky-chapter-nav-audio-btn's own unconditional
           display:flex (author origin) otherwise beats the browser's
           default [hidden]{display:none} (user-agent origin) regardless
           of specificity, so setting audioBtn.hidden in app.js (see
           initAudioPlayer()) did nothing without this - the button would
           have stayed visible for every translation, not just ESV. */
        .sticky-chapter-nav-audio-btn[hidden] {
            display: none;
        }
        /* Loading (audio being fetched - see playChapterAudio() in
           app.js) - a spinning arc in place of the speaker icon. Used to
           grey the border too; there is no border now, so it greys the
           icon colour instead. */
        .sticky-chapter-nav-audio-btn.loading {
            color: var(--text-muted);
        }
        /* Rotating ring - a plain HTML element (not an SVG, which
           doesn't rotate around its own visual center by default the way
           this needs to; the actual bug behind the last two attempts
           looking broken was the display-toggle JS, not the shape/
           animation itself - see playChapterAudio() in app.js). */
        .sticky-chapter-nav-audio-spinner {
            /* Same scaling as the speaker icon it replaces while loading,
               so the button doesn't visibly shrink its content during the
               loading state. */
            width: calc(18px * var(--chapter-nav-icon-scale));
            height: calc(18px * var(--chapter-nav-icon-scale));
            border-radius: 50%;
            border: 3px solid var(--bg-hover);
            border-top-color: var(--text-main);
            animation: avb-spin 0.8s linear infinite;
        }
        @media (prefers-reduced-motion: reduce) {
            .sticky-chapter-nav-audio-spinner {
                animation: none;
            }
        }
        @keyframes avb-spin {
            to { transform: rotate(360deg); }
        }
        /* [hidden] no longer does anything for either of these two -
           playChapterAudio() sets style.display with explicit values
           instead (an empty-string clear kept losing to this exact rule,
           since the hidden ATTRIBUTE was never actually being removed
           either way) - kept only as a harmless fallback for the
           non-JS-controlled initial page state. */
        #sticky-chapter-nav-audio-icon[hidden] {
            display: none;
        }
        .sticky-chapter-nav-audio-spinner[hidden] {
            display: none;
        }

        /* Audio player panel - sits inside .sticky-chapter-nav itself (see
           the flex-direction comment above), so it always opens on the
           side of the nav row facing away from whichever screen edge the
           strip is pinned to. */
        /* Thru the Bible bar - sits between the chapter-nav row and the
           player panel, and is the only handle on a broadcast once the
           Resources dialog has closed behind you. Same border treatment as
           the panel below it so the two read as one stacked unit. */
        .ttb-bar {
            display: flex;
            align-items: stretch;
            gap: 0;
            background-color: var(--bg-expanded);
            border-top: 2px solid var(--border-color);
        }
        .ttb-bar[hidden] {
            display: none;
        }
        .sticky-chapter-nav.sticky-chapter-nav-bottom .ttb-bar {
            border-top: none;
            border-bottom: 2px solid var(--border-color);
        }
        /* Play/pause, shown only with the panel collapsed - open, the
           panel's own transport button is a few pixels below and two would
           just be confusing. */
        .ttb-bar-play {
            flex: 0 0 auto;
            display: flex;
            align-items: center;
            justify-content: center;
            min-width: calc(2.6rem * var(--ui-scale));
            padding: 0 4px 0 12px;
            background: none;
            border: none;
            cursor: pointer;
            color: var(--accent-primary);
        }
        /* rem * --ui-scale, not em - em resolved against an inherited
           font-size, so these three controls did not actually follow App
           Sizing the way the rest of the bar does. */
        .ttb-bar-play .toolbar-icon {
            width: calc(2.1rem * var(--ui-scale));
            height: calc(2.1rem * var(--ui-scale));
        }
        .ttb-bar.ttb-bar-open .ttb-bar-play {
            display: none;
        }
        /* The whole label area is the collapse toggle - a small chevron on
           its own would be a tiny target on a phone. */
        .ttb-bar-toggle {
            flex: 1 1 auto;
            min-width: 0;
            display: flex;
            align-items: center;
            justify-content: flex-start;
            gap: 10px;
            padding: 10px 8px 10px 12px;
            background: none;
            border: none;
            cursor: pointer;
            font-family: inherit;
            color: var(--text-main);
            text-align: left;
        }
        .ttb-bar-label {
            flex: 1 1 auto;
            display: flex;
            align-items: baseline;
            gap: 8px;
            min-width: 0;
        }
        /* "TTB" as a small tinted chip rather than more running text, so
           the reference beside it is what the eye actually reads. */
        .ttb-bar-tag {
            flex: 0 0 auto;
            padding: 2px 7px;
            border-radius: 5px;
            background-color: rgb(from var(--accent-primary) r g b / 0.18);
            color: var(--accent-primary);
            font-size: calc(1.1rem * var(--ui-scale));
            font-weight: 800;
            letter-spacing: 0.06em;
        }
        .ttb-bar-ref {
            min-width: 0;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        /* A label longer than its space slides back and forth (see
           setScrollingLabel() in app.js); the tag beside it stays put. */
        /* The strips show the tag only (TTB, BIBLE, NOTEBOOK...) - no
           reference, per explicit request; open it to see. */
        .ttb-bar-ref, .ttb-bar-ref-inner { display: none; }
        .ttb-bar-ref.is-scrolling { text-overflow: clip; }
        .ttb-bar-ref.is-scrolling .ttb-bar-ref-inner {
            animation: ttb-label-scroll var(--scroll-time, 8s) ease-in-out infinite alternate;
        }
        @keyframes ttb-label-scroll {
            0%, 18% { transform: translateX(0); }
            82%, 100% { transform: translateX(var(--scroll-dist, 0px)); }
        }
        @media (prefers-reduced-motion: reduce) {
            .ttb-bar-ref.is-scrolling { text-overflow: ellipsis; }
            .ttb-bar-ref.is-scrolling .ttb-bar-ref-inner { animation: none; }
        }
        /* The chevron as its own narrow button, then play, then the label. */
        .ttb-bar-toggle-icon { flex: 0 0 auto; padding: 10px 4px 10px 12px; }
        /* A collapsed strip - the minimized modals, or the TTB / Bible bar
           with its player folded away - sits on 2px of vertical padding. */
        .minimized-bar .ttb-bar-toggle,
        .ttb-bar:not(.ttb-bar-open) .ttb-bar-toggle {
            padding-top: 2px;
            padding-bottom: 2px;
        }
        /* ...and on a smaller chevron and X, since those set its height
           (about 32px all told). The chevron is plain text colour on every
           strip, per request. */
        .minimized-bar .ttb-bar-chevron,
        .ttb-bar:not(.ttb-bar-open) .ttb-bar-chevron {
            width: calc(1.625rem * var(--ui-scale));
            height: calc(1.625rem * var(--ui-scale));
            color: var(--text-main);
        }
        .minimized-bar .ttb-bar-close,
        .ttb-bar:not(.ttb-bar-open) .ttb-bar-close { font-size: calc(1.85rem * var(--ui-scale)); }
        /* Bookmark / bookmarks live on the strip only while its player is
           open; collapsed, the strip is chevron, play, tag, X. */
        .ttb-bar:not(.ttb-bar-open) .bar-open-only { display: none; }
        .ttb-bar-chevron {
            flex: 0 0 auto;
            width: calc(2.1rem * var(--ui-scale));
            height: calc(2.1rem * var(--ui-scale));
            transition: transform 0.18s ease;
        }
        /* Points down when the player is open (press to collapse), up when
           it is collapsed (press to bring it back). */
        .ttb-bar:not(.ttb-bar-open) .ttb-bar-chevron {
            transform: rotate(-180deg);
        }
        @media (prefers-reduced-motion: reduce) {
            .ttb-bar-chevron { transition: none; }
        }
        .ttb-bar-close {
            flex: 0 0 auto;
            display: flex;
            align-items: center;
            justify-content: center;
            min-width: calc(2.4rem * var(--ui-scale));
            padding: 0 8px 0 4px;
            background: none;
            border: none;
            cursor: pointer;
            font-family: inherit;
            font-size: calc(2.2rem * var(--ui-scale));
            line-height: 1;
            color: var(--text-secondary);
        }
        .ttb-bar-close:hover,
        .ttb-bar-close:focus-visible {
            color: var(--text-main);
        }

        .audio-player-panel {
            /* Tighter all round (was 22px 16px 24px), per request. */
            padding: 2px 16px 12px;
            background-color: var(--bg-expanded);
            border-top: 2px solid var(--border-color);
        }
        /* Expanded under the TTB bar, the two are one surface - no line. */
        .ttb-bar.ttb-bar-open + .audio-player-panel { border-top: none; }
        .sticky-chapter-nav.sticky-chapter-nav-bottom .ttb-bar.ttb-bar-open { border-bottom: none; }
        .sticky-chapter-nav.sticky-chapter-nav-bottom:has(.ttb-bar-open) .audio-player-panel { border-top: none; }
        .sticky-chapter-nav.sticky-chapter-nav-bottom .audio-player-panel {
            border-top: none;
            border-bottom: 2px solid var(--border-color);
        }
        .audio-player-panel[hidden] {
            display: none;
        }
        .audio-player-head[hidden] {
            display: none;
        }
        .audio-player-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 12px;
            margin-bottom: 4px;
        }
        .audio-player-title {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            letter-spacing: 0.03em;
            text-transform: uppercase;
            color: var(--text-main);
        }
        /* Same overall size as .app-menu-close-btn/#welcome-close-btn now
           (1.8em box, 1.375rem font - was a fixed 26px/1rem, its own
           smaller one-off), per explicit request that every modal's X,
           including this one, render at the same size. Shape (rounded
           square, no border) is unrelated to size and stays as its own
           thing. */
        .audio-player-close-btn {
            width: 1.8em;
            height: 1.8em;
            flex-shrink: 0;
            border: none;
            border-radius: 6px;
            background: transparent;
            color: var(--text-main);
            font-size: calc(1.375rem * var(--ui-scale));
            line-height: 1;
            cursor: pointer;
        }
        @media (hover: hover) {
            .audio-player-close-btn:hover {
                filter: brightness(1.4);
            }
        }
        .audio-player-close-btn:focus-visible {
            filter: brightness(1.4);
        }
        /* Title row: the controls (bookmark, bookmarks, collapse) at the
           top right, the title on its own line under them. */
        .audio-player-head { flex-wrap: wrap; gap: 0 12px; }
        .audio-head-btns { display: flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: auto; }
        .audio-player-head .audio-player-title { flex-basis: 100%; }
        /* Bookmark buttons: the third accent, and filled in while the
           current spot is bookmarked (renderMarkButtons() in app.js). */
        #ttb-bar-mark, #bible-bar-mark { color: var(--accent-focus); }
        #ttb-bar-mark.is-marked svg path, #bible-bar-mark.is-marked svg path { fill: currentColor; }
        .audio-icon-btn { display: grid; place-items: center; height: 1.4em; }
        .audio-icon-btn svg { width: 1.05em; height: 1.05em; }
        /* The transport row under the seek bar: previous, back 10, play,
           forward 10, next - play in the middle and the largest. */
        .audio-transport {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 14px;
            margin-top: 4px;
        }
        .audio-side-btn {
            width: 44px;
            height: 44px;
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            border: 2px solid var(--divider-color);
            background: transparent;
            color: var(--text-main);
            cursor: pointer;
            padding: 0;
        }
        /* Less padding, bigger icons (24px -> 30px in the same 44px ring). */
        .audio-side-btn svg { width: 30px; height: 30px; }
        .audio-side-btn:disabled { opacity: 0.4; cursor: default; }
        .audio-side-btn:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        @media (hover: hover) {
            .audio-side-btn:not(:disabled):hover { border-color: var(--accent-primary); }
        }
        /* Bookmark / bookmarks on the TTB bar, beside its chevron. */
        .ttb-bar-icon-btn {
            flex: 0 0 auto;
            display: flex;
            align-items: center;
            justify-content: center;
            /* Close together, and close to the X (per request). */
            min-width: calc(2rem * var(--ui-scale));
            padding: 0 2px;
            background: none;
            border: none;
            cursor: pointer;
            color: var(--text-main);
        }
        .ttb-bar-icon-btn svg { width: calc(1.4rem * var(--ui-scale)); height: calc(1.4rem * var(--ui-scale)); }
        .ttb-bar-icon-btn:focus-visible { outline: 3px solid var(--accent-focus); outline-offset: -3px; }
        /* Big play/pause - accent-primary outline throughout, tinted
           background (was yellow outline/tint, then a fixed pink tint),
           text-main icon color in both Idle/Play and Playing/Pause states
           (was yellow icon while playing) - per explicit request, the
           icon color and background tint no longer change between the
           two states. Background now tracks the same --accent-primary the
           border already did (was the fixed --highlight-pink palette
           color, which never changed with a customized Theme Colors
           accent, unlike the border right next to it) - same
           color-mix-into-card-background convention as .app-btn-primary/
           .btn-accent-yellow elsewhere in this file, with the fallback
           declaration below covering browsers without color-mix(). */
        .audio-play-pause-btn {
            /* Bigger overall, and the icon below grows even more than the
               button does (22px -> 34px vs. 56px -> 68px) so there's
               noticeably less empty space around it, per explicit
               request. */
            /* 72px (was 68, then 70) per request. */
            width: 72px;
            height: 72px;
            flex-shrink: 0;
            border-radius: 50%;
            border: 3px solid var(--accent-primary);
            background: var(--bg-card);
            color: var(--text-main);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            padding: 0;
        }
        @supports (background-color: color-mix(in srgb, red, blue)) {
            .audio-play-pause-btn {
                background-color: color-mix(in srgb, var(--accent-primary) 15%, var(--bg-card));
            }
        }
        .audio-play-pause-btn svg {
            width: 38px;
            height: 38px;
        }
        @media (hover: hover) {
            .audio-play-pause-btn:hover {
                filter: brightness(1.4);
            }
        }
        .audio-play-pause-btn:focus-visible {
            filter: brightness(1.4);
        }
        /* Fills the remaining row width, but stops short of both the
           play/pause button and the panel's own right edge via matching
           left/right margin (doubled from an earlier right-only 8px, per
           explicit request). */
        /* The seek bar has the whole row to itself now. */
        .audio-seek-col {
            flex: 1;
            min-width: 0;
            display: flex;
            flex-direction: column;
            gap: 4px;
        }
        /* Bookmarks list. */
        .audio-marks-list { display: flex; flex-direction: column; }
        .audio-mark-row {
            display: flex;
            align-items: center;
            gap: 8px;
            border-bottom: 1px solid var(--divider-color);
        }
        .audio-mark-row:last-child { border-bottom: 0; }
        .audio-mark-btn {
            flex: 1 1 auto;
            min-width: 0;
            display: flex;
            flex-direction: column;
            gap: 2px;
            padding: 10px 4px;
            background: none;
            border: none;
            color: var(--text-main);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            text-align: left;
            cursor: pointer;
        }
        .audio-mark-btn:focus-visible { outline: 3px solid var(--accent-focus); outline-offset: 2px; border-radius: 6px; }
        .audio-mark-kind {
            font-size: 0.8em;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            font-weight: 800;
            color: var(--accent-primary);
        }
        .audio-mark-kind.ttb { color: var(--accent-secondary); }
        .audio-mark-meta { color: var(--text-secondary); font-size: calc(1.15rem * var(--ui-scale)); font-variant-numeric: tabular-nums; }
        .audio-mark-x {
            flex: 0 0 auto;
            background: none;
            border: none;
            color: var(--text-secondary);
            font-size: calc(1.6rem * var(--ui-scale));
            padding: 6px 10px;
            cursor: pointer;
        }
        .audio-mark-x:hover, .audio-mark-x:focus-visible { color: var(--text-main); }
        #audio-marks-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); z-index: 60; }
        #audio-marks-dialog:not([hidden]) {
            position: fixed; inset: 0; z-index: 61;
            display: flex; align-items: center; justify-content: center; padding: 1rem;
        }
        #audio-marks-dialog [hidden] { display: none !important; }
        #audio-marks-filter-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); z-index: 62; }
        #audio-marks-filter-dialog:not([hidden]) {
            position: fixed; inset: 0; z-index: 63;
            display: flex; align-items: center; justify-content: center; padding: 1rem;
        }
        #audio-marks-filter-dialog [hidden] { display: none !important; }
        .audio-marks-tools { margin: 0 0 12px; justify-content: flex-end; }
        .audio-seek-times {
            display: flex;
            justify-content: space-between;
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-main);
            font-variant-numeric: tabular-nums;
        }
        /* Custom-drawn seek track + handle, per explicit request - a NATIVE
           range thumb's rendered center is always inset by half its own
           width from the track's ends (a fixed browser behavior), which
           can never be made to land exactly on a plain percentage fill at
           every value while also keeping normal rounded track ends - one
           or the other always gave a little. Drawing the track fill and
           the handle as two plain, separately positioned layers sidesteps
           that limitation entirely: both are computed as a plain
           percentage of this same wrapper via --frac (kept in sync with
           playback progress by initAudioPlayer() in app.js), so they
           always agree by construction, at every value, including 0%/
           100% - the round handle overhanging slightly past the track's
           rounded ends there is normal, expected slider behavior (the
           same way e.g. a video scrubber's handle works). The real
           <input> underneath still handles all actual interaction (drag,
           keyboard, screen reader) exactly as before - it's just made
           fully invisible (opacity: 0) so it never visually competes with
           the drawn version above it. */
        .audio-seek-track-wrap {
            position: relative;
            height: 28px;
            display: flex;
            align-items: center;
        }
        .audio-seek-track {
            position: absolute;
            left: 0;
            right: 0;
            height: 8px;
            border-radius: 999px;
            border: 2px solid var(--accent-primary);
            background: linear-gradient(to right,
                var(--accent-primary) 0%,
                var(--accent-primary) calc(var(--frac, 0) * 100%),
                var(--bg-hover) calc(var(--frac, 0) * 100%),
                var(--bg-hover) 100%);
        }
        .audio-seek-handle {
            position: absolute;
            top: 50%;
            left: calc(var(--frac, 0) * 100%);
            transform: translate(-50%, -50%);
            width: 28px;
            height: 28px;
            border-radius: 50%;
            /* Opaque green center (was a translucent tint), per explicit
               request - --accent-secondary itself is already a solid,
               opaque color, unlike --highlight-green's rgba. */
            background: var(--accent-secondary);
            /* Green, thinner than before (was 5px, then 4px), per
               explicit request. */
            border: 3px solid var(--accent-secondary);
            box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
            pointer-events: none;
        }
        /* Keyboard-only focus ring, drawn on the decorative handle rather
           than relying on the real (invisible) input's own outline - see
           the focus/blur listeners in initAudioPlayer(). */
        .audio-seek-track-wrap.focus-visible .audio-seek-handle {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        .audio-seek-bar {
            -webkit-appearance: none;
            appearance: none;
            position: absolute;
            inset: 0;
            width: 100%;
            height: 100%;
            margin: 0;
            background: transparent;
            opacity: 0;
            cursor: pointer;
        }

        /* Shared button look - structurally matches Access-a-Lyrics' .toolbar
           button (rounded-8px, 2px border, tinted-on-hover) but keeps a
           larger font-size than their 14px, since big readable touch targets
           are this app's whole design intent. .app-btn-primary is the pink-
           tinted "primary action" variant (Access-a-Lyrics' .primary). */
        .app-btn {
            border-width: 2px;
            border-style: solid;
            border-radius: 8px;
            padding: 10px 18px;
            font-weight: 700;
            font-size: calc(1.3rem * var(--ui-scale));
            font-family: inherit;
            cursor: pointer;
        }
        .app-btn-primary {
            background-color: color-mix(in srgb, var(--accent-primary) 15%, var(--bg-card));
        }
        .app-btn-primary:hover, .app-btn-primary:focus-visible {
            background-color: color-mix(in srgb, var(--accent-primary) 28%, var(--bg-card));
        }

        /* Previous/Next chapter buttons (both the pair above the passage and
           the pair below it) - fills the full width available in its half of
           the row (was a fixed w-5/12, which left the buttons cut off/
           clipped on narrow phones instead of actually using the space),
           with zero gap between the arrow icon and the label so "Previous"
           sits tight against it. Falls back to an abbreviated label
           ("Prev"/"Next") once the button is too narrow for the full word,
           then shrinks the label's font size as a last resort if even the
           abbreviation doesn't fit. */
        .nav-chapter-btn {
            display: flex;
            flex: 1 1 0;
            align-items: center;
            justify-content: center;
            gap: 0;
            white-space: nowrap;
            min-width: 0;
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .nav-btn-label-short { display: none; }
        @media (max-width: 480px) {
            .nav-btn-label-full { display: none; }
            .nav-btn-label-short { display: inline; }
        }
        @media (max-width: 340px) {
            .nav-chapter-btn { font-size: calc(1.3rem * var(--ui-scale)); }
        }
        @media (max-width: 280px) {
            .nav-chapter-btn { font-size: calc(1.3rem * var(--ui-scale)); }
        }

        /* App Menu dialog card - colors/structure copied from the root CoA
           Apps site's mobile nav modal (.nav-modal/.nav-modal-header/
           .nav-modal-close-btn/.nav-modal-body in the root style.css), which
           itself was built to match the Access-a-Lyrics Settings modal:
           page background (not card background) so the dialog blends with
           the page behind its border, a non-scrolling header with a
           circular close button, and a separately-scrolling body. */
        .app-menu-card {
            /* Was a match for Access-a-Lyrics' own #app-settings-dialog
               (90%/460px) - widened a bit further per explicit request;
               no longer an exact match. Doesn't affect Verse Actions,
               which has its own separate width override further down. */
            /* 99% (was 90%) per explicit request; the desktop caps below
               are what actually limit it on a wide screen. */
            width: 99%;
            max-width: 500px;
            max-height: 85vh;
            overflow-wrap: break-word;
            padding: 0;
            border: 2px solid var(--divider-color);
            border-radius: 0.75rem;
            background-color: var(--bg-body);
            color: var(--text-main);
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }
        /* Resources is a fixed HEIGHT, not a max-height, per explicit
           request: its three tabs carry very different amounts of content
           (Study can be a screenful, an empty Plans tab a few lines), and
           a max-height let the card grow and shrink as you switched,
           which read as the whole dialog jumping around. Pinned to the
           same 85vh the base rule caps at, so nothing gets taller than it
           already could - the body still scrolls inside it. */
        #reading-dialog .app-menu-card {
            height: 85vh;
        }
        /* Wider on mobile (was the same 90% as desktop, then 95%, then
           97%) - still comfortably inside the viewport width, per
           explicit request. Doesn't affect Verse Actions, which has its
           own more specific mobile width override (100%) already. */
        @media (max-width: 639px) {
            .app-menu-card {
                width: 99%;
            }
        }
        /* How to Use - was sized like Access-a-Lyrics' .shortcuts-dialog
           (min(30rem/480px, 92vw)), the closest Access-a-Lyrics equivalent
           (a text-heavy reference dialog, not a settings form) - bumped
           up alongside the base .app-menu-card above so it stays wider
           than that, not narrower (480px < the new 500px base). */
        .app-menu-card-wide {
            max-width: min(33rem, 92vw);
        }
        .app-menu-header {
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 12px;
            /* Left/right narrowed from 20px, per explicit request - less
               empty space between the modal's own edge and its title/
               content. Verse Actions pinned back to the old 20px (see its
               own override further down) since it was excluded from that
               specific request. Top/bottom also reduced (was 18px) - too
               much space above/below the title and close button, per
               explicit follow-up - Verse Actions' own override below
               matches this part since it wasn't about the left/right
               exclusion. Then cut again from 12px to 4px (two-thirds
               off) per a later request that every modal's title bar be
               shorter and all of them the same, then halved again to
               2px. */
            padding: 2px 10px;
            border-bottom: 2px solid var(--divider-color);
            background-color: var(--bg-body);
        }
        /* More than one control at the right of a title bar (Properties
           next to the X, later Minimize) - one flex group so they sit
           on one line and the heading keeps its space. */
        .app-menu-header-btns {
            display: flex;
            align-items: center;
            gap: 0;
            flex-shrink: 0;
        }
        .nb-header-btn {
            display: grid;
            place-items: center;
        }
        .nb-header-btn svg {
            width: 1.05em;
            height: 1.05em;
        }
        /* The entry's title, typed and placeholder alike, at the size and
           weight the card titles use. */
        #nb-entry-title, #nb-entry-title::placeholder {
            font-size: calc(1.55rem * var(--ui-scale));
            font-weight: 800;
        }
        /* The editor is one surface (per explicit request): the body takes
           the raised colour the fields had, every border goes, and one
           line under the title is all that separates it from the text. */
        #nb-entry-dialog .app-menu-body { background-color: var(--bg-expanded); }
        #nb-entry-dialog .app-menu-body > .settings-group-fields:first-child { margin-top: 6px; }
        #nb-entry-dialog #nb-entry-title {
            border: none;
            border-bottom: 2px solid var(--divider-color);
            border-radius: 0;
            background: transparent;
            padding: 10px 4px 12px;
            /* A textarea that behaves as one growing line. */
            display: block;
            width: 100%;
            resize: none;
            overflow: hidden;
            line-height: 1.3;
            font-family: inherit;
        }
        #nb-entry-dialog #nb-entry-title:focus-visible { outline: none; border-bottom-color: var(--accent-focus); }
        #nb-entry-dialog .nb-field-editor { margin-top: 0; }
        #nb-entry-dialog .nb-editor, #nb-entry-dialog .nb-editor.ql-container {
            border: none;
            border-radius: 0;
            background: transparent;
        }
        #nb-entry-dialog .nb-editor .ql-editor { padding: 18px 4px 26px; }

        /* Category on a card and the lit chip in the picker. */
        .nb-card-cat {
            font-size: 0.8em;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            font-weight: 800;
            color: var(--accent-secondary);
        }
        .nb-suggest .nb-chip.selected {
            color: var(--text-main);
            border-color: var(--accent-secondary);
            background-color: rgb(from var(--accent-secondary) r g b / 0.14);
        }
        .nb-autosave {
            margin: 12px 0 0;
            font-size: calc(1.15rem * var(--ui-scale));
            color: var(--text-secondary);
        }
        .nb-field-first { margin-top: 0; }
        /* Verse preview: verse numbers small and secondary, the words at
           body size. Capped like the other views. */
        #nb-verse-dialog .app-menu-card { max-height: 90vh; }
        .nb-verse-text { display: flex; flex-direction: column; gap: 0.6em; }
        .nb-verse-line { margin: 0; font-size: calc(1.3rem * var(--ui-scale)); line-height: 1.5; }
        .nb-verse-num {
            font-size: 0.75em;
            font-weight: 800;
            color: var(--accent-secondary);
            vertical-align: super;
        }
        .nb-leave-btns {
            display: flex;
            flex-direction: column;
            gap: 8px;
            margin-top: 14px;
        }
        /* Minimize (a chevron-down, left of the title, in the accent the
           TTB bar's chevron uses) - added to each modal by
           injectMinimizeButtons() in app.js, which also makes the title
           itself minimize on tap. */
        .app-menu-header-lead {
            display: flex;
            align-items: center;
            gap: 4px;
            min-width: 0;
            flex: 1 1 auto;
        }
        .app-menu-close-btn.minimize-btn { display: grid; place-items: center; color: var(--accent-primary); }
        .minimize-btn svg { width: 1.15em; height: 1.15em; }
        /* Save, up in the title bar beside the X (which cancels). A filled
           pill in the secondary accent - the same green the "bold" action
           buttons already carry as their outline. */
        .app-menu-save-btn {
            flex-shrink: 0;
            padding: 5px 10px;
            margin-right: 2px;
            border: none;
            border-radius: 999px;
            background-color: var(--accent-secondary);
            color: var(--bg-body);
            font-family: inherit;
            font-size: calc(1.15rem * var(--ui-scale));
            font-weight: 800;
            cursor: pointer;
        }
        .app-menu-save-btn:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        @media (hover: hover) {
            .app-menu-save-btn:hover { filter: brightness(1.1); }
        }
        /* Buttons inside modals: a little less padding, and tighter rows,
           per explicit request. */
        .app-menu-card .app-btn { padding: 8px 14px; }
        .app-menu-card .plan-btn-row, .app-menu-card .nb-btn-row { gap: 6px; }
        /* The minimized strip - the TTB bar's twin, green where that one
           is pink so the two read as different things when both show. */
        .minimized-bar-tag {
            background-color: rgb(from var(--accent-secondary) r g b / 0.18);
            color: var(--accent-secondary);
        }
        .bible-bar-tag {
            background-color: rgb(from var(--accent-focus) r g b / 0.18);
            color: var(--accent-focus);
        }
        .bible-bar .ttb-bar-play { color: var(--accent-focus); }
        /* Drawn pointing up already; the TTB bar's collapsed-state flip
           (a more specific rule) must not turn it over. */
        .ttb-bar.minimized-bar:not(.ttb-bar-open) .ttb-bar-chevron,
        .ttb-bar.minimized-bar .ttb-bar-chevron { transform: none; }
        .app-menu-heading {
            margin: 0;
            /* Up from 1.45rem per request, alongside the shorter bar. */
            font-size: calc(1.55rem * var(--ui-scale));
            font-weight: bold;
        }
        /* Verse Actions only - wraps the reference heading plus the Add/
           Remove range controls so they can sit on the same line (and wrap
           together onto a second line on narrow screens) while still
           being one flex item next to the close button. */
        .verse-actions-heading-row {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 10px;
            min-width: 0;
        }
        /* Wraps Add Verse + Remove together so the pair can be forced onto
           their own line below the reference (H2) as a UNIT, via
           flex-basis:100% on this wrapper within .verse-actions-heading-row's
           flex-wrap row - a standard "always wrap this one item" trick.
           Applying flex-basis:100% to Add/Remove individually instead
           would make the SECOND one wrap onto a third line by itself
           (nothing left on line 2 to share with it), which is why they
           need a shared wrapper rather than each carrying their own
           flex-basis. */
        .verse-range-controls {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 10px;
            flex-basis: 100%;
        }
        /* "Add Verse >" - extends the active verse range by one. Yellow
           accent per request, pill-shaped like the other small inline
           controls in this file (.more-colors-manage-btn). Screen reader
           name is just "Add verse" (set via aria-label in the HTML)
           regardless of how many verses are already selected - simpler
           than announcing a running count. */
        .verse-range-add-btn {
            display: inline-flex;
            align-items: center;
            gap: 2px;
            flex-shrink: 0;
            background: none;
            /* Green (was yellow), per explicit request. */
            border: 2px solid var(--accent-secondary);
            border-radius: 999px;
            color: var(--accent-secondary);
            font-weight: 700;
            font-size: calc(1.3rem * var(--ui-scale));
            padding: 4px 12px;
            cursor: pointer;
        }
        @supports (background-color: color-mix(in srgb, red, blue)) {
            /* No default tint (transparent) - only tints on hover/focus,
               per explicit request. */
            .verse-range-add-btn:hover, .verse-range-add-btn:focus-visible {
                background-color: color-mix(in srgb, var(--accent-secondary) 15%, var(--bg-card));
            }
        }
        .verse-range-add-chevron {
            width: 16px;
            height: 16px;
        }
        /* Same [hidden]-vs-author-origin-display fix as #more-colors-toggle-btn
           and #highlight-swatch-row-secondary elsewhere in this file - the
           inline-flex rule above beats the browser's own [hidden]{display:none}
           regardless of specificity, so it needs an explicit override. */
        #verse-range-add-btn[hidden] {
            display: none;
        }
        /* "Remove" (shrink the range back down by one verse) - plain/muted
           styling since it's a secondary, undo-style action next to the
           more prominent green Add Verse button. Sits on the same line as
           Add Verse (see .verse-range-controls above). */
        .verse-range-remove-btn {
            display: inline-flex;
            align-items: center;
            gap: 2px;
            flex-shrink: 0;
            background: none;
            border: 2px solid var(--border-color);
            border-radius: 999px;
            color: var(--text-main);
            font-weight: 700;
            font-size: calc(1.3rem * var(--ui-scale));
            padding: 4px 12px;
            cursor: pointer;
        }
        .verse-range-remove-btn:hover, .verse-range-remove-btn:focus-visible {
            background-color: var(--bg-hover);
        }
        /* Same [hidden]-vs-author-origin-display fix as #verse-range-add-btn
           above - the inline-flex rule beats [hidden]{display:none}. */
        #verse-range-remove-btn[hidden] {
            display: none;
        }
        /* Box tightened from 2.3em to 1.8em (less empty space around the
           X glyph - font-size, and so the glyph itself, unchanged), per
           explicit request. Also matched by #welcome-close-btn and
           .audio-player-close-btn so every modal's X (including the
           audio player's) renders at the same size. */
        .app-menu-close-btn {
            flex-shrink: 0;
            width: 1.8em;
            height: 1.8em;
            background: transparent;
            /* No outline - the glyph is enough, and a ring around every X
               was one more line competing with the modal's own. */
            border: none;
            border-radius: 50%;
            color: var(--text-main);
            font-size: calc(1.375rem * var(--ui-scale));
            line-height: 1;
            cursor: pointer;
        }
        .app-menu-close-btn:hover,
        .app-menu-close-btn:focus-visible {
            background-color: var(--border-color);
            color: var(--bg-body);
        }
        .app-menu-body {
            /* Left/right narrowed from a flat 20px, per explicit request -
               less empty space between the modal's own edge and its
               dropdowns/text. Verse Actions pinned back to the old 20px
               (see its own override further down) since it was excluded
               from this specific request. */
            /* Sides 10px (was 14px) per explicit request. */
            padding: 20px 10px;
            overflow-y: auto;
            flex: 1 1 auto;
            min-height: 0;
            /* iOS Safari has historically needed this for touch/momentum
               scrolling to work reliably on a scrollable element nested
               inside a `position: fixed` ancestor (every dialog here is
               fixed-positioned) - without it, a swipe inside the modal
               body can silently do nothing instead of scrolling. Ignored
               (harmlessly) by non-WebKit browsers. */
            -webkit-overflow-scrolling: touch;
        }
        /* Single column always, now that the modal itself is only 460px wide
           (was a responsive grid via Tailwind that could go 2-up - no longer
           makes sense at this width). */
        .settings-group-fields {
            display: flex;
            flex-direction: column;
            /* More breathing room between each option within an accordion,
               per explicit request - was 16px, then 24px, bumped once
               more. */
            gap: 32px;
            padding-top: 16px;
        }
        .settings-group-fields label {
            display: block;
            font-weight: 700;
            margin-bottom: 4px;
        }
        /* Chapter Nav Strip Position's label specifically, sitting right
           under the On/Off switch + beta warning paragraph above it in
           the same field - a bit of separation so it doesn't read as
           crowded against the warning text. */
        .settings-field-label-gap {
            margin-top: 12px;
        }
        /* Chapter/Verse (Bible Navigation, Drop Downs style) share a row
           instead of each getting its own full-width row like Book above -
           flex-wrap so a narrow modal still gets a row each rather than
           clipping/squeezing either select. */
        .bible-nav-chapter-verse-row {
            display: flex;
            flex-wrap: wrap;
            gap: 16px;
        }
        .bible-nav-chapter-verse-row > div {
            flex: 1 1 140px;
            min-width: 0;
        }
        /* Auto Scroll toggle row - label stays block (matches every other
           field label above), the switch button itself sits to its right. */
        .settings-switch-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 12px;
        }
        .settings-switch-row label {
            margin-bottom: 0;
        }
        /* Accessible on/off switch (role="switch" in the HTML) - same
           pill-button-with-text convention used for equivalent toggles in
           other CoA Apps (e.g. Typing Speedometer's Text-to-Speech/Sound
           Effects switches), restyled with this app's own button language. */
        /* 1.2rem = the app's button size (.app-btn) - these were 1.1. */
        .switch-btn {
            background: transparent;
            border: 2px solid var(--divider-color);
            border-radius: 999px;
            color: var(--text-main);
            padding: 8px 20px;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            font-family: inherit;
            cursor: pointer;
            min-width: 80px;
            flex-shrink: 0;
        }
        /* Pinned to the same fixed green as the CoA Apps back-link (not
           var(--accent-secondary)) - excluded from this session's ongoing
           "green" color tweaks, per explicit request, same exclusion as
           the theme toggle/back-link/login-signup. */
        .switch-btn[aria-checked="true"] {
            background-color: #4ade80;
            border-color: #4ade80;
            color: #111827;
        }
        :root[data-theme="light"] .switch-btn[aria-checked="true"] {
            background-color: #097732;
            border-color: #097732;
        }
        /* Styled the same as .settings-field-note now (was bold yellow),
           per explicit request - Experimental Features' descriptions
           shouldn't read differently from every other setting's. */
        .settings-beta-warning {
            margin: 6px 0 0;
            color: var(--text-main);
            opacity: 0.85;
            font-size: calc(1.3rem * var(--ui-scale));
        }
        /* Full width of the settings-group-fields column it sits in
           (same width every dropdown/select in this menu already uses),
           with a generous gap above separating it from the Auto Scroll
           field right above it. */
        #clear-passage-cache-btn {
            display: block;
            width: 100%;
            margin-top: 28px;
        }
        /* Plain horizontal rule separating the settings-group accordions
           above from the How to Use button below. */
        .settings-divider {
            border: none;
            border-top: 2px solid var(--divider-color);
            margin: 20px 0 0;
        }
        /* Same divider, but sitting above the first accordion instead of
           below the last one - needs its own bottom margin (the plain
           .settings-divider above relies on the next element, previously
           always a button with its own margin-top, for that gap) to match
           the ~22px breathing room every other accordion-to-accordion
           border already has via .settings-group's padding/margin. */
        .settings-divider-top {
            margin-bottom: 22px;
        }
        /* Plain descriptive note under a setting (e.g. what Verbal Feedback
           actually does) - deliberately more subdued than
           .settings-beta-warning above, which is reserved for an actual
           caution, not routine explanation. */
        .settings-field-note {
            margin: 6px 0 0;
            color: var(--text-main);
            opacity: 0.85;
            font-size: calc(1.3rem * var(--ui-scale));
        }
        #auto-scroll-speed-field {
            margin-top: 12px;
        }

        /* Theme Colors picker (Settings > Other App Options) - a
           dropdown + decorative swatch per accent slot, same pattern as
           Accessible Story Creator's accent-color presets. */
        .color-preset-row {
            display: flex;
            flex-direction: column;
            gap: 14px;
            margin-top: 10px;
        }
        .color-preset-field {
            flex: 1;
            min-width: 130px;
        }
        .color-preset-label {
            display: block;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: bold;
            text-transform: uppercase;
            letter-spacing: 0.03em;
            /* --text-main (was --text-muted), per explicit request. */
            color: var(--text-main);
            margin-bottom: 6px;
        }
        #theme-colors-open-btn {
            margin-top: 8px;
        }
        .color-preset-input-row {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .color-preset-input-row select {
            flex: 1;
        }
        .color-swatch {
            width: 28px;
            height: 28px;
            flex-shrink: 0;
            border-radius: 50%;
            border: 2px solid var(--border-color);
            order: -1;
        }
        #theme-colors-reset-btn {
            margin-top: 20px;
        }

        /* Theme Colors dialog - same positioning rules and z-index tier as
           Clear Passage Cache (stacks on top of Account Settings, and the
           two never open at once). Missing this exact block is what broke
           the feature the first time around - removing [hidden] alone
           just leaves an in-flow block instead of a centered overlay. */
        #theme-colors-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 62;
        }
        #theme-colors-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 63;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Dark/Light toggle - copied from the root CoA Apps site's own
           .theme-toggle-group/.theme-toggle-btn (style.css), reusing this
           app's own --border-color/--text-main/--accent-primary/--bg-body
           variables (which now carry the same hex values as the root site's
           --border-light/--text-color/--accent-pink/--bg-color) instead of
           duplicating separate hardcoded colors. Replaces the old Dark/
           Light/Sepia Mode <select>. */
        .theme-toggle-group {
            display: flex;
            gap: 4px;
            border: 2px solid var(--divider-color);
            border-radius: 8px;
            padding: 3px;
            width: fit-content;
        }
        .app-menu-theme-toggle { margin-bottom: 20px; }
        .theme-toggle-btn {
            background-color: transparent;
            color: var(--text-main);
            border: none;
            border-radius: 6px;
            padding: 6px 14px;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: bold;
            cursor: pointer;
            font-family: inherit;
        }
        .theme-toggle-btn:hover,
        .theme-toggle-btn:focus-visible {
            background-color: var(--border-color);
            color: var(--bg-body);
        }
        /* Pinned to the fixed pink (not var(--accent-primary)) - excluded
           from Theme Colors customization, same exclusion as the CoA Apps
           link above. */
        .theme-toggle-btn[aria-checked="true"] {
            background-color: #f472b6;
            color: var(--bg-body);
        }
        :root[data-theme="light"] .theme-toggle-btn[aria-checked="true"] {
            background-color: #800d48;
        }

        /* Settings groups (Bible Navigation/Reading & Audio/Passage
           Appearance) - accordion style copied from Access-a-Lyrics'
           .settings-group (style.css): a "+"/"-" marker instead of a
           rotating chevron SVG, uppercase focus-color heading, flat list
           separated by a bottom border rather than each being its own
           bordered card. Replaces the old nested "Settings" accordion
           wrapping these three. */
        /* Section heading sitting above the accordion list (below the
           theme toggle/account rows) - plain main text color (white dark
           mode/black-ish light mode, same as passage body text), not an
           accent, so it reads as a plain divider label rather than
           another colored element. */
        /* Overrides the divider from shared/coa-auth-modal.css, which sits
           directly above the Settings heading in this app's menu. Scoped
           here rather than changed at the source: that stylesheet is shared
           by every CoA app and the others still want the line. */
        #account-settings-dialog .coa-auth-account-section {
            border-bottom: none;
            padding-bottom: 0;
        }
        .settings-section-heading {
            color: var(--text-main);
            font-weight: 800;
            font-size: calc(1.3rem * var(--ui-scale));
            margin: 4px 0 8px;
        }
        .settings-group { margin-bottom: 22px; padding-bottom: 22px; border-bottom: 2px solid var(--border-color); }
        .settings-group:last-of-type { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
        .settings-group summary {
            /* Matches .app-menu-heading's size (calc(1.3rem *
               var(--ui-scale))), per explicit request - was
               calc(1rem * var(--ui-scale)), a step smaller. */
            cursor: pointer; font-size: calc(1.3rem * var(--ui-scale)); font-weight: bold; text-transform: uppercase; letter-spacing: 0.04em;
            color: var(--accent-secondary); margin: 0; list-style: none;
            /* flex-start (was center) - most summaries now stack a title
               + description (see .settings-group-summary-text below), so
               the marker should align with the title's own line, not sit
               centered across both lines. */
            display: flex; align-items: flex-start; gap: 0.5em;
        }
        .settings-group summary::-webkit-details-marker { display: none; }
        .settings-group summary::before { content: "+"; display: inline-block; position: relative; top: -2px; color: var(--accent-primary); font-weight: 800; transform: scale(1.3); }
        .settings-group[open] summary::before { content: "\2212"; }
        /* Stacks the title above its description INSIDE <summary> (rather
           than as a separate element after it) so the description stays
           visible even while the accordion is collapsed - <details> only
           ever renders <summary> itself when closed, everything else is
           hidden until [open]. .settings-group-title carries no rules of
           its own - it just inherits summary's bold/uppercase/accent
           styling above as plain pass-through inheritance. */
        .settings-group-summary-text {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }
        /* ".settings-group summary" prefix (not just the class alone) is
           needed to beat summary's own color:var(--accent-secondary) just
           above - that ancestor-scoped selector (.settings-group summary,
           specificity 0,1,1) otherwise outranks a bare class selector
           (0,1,0) regardless of how deeply nested the element actually
           is, so this was silently rendering green instead of
           var(--text-main) without the extra qualifier. */
        .settings-group summary .settings-group-description {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: normal;
            text-transform: none;
            letter-spacing: normal;
            /* Full-strength --text-main, no opacity dimming (was 0.75) -
               per explicit request, plain white in dark mode/black in
               light mode, not a dimmed-down gray. */
            color: var(--text-main);
        }

        /* Dropdowns - style copied from Access-a-Lyrics' .field select
           (style.css): pink border, custom chevron SVG (swaps color per
           theme) instead of the native OS arrow. The base width/height/
           padding/border-width already comes from this file's shared
           "select, input" rule above; this just adds the chevron and a
           couple of overrides on top of it. */
        select {
            /* Yellow, same as every other field (see the shared
               "select, input, textarea" rule above) - this more specific
               per-element rule was silently winning back to pink for
               <select> elements specifically until this override. */
            border-color: var(--accent-focus);
            appearance: none;
            -webkit-appearance: none;
            -moz-appearance: none;
            background-repeat: no-repeat;
            background-position: right 12px center;
            background-size: 22px 22px;
            padding-right: 46px;
            background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f2f2f2' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
        }
        :root[data-theme="light"] select {
            background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23121212' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
        }
        /* Themed focus outline for every select - previously came from an
           "accent-ring" class these selects no longer carry now that they're
           styled by element type instead. */
        select:focus {
            outline-color: var(--accent-focus) !important;
        }

        /* How to Use - pink-bordered button at the bottom of the menu that
           opens the separate #how-to-use-dialog, replacing the old inline
           collapsible section. */
        .how-to-use-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            width: 100%;
            margin-top: 36px;
            background: transparent;
            color: var(--text-main);
            border: 2px solid var(--accent-primary);
            border-radius: 10px;
            padding: 12px 16px;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: bold;
            font-family: inherit;
            cursor: pointer;
        }
        .how-to-use-btn:hover,
        .how-to-use-btn:focus-visible {
            background-color: color-mix(in srgb, var(--accent-primary) 15%, transparent);
        }


        /* Hamburger/app-menu button - green border (was pink via its own
           .accent-border class, before that yellow). Search - pink border
           (was green), its own .accent-border class already provides this
           so no override needed there anymore. ID selector needed to beat
           .accent-border's !important on the hamburger. */
        #account-settings-btn {
            border-color: var(--accent-secondary) !important;
        }
        @supports (background-color: color-mix(in srgb, red, blue)) {
            #account-settings-btn {
                background-color: color-mix(in srgb, var(--accent-secondary) 15%, var(--btn-bg));
            }
            #account-settings-btn:hover, #account-settings-btn:focus-visible {
                background-color: color-mix(in srgb, var(--accent-secondary) 28%, var(--btn-bg));
            }
            /* Pink tint, per explicit request. */
            #search-btn {
                background-color: color-mix(in srgb, var(--accent-primary) 15%, var(--btn-bg));
            }
            #search-btn:hover, #search-btn:focus-visible {
                background-color: color-mix(in srgb, var(--accent-primary) 28%, var(--btn-bg));
            }
        }

        /* Account Settings dialog (new gear button, top right of header) - plain
           CSS rather than Tailwind's hidden/flex utility classes, since toggling
           the native [hidden] attribute alongside a permanent Tailwind "flex"
           class on the same element is unreliable (equal-specificity cascade
           tie between Tailwind's .flex and the UA [hidden] rule). Scoping the
           flex layout to :not([hidden]) sidesteps that entirely. */
        #account-settings-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        #account-settings-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Clear Passage Cache confirm dialog - stacks on top of Account
           Settings (z-60/61), same tier as Clear History stacking on Bible
           Navigation below; the two never open at once since Settings and
           Bible Navigation are separate top-level dialogs. */
        #clear-cache-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 62;
        }
        #clear-cache-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 63;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* How to Use dialog - same positioning rules as Account Settings
           above (this one was missing entirely, which is why opening it
           didn't visibly do anything: removing [hidden] just left it as a
           normal in-flow block instead of a centered overlay). */
        #how-to-use-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        #how-to-use-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Type Command modal - same positioning rules as How to Use above.
           Opened from the toolbar mic button/Shift+Space when Interaction
           Style (Settings > Other App Options) is "Text Input", or from
           the matching voice commands regardless of which mode is active. */
        #type-command-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        /* height (not just inset:0's implied full-viewport stretch) is set
           from --vv-height, updated in app.js from visualViewport's own
           height whenever it fires (which a mobile on-screen keyboard
           does, by shrinking the visual viewport without shrinking the
           layout one) - top:0 plus an explicit height wins out over
           inset's implied bottom:0 per the CSS spec's over-constrained-box
           rules, so this box - and everything centered inside it via
           align-items/justify-content below - shrinks to the actually-
           visible area above the keyboard instead of staying centered
           against the full pre-keyboard height and ending up half hidden
           behind it. Falls back to inset:0's normal full-height behavior
           before JS has set the variable, or if visualViewport isn't
           supported at all. */
        #type-command-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            height: var(--vv-height, 100%);
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        /* Toggled by the visualViewport 'resize' listener in app.js once
           the on-screen keyboard is confirmed open. align-items:center
           above distributes any overflow evenly above AND below the
           card - fine normally, but once the keyboard eats enough of the
           visible height that the (now taller, especially with search
           results showing) card no longer fits, centering pushes the
           card's own top - where the input field lives - above the
           visible area exactly as much as it pushes the bottom below it.
           Top-aligning instead means overflow only ever pushes down into
           .app-menu-body's own scroll, so the input stays reachable. */
        #type-command-dialog.keyboard-open {
            align-items: flex-start;
        }
        #type-command-dialog.keyboard-open .app-menu-card {
            max-height: 100%;
        }

        /* Bible Navigation modal - opened from the toolbar, same
           positioning rules as the dialogs above. Was previously an
           accordion group inside Settings. */
        #bible-nav-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        #bible-nav-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        /* History stacks ON TOP of Bible Navigation (61), and Clear History
           in turn stacks on this - hence the step up rather than reusing
           Bible Navigation's own layer. */
        #history-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 62;
        }
        #history-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 63;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        /* History sits in Bible Navigation's header, immediately left of the
           close button - pink border to set it apart from the neutral ×
           beside it. Same 1.8em box as .app-menu-close-btn so the pair
           reads as one control group rather than two odd sizes. */
        .history-icon-btn {
            flex-shrink: 0;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 1.8em;
            height: 1.8em;
            padding: 0;
            background: transparent;
            border: 2px solid var(--accent-primary);
            border-radius: 50%;
            color: var(--text-main);
            font-size: calc(1.375rem * var(--ui-scale));
            cursor: pointer;
        }
        .history-icon-btn:hover, .history-icon-btn:focus-visible {
            background-color: var(--bg-hover);
        }
        .history-icon-btn svg {
            width: 1em;
            height: 1em;
        }
        /* The 3-dot menu and the × sit together at the right of the History
           dialog's header. */
        .history-header-actions {
            display: flex;
            align-items: center;
            gap: 6px;
            flex-shrink: 0;
        }
        /* Tighter body padding than other modals get from .app-menu-body's
           shared 20px - more width for the chapter grid (5 fixed columns)
           to work with, since this is the one modal where a few extra
           pixels of left/right padding measurably shrinks every chapter
           number. */
        /* Top padding moved onto #bible-nav-translation-field itself
           (below) instead of living here - a sticky child can't reliably
           reach into its scrolling parent's own top padding via a negative
           top margin the way it can for a non-scrolling parent (that
           padding-box area is right at the scrollport's own top edge, so a
           negative margin pulling into it is liable to get clipped exactly
           where scrollTop can't go negative to reveal it - which showed up
           as a visible gap above the pinned dropdown instead of the fix
           covering it). Left/right padding stays here since only the
           vertical direction actually scrolls. */
        #bible-nav-dialog .app-menu-body {
            padding: 0 12px 12px;
        }
        /* Restores the same top gap #bible-nav-dialog .app-menu-body used
           to provide, now permanently on the field itself (both Bible
           Navigation Style options) rather than on the parent, since this
           element is always the scrolling body's first child either way. */
        #bible-nav-translation-field {
            padding-top: 12px;
        }
        /* Pins Translation to the top of the scrolling dialog body while
           List & Chapters Grid style is active (a long book list, then a
           chapter grid, is enough content to actually scroll it out of
           view - Drop Downs never gets long enough to bother) - class
           toggled by applyBibleNavStyle() in app.js. Horizontal negative
           margin still extends this element out to .app-menu-body's own
           left/right padding-box edges (safe here - unlike top, the
           horizontal direction never scrolls, so there's no clipping risk),
           with its own matching horizontal padding restoring the same
           visual position at rest. Its own top padding (see above) already
           reaches the scrollport's true top with no gap, so once stuck at
           top:0 its background fully covers the list scrolling beneath,
           with a clean border separating the two. */
        #bible-nav-translation-field.bible-nav-translation-sticky {
            position: sticky;
            top: 0;
            z-index: 1;
            margin: 0 -12px;
            padding: 12px 12px 8px;
            background-color: var(--bg-body);
        }

        /* Verse Actions modal - same positioning rules as the dialogs
           above, but lighter backdrop than the usual 0.7 (same reasoning
           as the mobile bottom sheet's own 0.35 below - the passage stays
           partly visible behind it, which matters more here now that the
           dialog is left-anchored right next to the passage instead of
           covering the center of the screen). Purely visual - the
           background is still `inert` while this dialog is open (see
           openVerseActions()), so it doesn't affect screen reader/keyboard
           accessibility regardless of how dark it looks. */
        #verse-actions-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.35);
            z-index: 60;
        }
        #verse-actions-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: flex-start;
            padding: 1rem;
        }
        /* Desktop/tablet only (the mobile media query below overrides this
           back to width:100% for its own bottom-sheet layout) - anchored
           to the left edge via justify-content:flex-start above, and
           narrowed to roughly fit the gutter between the screen's left
           edge and the passage card's own left edge, rather than
           overlapping it. --verse-actions-gutter is set from
           getBoundingClientRect() in openVerseActions() (app.js) each time
           the dialog opens, since that's the only reliable way to know
           the passage card's actual on-screen position - it depends on
           #app-container's own max-width/centering and padding, which
           this file would otherwise have to duplicate as a fragile
           calc(). clamp() keeps it from getting unusably thin (on
           anything narrower than ~750px, where #app-container hasn't hit
           its own 700px cap yet and there's barely any gutter at all) or
           needlessly wide (past this dialog's normal 460px). */
        #verse-actions-dialog .app-menu-card {
            width: clamp(300px, var(--verse-actions-gutter, 460px), 460px);
            max-width: clamp(300px, var(--verse-actions-gutter, 460px), 460px);
        }
        /* Pinned back to the old 20px left/right (every other modal's
           .app-menu-header/.app-menu-body were narrowed to 14px), per
           explicit request to exclude Verse Actions from that change. */
        #verse-actions-dialog .app-menu-header {
            padding: 2px 20px;
            /* Matches .va-sheet-handle's own touch-action:none (app.js
               wires up the same drag-to-expand gesture on this header too,
               not just the handle) - stops the browser trying to scroll
               the page while dragging here. Safe on this non-scrollable
               sticky area specifically - unlike .app-menu-body below,
               which stays plain/unrestricted since it's real scrollable
               content an AT user needs to swipe through normally. */
            touch-action: none;
        }
        #verse-actions-dialog .app-menu-body {
            padding: 20px;
        }
        /* Drag handle affordance for the mobile bottom sheet below - hidden
           everywhere else (desktop keeps the plain left-anchored dialog,
           no handle needed there). Also a real, directly tappable/
           draggable button (see its click and pointer listeners alongside
           openVerseActions() in app.js) that expands the sheet the same
           way scrolling its content down does - the visible icon stays
           small, but the button itself spans a wider invisible hit area
           so it's actually easy to tap or grab. touch-action:none stops
           the browser trying to scroll the page while dragging on it. */
        .va-sheet-handle {
            display: none;
            touch-action: none;
        }
        /* Wide, shallow chevron (collapsed - an invitation to expand)
           crossfading to a plain line (expanded) - two overlaid paths
           rather than trying to interpolate one path's "d" across two very
           different shapes, which CSS can't animate. */
        .va-sheet-handle-icon {
            width: 40px;
            height: 10px;
            display: block;
            overflow: visible;
            pointer-events: none;
        }
        .va-sheet-handle-icon path {
            fill: none;
            stroke: var(--border-color);
            stroke-width: 2.5;
            stroke-linecap: round;
            stroke-linejoin: round;
            transition: opacity 0.25s ease;
        }
        .va-sheet-handle-line { opacity: 0; }
        #verse-actions-dialog.va-sheet-expanded .va-sheet-handle-chevron { opacity: 0; }
        #verse-actions-dialog.va-sheet-expanded .va-sheet-handle-line { opacity: 1; }
        @media (prefers-reduced-motion: reduce) {
            .va-sheet-handle-icon path { transition: none; }
        }
        /* Mobile: anchor Verse Actions to the bottom of the screen as a
           full-width sheet instead of a left-anchored card (the backdrop's
           own lighter 0.35 above already applies at every width, no
           mobile-specific override needed for that anymore). */
        @media (max-width: 639px) {
            #verse-actions-dialog:not([hidden]) {
                align-items: flex-end;
                padding: 0;
            }
            /* Starts "peeked" - just tall enough for the highlight swatches
               and the More Colors toggle, so the passage stays mostly
               visible until the sheet is actually needed (37vh - dialed in
               via a few explicit nudges from an original 40vh, down to
               32vh, then up in small steps to here). Scrolling the
               sheet's own content down, tapping the handle, or dragging up
               on the handle (all wired up alongside openVerseActions() in
               app.js) add .va-sheet-expanded to grow it to the taller
               state below, which reads as the sheet sliding up - one-way,
               it resets back to peeked only the next time the sheet opens
               (see the JS comment on that listener for why it doesn't also
               collapse back down mid-session). A plain scroll (rather than
               a custom drag-the-whole-sheet gesture) was chosen
               deliberately for the sheet's own content - it works
               identically for touch, mouse, keyboard and AT users, and
               doesn't fight with the swipe gesture VoiceOver/TalkBack use
               to navigate; the handle button (tap OR a short drag up on it
               specifically, not the whole sheet) is a discoverable,
               contained alternative for anyone who doesn't happen to
               scroll the content first. */
            #verse-actions-dialog .app-menu-card {
                width: 100%;
                max-width: 100%;
                max-height: 37vh;
                border-radius: 0.75rem 0.75rem 0 0;
                padding-bottom: env(safe-area-inset-bottom, 0px);
                transition: max-height 0.25s ease;
            }
            #verse-actions-dialog.va-sheet-expanded .app-menu-card {
                max-height: 75vh;
            }
            .va-sheet-handle {
                display: flex;
                align-items: center;
                justify-content: center;
                width: 100%;
                min-height: 24px;
                margin: 10px 0 0;
                padding: 0;
                border: none;
                background: none;
                cursor: pointer;
                flex-shrink: 0;
            }
        }

        /* Login Required modal - same positioning rules as the dialogs
           above. */
        #login-required-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        #login-required-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Highlights & Notes browser modal - same positioning rules as the
           dialogs above. */
        #highlights-notes-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        #highlights-notes-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        /* Fixed (not just max) height - stays the same size switching tabs
           or paginating instead of growing/shrinking with content, up to
           the same 85vh ceiling every other modal caps at. */
        #highlights-notes-dialog .app-menu-card {
            height: 92vh;
        }
        /* Reading (Stats & Plans) - same layer as Highlights & Notes, since
           the two are peers opened from the toolbar, never stacked. */
        #reading-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        #reading-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        .stat-section {
            margin-bottom: 22px;
        }
        .stat-section:last-child {
            margin-bottom: 0;
        }
        .stat-heading {
            margin: 0 0 10px;
            /* Heading tier - the body-size sweep had swept this in too,
               leaving section headings the same size as the text
               under them. */
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 800;
            color: var(--accent-secondary);
        }
        /* The three headline figures - the only place big numbers appear,
           so they stay the thing the eye lands on first. */
        .stat-tiles {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 8px;
        }
        .stat-tile {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 2px;
            padding: 12px 6px;
            border: 2px solid rgb(from var(--border-color) r g b / 0.45);
            border-radius: 10px;
            background-color: rgb(from var(--accent-secondary) r g b / 0.12);
            text-align: center;
        }
        .stat-tile-streak {
            border-color: var(--accent-focus);
            background-color: rgb(from var(--accent-focus) r g b / 0.14);
        }
        .stat-tile-num {
            font-size: calc(1.9rem * var(--ui-scale));
            font-weight: 800;
            line-height: 1.05;
            font-variant-numeric: tabular-nums;
        }
        .stat-tile-streak .stat-tile-num {
            color: var(--accent-focus);
        }
        .stat-tile-label {
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-secondary);
            line-height: 1.25;
        }
        .stat-subheading {
            /* Room either side, and the yellow accent at a lighter weight -
               it labels a group, it shouldn't compete with the green
               section heading above it. */
            margin: 24px 0 14px;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            color: var(--accent-focus);
        }
        .stat-section .stat-subheading:first-of-type { margin-top: 0; }
        /* The month reads as a calendar grid rather than a single row - 31
           circles in a line would be unreadable at any text size. */
        .stat-month {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 6px;
            justify-items: center;
        }
        .stat-month .stat-day { width: 100%; }
        .stat-month .stat-dot { font-size: calc(1.3rem * var(--ui-scale)); }
        /* Days that haven't happened yet - dimmed rather than hidden, so the
           month still reads as a whole. */
        .stat-day-future { opacity: 0.45; }
        .stat-week {
            display: flex;
            gap: 6px;
        }
        .stat-day {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 5px;
        }
        .stat-day-initial {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            color: var(--text-secondary);
        }
        .stat-dot {
            /* Reset here, not on .plan-tick-btn - that rule comes after
               .stat-dot-on and its background:none was cancelling the
               ticked state's fill entirely. */
            background: none;
            width: 100%;
            max-width: 34px;
            aspect-ratio: 1;
            border-radius: 50%;
            border: 2px solid rgb(from var(--border-color) r g b / 0.78);
            display: grid;
            place-items: center;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            color: var(--text-secondary);
        }
        .stat-dot-on {
            background-color: var(--accent-secondary);
            border-color: var(--accent-secondary);
            /* Fixed dark ink, not a token: this sits on the accent colour
               itself, which stays light in both themes. */
            color: #07210f;
        }
        /* An outline sitting just outside the circle rather than recolouring
           its border - so today reads as a ring around the day, and a day
           that's both today AND read keeps its green fill and border
           underneath instead of losing one to the other. */
        .stat-dot-today {
            outline: 2px solid var(--accent-focus);
            outline-offset: 2px;
        }
        .stat-tiles-two {
            grid-template-columns: repeat(2, 1fr);
        }

        /* ---- Reading plans ---- */
        /* Plan builder and the cancel confirmation both stack on Reading
           (60/61), so they sit a layer above it. */
        #plan-builder-backdrop, #cancel-plan-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 62;
        }
        #plan-builder-dialog:not([hidden]), #cancel-plan-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 63;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        /* The card's width is a percentage of THIS wrapper's content box,
           and the wrapper carries 16px of side padding - so a plain 95%
           only reached 87% of the screen, while the vh-based height reached a
           true 95%. Dropping the side padding here (only for these four)
           makes the two agree: 95% of the screen, with the remaining 5%
           acting as the gutter the padding used to provide. Vertical
           padding is left alone - the height is in vh and never depended
           on it. */
        /* :not([hidden]) is carried deliberately, not decoratively: each of
           these dialogs sets its padding in a "#id:not([hidden])" rule,
           which outranks a bare id selector, so a plain "#id" here lost
           silently. */
        #account-settings-dialog:not([hidden]),
        #bible-nav-dialog:not([hidden]),
        #highlights-notes-dialog:not([hidden]),
        #reading-dialog:not([hidden]) {
            padding-left: 0;
            padding-right: 0;
        }
        #account-settings-dialog .app-menu-card,
        #bible-nav-dialog .app-menu-card {
            width: 99%;
            max-height: 90vh;
        }
        #highlights-notes-dialog .app-menu-card,
        #reading-dialog .app-menu-card {
            width: 99%;
            /* Same 33rem ceiling as .app-menu-card-wide; only the viewport
               half moves, 92vw -> 95vw, so these two reach the same 95% on
               a phone as the two above. */
            max-width: min(33rem, 99vw);
            max-height: 90vh;
        }
        /* Resources: fixed, not capped - see the desktop rule for why. */
        #reading-dialog .app-menu-card {
            height: 90vh;
        }
        .app-menu-card-narrow {
            max-width: min(24rem, 92vw);
        }
        /* No outline - the raised surface carries the card, same as the
           mockup settled on. */
        .plan-card {
            background-color: var(--bg-expanded);
            border-radius: 10px;
            padding: 24px;
            margin-bottom: 10px;
            display: flex;
            flex-direction: column;
            gap: 9px;
        }
        .plan-card-heading {
            margin: 0;
            font-size: inherit;
            font-weight: inherit;
        }
        .plan-card-head {
            display: flex;
            /* column-reverse puts the tag ABOVE the title without reordering
               the DOM, so a screen reader still reads the plan name first.
               Sharing one line meant the title wrapped repeatedly while the
               tag sat comfortably beside it. */
            flex-direction: column-reverse;
            align-items: flex-start;
            gap: 10px;
            width: 100%;
            padding: 0;
            background: none;
            border: none;
            color: var(--text-main);
            font: inherit;
            text-align: left;
            cursor: pointer;
        }
        .plan-card-head-static { cursor: default; }
        .plan-card-name {
            font-weight: 800;
            /* h1 scale (2rem is what a browser gives an <h1> by default),
               up from 1.65rem - the largest type in the Reading modal. */
            font-size: calc(2rem * var(--ui-scale));
        }
        .plan-pill {
            flex: none;
            box-sizing: border-box;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            letter-spacing: 0.05em;
            text-transform: uppercase;
            color: var(--accent-focus);
            border: 2px solid var(--accent-focus);
            border-radius: 99px;
            padding: 2px 9px;
        }
        .plan-pill-cal { color: var(--accent-primary); border-color: var(--accent-primary); }
        /* Today's passage is the tap target on each card. */
        .plan-today {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 10px;
            width: 100%;
            padding: 10px 12px;
            background-color: var(--bg-card);
            border: 2px solid var(--accent-secondary);
            border-radius: 8px;
            color: var(--text-main);
            font: inherit;
            text-align: left;
            cursor: pointer;
        }
        .plan-today-inner { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
        .plan-today-label {
            font-size: calc(1.3rem * var(--ui-scale));
            text-transform: uppercase;
            letter-spacing: 0.1em;
            font-weight: 700;
            color: var(--text-secondary);
        }
        /* The same pink underline the schedule's references carry, so a
           tappable reference looks the same wherever it appears. */
        .plan-today-ref {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            text-decoration: underline;
            text-underline-offset: 3px;
            text-decoration-color: rgb(from var(--accent-primary) r g b / 0.8);
        }
        .plan-today-go { flex-shrink: 0; color: var(--accent-secondary); font-size: 1.3em; line-height: 1; }
        /* Shown instead of today's passage once the day is done - the point
           is that you've finished, with reading on offered separately. */
        .plan-done-box {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 10px 12px;
            background-color: var(--bg-card);
            border: none;
            border-radius: 8px;
        }
        .plan-done-tick {
            flex-shrink: 0;
            width: 26px;
            height: 26px;
            border-radius: 50%;
            background-color: var(--accent-secondary);
            color: #07210f;
            display: grid;
            place-items: center;
            font-weight: 800;
        }
        .plan-done-text { font-weight: 700; font-size: calc(1.3rem * var(--ui-scale)); }
        .plan-pill-ahead { color: var(--accent-secondary); border-color: var(--accent-secondary); }

        .plan-rows { display: flex; flex-direction: column; }
        .plan-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 10px;
            padding: 11px 0;
            border-bottom: 2px solid var(--divider-color);
        }
        .plan-row:last-child { border-bottom: 0; }
        /* The row itself opens the schedule; Start is a separate control
           inside it, so its click must not also reach the row. */
        .plan-row-main {
            display: flex;
            flex-direction: column;
            gap: 1px;
            flex: 1 1 auto;
            min-width: 0;
            padding: 0;
            background: none;
            border: none;
            color: var(--text-main);
            font: inherit;
            text-align: left;
            cursor: pointer;
        }
        .plan-row-name { font-weight: 700; font-size: calc(1.3rem * var(--ui-scale)); }
        .plan-row-meta { font-size: calc(1.3rem * var(--ui-scale)); color: var(--text-secondary); }
        .plan-start-btn { flex-shrink: 0; }
        /* A finished plan's row isn't a button - only its Read again
           is - so it mustn't look pressable. */
        .plan-row-static { cursor: default; }
        .plan-wide-btn { width: 100%; margin-top: 6px; }
        .plan-back-btn { margin-bottom: 14px; }
        .plan-cancel-btn { color: var(--accent-primary); }
        .plan-btn-row { display: flex; gap: 8px; margin-top: 10px; }
        .plan-btn-row .app-btn { flex: 1; }

        .plan-sched { display: flex; flex-direction: column; }
        .plan-sched-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 10px;
            padding: 8px 0;
            border-bottom: 2px solid var(--divider-color);
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .plan-sched-row:last-child { border-bottom: 0; }
        .plan-sched-left {
            display: flex;
            align-items: center;
            gap: 9px;
            min-width: 0;
            flex: 1 1 auto;
        }
        /* The week strip sizes its circles from the column they sit in
           (width:100% of a flex COLUMN). Here they're items in a flex ROW,
           where that resolves against the row and shrinks per row - hence
           a fixed size and no shrinking. */
        /* The circle is a real control now - tap to tick or untick - so it
           needs the button reset the span never did. */
        .plan-tick-btn {
            padding: 0;
            font: inherit;
            cursor: pointer;
        }
        .plan-sched-left .stat-dot {
            /* Explicit width AND height - aspect-ratio alone lets a
               tick's own line-height stretch the box into an oval. */
            width: 30px;
            height: 30px;
            max-width: none;
            aspect-ratio: auto;
            flex: none;
            line-height: 1;
        }
        .plan-sched-ref {
            /* A <button> centres its text by default, so a wrapped
               reference put its second line in the middle. */
            text-align: left;
            min-width: 0;
            padding: 0;
            background: none;
            border: none;
            color: var(--text-main);
            font: inherit;
            font-weight: 700;
            cursor: pointer;
            text-decoration: underline;
            text-underline-offset: 3px;
            text-decoration-color: rgb(from var(--accent-primary) r g b / 0.8);
        }
        .plan-sched-when {
            flex: none;
            margin-left: auto;
            text-align: right;
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-secondary);
            white-space: nowrap;
        }
        /* Today is the row you act on, so it carries the weight. */
        .plan-sched-today .plan-sched-ref,
        .plan-sched-today .plan-sched-when { font-weight: 800; color: var(--text-main); }
        /* Missed and caught-up say so in words as well as colour - state is
           never carried by colour alone. */
        .plan-when-missed { color: var(--accent-focus); font-weight: 700; }
        .plan-when-late { font-style: italic; }
        .plan-tick-missed { border-color: var(--accent-focus); color: var(--accent-focus); }
        .plan-tick-now { border-color: var(--accent-focus); border-width: 3px; }
        .plan-behind {
            border-left: 3px solid var(--accent-focus);
            padding-left: 12px;
            margin-top: 4px;
        }
        .plan-behind-title { font-weight: 800; font-size: calc(1.3rem * var(--ui-scale)); color: var(--accent-focus); }

        /* Builder */
        #plan-builder-dialog .settings-group-fields { margin-top: 0; padding-top: 0; }
        #plan-builder-dialog .app-menu-body > :first-child { margin-top: 0; }
        .plan-field { display: flex; flex-direction: column; gap: 6px; margin-top: 14px; }
        /* Chapter count sits directly under the book dropdown, so the
           dropdown itself only has to carry the name. */
        .plan-book-count { margin: 4px 0 0; font-size: calc(1.3rem * var(--ui-scale)); color: var(--text-secondary); }
        .plan-field-label { font-weight: 700; }
        .plan-chips { display: flex; flex-wrap: wrap; gap: 7px; }
        .plan-chip {
            font: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            padding: 7px 12px;
            border-radius: 99px;
            background: transparent;
            color: var(--text-main);
            border: 2px solid var(--divider-color);
            cursor: pointer;
        }
        .plan-chip[aria-checked="true"] {
            border-color: var(--accent-secondary);
            background-color: rgb(from var(--accent-secondary) r g b / 0.35);
        }
        .plan-switch-row {
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            gap: 12px;
            margin-top: 16px;
        }
        .plan-switch-text { display: flex; flex-direction: column; gap: 3px; }
        .plan-switch-note { font-size: calc(1.3rem * var(--ui-scale)); color: var(--text-secondary); }
        .plan-summary {
            margin: 16px 0 0;
            padding: 10px 12px;
            border-radius: 8px;
            background-color: rgb(from var(--accent-secondary) r g b / 0.35);
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .plan-finished-list { margin-top: 10px; }
        .stat-caption {
            margin: 10px 0 0;
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-secondary);
        }
        .stat-bars {
            display: flex;
            flex-direction: column;
            gap: 18px;
        }
        .stat-bar-top {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 10px;
            font-size: calc(1.3rem * var(--ui-scale));
            margin-bottom: 4px;
        }
        .stat-bar-top span {
            color: var(--text-secondary);
            font-variant-numeric: tabular-nums;
        }
        .stat-track {
            height: 15px;
            border-radius: 99px;
            background-color: rgb(from var(--border-color) r g b / 0.3);
            overflow: hidden;
        }
        .stat-fill {
            height: 100%;
            border-radius: 99px;
        }
        .stat-fill-ot { background-color: var(--accent-secondary); }
        .stat-fill-nt { background-color: var(--accent-primary); }
        .stat-fill-all { background-color: var(--accent-focus); }
        .stat-rows {
            margin: 0;
            display: flex;
            flex-direction: column;
        }
        .stat-row {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 12px;
            padding: 9px 0;
            /* Same rule as the book list dividers, so the two lists read
               as the same kind of list. */
            border-bottom: 2px solid var(--divider-color);
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .stat-row:last-child { border-bottom: 0; }
        /* Roughly 3:2 - the label gets the wider column, and either side
           wraps to a second line rather than being squeezed or clipped.
           min-width:0 is what actually lets a flex item shrink below its
           own content width and wrap at all. */
        .stat-row dt {
            margin: 0;
            color: var(--text-secondary);
            flex: 3 1 0;
            min-width: 0;
            overflow-wrap: break-word;
        }
        .stat-row dd {
            margin: 0;
            font-weight: 700;
            font-variant-numeric: tabular-nums;
            flex: 2 1 0;
            min-width: 0;
            text-align: right;
            overflow-wrap: break-word;
        }
        /* Shared by the Plans tab and the "nothing recorded yet" Stats
           state - both are an empty room with an explanation in it. */
        .reading-placeholder {
            display: flex;
            flex-direction: column;
            align-items: center;
            text-align: center;
            gap: 8px;
            padding: 30px 16px;
            color: var(--text-secondary);
        }
        .reading-placeholder-title {
            margin: 0;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            color: var(--text-main);
        }
        .reading-placeholder-note {
            margin: 0;
            font-size: calc(1.3rem * var(--ui-scale));
            max-width: 34em;
        }

        /* --- Resources > Study ------------------------------------------
           The passage this tab is sitting on, and the collapsed picker for
           changing it. Same translucent-border/derived-accent-tint
           treatment the Stats tiles use, so the two tabs read as one
           dialog rather than two features that landed separately. */
        .study-passage-bar {
            padding: 12px 14px;
            margin-bottom: 20px;
            border: 2px solid rgb(from var(--border-color) r g b / 0.45);
            border-radius: 10px;
            background-color: rgb(from var(--accent-secondary) r g b / 0.12);
        }
        .study-passage-line {
            /* The label and the reference sit on their own lines rather
               than side by side - at large text sizes a long book name
               ("Song of Solomon 8") plus a label wraps into a mess. */
            display: flex;
            flex-direction: column;
            gap: 2px;
            margin: 0 0 10px;
        }
        .study-passage-label {
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .study-passage-ref {
            font-size: calc(1.7rem * var(--ui-scale));
            font-weight: 800;
            line-height: 1.15;
        }
        .study-change-btn {
            width: 100%;
        }
        .study-picker {
            margin-top: 14px;
            padding-top: 14px;
            border-top: 2px solid rgb(from var(--border-color) r g b / 0.45);
        }
        /* Book takes the room it needs, chapter takes what a two- or
           three-digit number needs - and both drop to full width rather
           than shrinking once the dialog is narrow. */
        .study-picker-fields {
            display: flex;
            flex-wrap: wrap;
            gap: 12px;
            margin-bottom: 14px;
        }
        .study-picker-fields > div {
            flex: 1 1 12em;
            min-width: 0;
        }
        .study-picker-fields > div:last-child {
            flex: 0 1 7em;
        }
        .study-picker-fields select {
            width: 100%;
        }
        /* Sits under an external link, so it explains where the link goes
           before it's followed rather than after. */
        .study-note {
            max-width: 40em;
        }
        .study-loading {
            color: var(--text-secondary);
            padding: 20px 0;
        }
        .study-source-list,
        .study-audio-list {
            margin-bottom: 4px;
        }
        /* .stat-heading and .xref-link-btn both resolve to
           --accent-secondary, so inside Study a source name and the
           section heading above it came out the same green and the list
           read as a stack of headings. The links move to the primary
           accent instead - the headings stay put, matching Plans and
           Stats, which are the reference for how a section title looks. */
        .study-source-list .xref-link-btn,
        .study-audio-list .xref-link-btn {
            color: var(--accent-primary);
        }
        .study-audio-label {
            min-width: 0;
            overflow-wrap: break-word;
        }
        /* A broadcast with no file behind it yet - present, named, and
           plainly not a control. The middle text tier, so it reads as
           listed-but-inactive rather than as body text or as a link. */
        .study-audio-pending {
            display: block;
            padding: 6px 0;
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-secondary);
        }
        /* The TTB attribution link - inherits .stat-caption's secondary
           tier, so it reads as a credit rather than competing with the
           broadcast links above it. Underlined so it is still obviously a
           link at that weight. */
        .study-credit-link {
            color: inherit;
            text-decoration: underline;
        }
        .study-credit-link:hover,
        .study-credit-link:focus-visible {
            color: var(--accent-primary);
        }
        .study-external {
            margin-top: 12px;
        }
        .study-external-link {
            display: inline-block;
            text-decoration: none;
        }

        /* Tabs - plus, in Highlights & Notes, the search field with them -
           pinned to the top of a scrolling modal body, the same way Bible
           Navigation pins its Translation dropdown. The negative margins
           pull this out to .app-menu-body's own padding edges so its
           background fully covers the list scrolling underneath, and the
           matching padding puts the content back where it was. Shared by
           Highlights & Notes and Reading so the two behave identically. */
        /* The body's own top padding is removed for these two dialogs (see
           the rule below) and carried by this element's padding-top
           instead. A negative top margin here does NOT work: the sticky
           element's MARGIN box is what gets pinned, so -20px left the
           border box 20px below the scrollport edge and content scrolled
           visibly through that gap. Bible Navigation's pinned Translation
           field hit the same thing and is solved the same way. */
        #highlights-notes-dialog .app-menu-body,
        #reading-dialog .app-menu-body,
        #notebook-dialog .app-menu-body {
            padding-top: 0;
        }
        /* No dividing line - the background alone separates it from the
           list scrolling underneath, per explicit request. */
        .modal-sticky-head {
            position: sticky;
            top: 0;
            z-index: 1;
            margin: 0 -10px 14px;
            padding: 8px 10px 10px;
            background-color: var(--bg-body);
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        /* Spacing inside comes from the flex gap above, so the children's
           own bottom margins would just double it. */
        .modal-sticky-head .hn-tab-group,
        .modal-sticky-head .hn-search-input,
        .modal-sticky-head .nb-search-row {
            margin-bottom: 0;
        }
        .modal-sticky-head .nb-search-row { margin-top: 0; }
        .hn-tab-group {
            display: flex;
            margin-bottom: 16px;
        }
        /* Search field - same look as Bible Navigation's reference field
           (shared select/input/textarea base + accent-border/accent-ring),
           just full-width with no attached Go button since it filters live
           as you type instead of submitting. */
        .hn-search-input {
            margin-bottom: 16px;
        }
        /* Tabs are buttons, so they take the button size too. */
        .hn-tab {
            flex: 1;
            /* Top/bottom halved (was 10px all around), per explicit
               request - left/right unchanged. */
            padding: 5px 10px;
            background: none;
            border: none;
            border-bottom: 4px solid transparent;
            font-weight: 700;
            font-family: inherit;
            /* <button> elements don't inherit font-size from their parent
               by default (unlike most other elements) - this was silently
               falling back to the browser's own default form-control
               size, not the page's actual (scaled) body text, which is
               why it read as too small and didn't track --ui-scale at
               all. Slightly smaller than .hn-note-text now (was an exact
               match at 1.25rem), per explicit follow-up request - still
               scales with --ui-scale, used by both the Highlights & Notes
               tabs and the Search results tabs (#search-tab-group reuses
               this same class). No white-space/overflow override here,
               so a label that doesn't fit on one line wraps onto a
               second rather than being cut off - normal text flow only
               breaks a line when it actually needs to. */
            font-size: calc(1.3rem * var(--ui-scale));
            cursor: pointer;
            color: var(--text-muted);
        }
        .hn-tab[aria-selected="true"] {
            border-bottom-color: var(--accent-primary);
            background-color: var(--bg-hover);
            color: var(--text-main);
        }
        .hn-empty-message {
            color: var(--text-muted);
            font-style: italic;
        }
        /* Highlights list - each entry styled exactly like the verse's own
           background in the passage view (reuses .user-highlight-*), so it
           reads as a preview of how it actually looks there. */
        .hn-highlight-item {
            display: block;
            width: 100%;
            text-align: left;
            padding: 10px 12px;
            margin-bottom: 10px;
            border-radius: 4px;
            border: none;
            font-family: inherit;
            cursor: pointer;
            color: var(--text-main);
        }
        /* "Recents" heading (Highlights & Notes browser, both tabs) -
           plain, not collapsible, always shows the first 15 entries
           beneath it (see renderHnBrowserSections() in app.js). */
        .hn-section-heading {
            /* Bottom gap widened (was 12px), per explicit request - more
               breathing room between the heading and its own items. */
            margin: 0 0 20px;
            /* Matches the OT/NT testament heading level, per explicit
               follow-up request - was 1.1rem. */
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 800;
            text-transform: uppercase;
            letter-spacing: 0.04em;
            color: var(--accent-secondary);
        }
        /* Old/New Testament collapsible section - same +/- marker
           convention as .settings-group summary, but NOT mutually
           exclusive (no name= attribute) - browsing multiple testaments/
           books open at once is expected here, unlike Settings. */
        /* This same margin-top covers both the gap above the first
           accordion (right after Recents' own items) and the gap between
           consecutive accordions (Old Testament to New Testament) - every
           .hn-testament-group gets it uniformly. Widened per explicit
           request (was 24px). */
        .hn-testament-group {
            margin-top: 32px;
        }
        /* Direct-child combinator (>), not a plain descendant selector -
           .hn-testament-group's <summary> is a direct child, but a
           descendant selector here would ALSO match every nested book's
           own <summary> (2 levels down, inside .hn-testament-group-body),
           which is exactly the bug that caused every book's +/- marker to
           read "-" as soon as its parent testament was open, regardless
           of that specific book's own state. */
        .hn-testament-group > summary {
            cursor: pointer;
            /* Matches .hn-section-heading ("Recents") - was unified to
               1.375rem alongside the book level a moment ago, then
               changed again per explicit follow-up to be its own bigger
               size instead, matching Recents. Books stay at 1.375rem. */
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 800;
            text-transform: uppercase;
            letter-spacing: 0.04em;
            color: var(--accent-secondary);
            list-style: none;
            display: flex;
            align-items: center;
            gap: 0.5em;
        }
        .hn-testament-group > summary::-webkit-details-marker { display: none; }
        .hn-testament-group > summary::before { content: "+"; display: inline-block; position: relative; top: -2px; color: var(--accent-primary); font-weight: 800; transform: scale(1.3); }
        .hn-testament-group[open] > summary::before { content: "\2212"; }
        .hn-testament-group-body {
            padding: 14px 0 0 4px;
        }
        /* Book collapsible section, nested inside a testament - indented
           (was flush with the testament level) so the hierarchy reads
           clearly at a glance instead of just from font-size/weight
           alone, per explicit request - indent reduced from an earlier,
           wider 24px per explicit follow-up. Direct-child combinator (>)
           on the summary rules below, same reasoning as the testament
           level above - a plain descendant selector would also match the
           nested chapter level's own <summary> 2 levels down. */
        .hn-book-group {
            margin-top: 14px;
            margin-left: 14px;
        }
        .hn-book-group:first-child {
            margin-top: 0;
        }
        .hn-book-group > summary {
            cursor: pointer;
            /* Matches every item's own reference/content text (1.375rem) -
               a step below Recents/the testament level (1.45rem), per
               explicit request. Weight/color also stay quieter than the
               testament level (not bold-800/uppercase/accent-secondary)
               so the hierarchy reads clearly from both size and weight
               together. */
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            color: var(--text-main);
            list-style: none;
            display: flex;
            align-items: center;
            gap: 0.5em;
        }
        .hn-book-group > summary::-webkit-details-marker { display: none; }
        .hn-book-group > summary::before { content: "+"; display: inline-block; position: relative; color: var(--accent-primary); font-weight: 800; }
        .hn-book-group[open] > summary::before { content: "\2212"; }
        .hn-book-group-body {
            padding: 12px 0 0 4px;
        }
        /* Chapter collapsible section, nested inside a book - the actual
           highlight/note items live here now, not directly in the book.
           Same size as the book level (1.375rem) - italic is the only
           thing setting it apart, since a 3rd distinct weight/color tier
           started to feel like overkill. */
        .hn-chapter-group {
            margin-top: 10px;
            margin-left: 14px;
        }
        .hn-chapter-group:first-child {
            margin-top: 0;
        }
        .hn-chapter-group > summary {
            cursor: pointer;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            font-style: italic;
            color: var(--text-main);
            list-style: none;
            display: flex;
            align-items: center;
            gap: 0.5em;
        }
        .hn-chapter-group > summary::-webkit-details-marker { display: none; }
        .hn-chapter-group > summary::before { content: "+"; display: inline-block; position: relative; color: var(--accent-primary); font-weight: 800; }
        .hn-chapter-group[open] > summary::before { content: "\2212"; }
        .hn-chapter-group-body {
            padding: 10px 0 0 4px;
        }
        .hn-item-ref {
            display: block;
            font-weight: 800;
            /* Matches .hn-note-ref/.search-result-ref - all three
               (Highlights list, Notes list, and search results'
               reference) unified to the same 1.375rem, per explicit
               request - was 1.45rem. */
            font-size: calc(1.3rem * var(--ui-scale));
            margin-bottom: 4px;
        }
        .hn-item-text {
            display: block;
            font-size: calc(1.3rem * var(--ui-scale));
            overflow-wrap: break-word;
        }
        /* Notes list - two separate tappable buttons per entry (reference
           navigates to the verse, the note card itself opens it for
           editing) rather than one button, since buttons can't nest inside
           each other. */
        /* Plain gap between cards now (was a border-bottom divider), per
           explicit request. */
        /* The row is the card now (border, fill and padding moved here
           from .hn-note-text-btn below), so the verse reference sits
           inside it with the note, per explicit request. */
        .hn-note-item {
            margin-bottom: 18px;
            padding: 14px 12px;
            background-color: var(--bg-body);
            border: 2px solid var(--divider-color);
            border-radius: 8px;
        }
        .hn-note-item:last-child {
            margin-bottom: 0;
        }
        .hn-note-ref {
            display: inline-block;
            background: none;
            border: none;
            padding: 0;
            /* Room under the reference before the note (was 6px). */
            margin-bottom: 12px;
            font-weight: 800;
            /* Matches .hn-item-ref/.search-result-ref - all three unified
               to the same 1.375rem, per explicit request - was 1.15rem. */
            font-size: calc(1.3rem * var(--ui-scale));
            font-family: inherit;
            color: var(--accent-secondary);
            text-decoration: underline;
            cursor: pointer;
        }
        /* The note itself: a plain tap target inside the card (the card
           chrome is on .hn-note-item above). */
        .hn-note-text-btn {
            display: block;
            width: 100%;
            text-align: left;
            padding: 0;
            background: none;
            border: none;
            border-radius: 4px;
            font-family: inherit;
            cursor: pointer;
            color: var(--text-main);
        }
        .hn-note-text-btn:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 4px;
        }
        .hn-note-item:hover {
            border-color: var(--accent-primary);
        }
        /* Notes tab only (the class is shared with the Highlights tab's
           date pills): more room between the note and its date. */
        .hn-note-item .hn-note-date { margin-top: 14px; }
        /* Clamped to 12 lines by default, same as .verse-note-display-text
           in Verse Actions - renderNotesList() in app.js only reveals a
           "Show More"/"Show Less" toggle (.verse-note-more-toggle, shared
           with that same Verse Actions treatment) when a note's text
           actually overflows this. */
        .hn-note-text {
            margin: 0;
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-main);
            white-space: pre-line;
            overflow-wrap: break-word;
            display: -webkit-box;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: 12;
            overflow: hidden;
        }
        .hn-note-text.expanded {
            display: block;
            -webkit-line-clamp: unset;
            overflow: visible;
        }
        .hn-note-date {
            display: block;
            margin-top: 6px;
            /* Matches .hn-note-text's own explicit size exactly (was 1em -
               relative to the surrounding <button>'s font-size, which is
               unset/browser-default since buttons don't inherit font-size
               by default, so this never actually matched the note text's
               size at all). */
            font-size: calc(1.3rem * var(--ui-scale));
            /* --text-main + opacity (was var(--text-muted), a separate
               dedicated color token), per explicit request - same muted-
               text approach as .settings-field-note/.settings-beta-warning
               elsewhere in this file. */
            color: var(--text-main);
            opacity: 0.85;
        }
        /* Highlight dates only (.hn-note-date is shared with the Notes
           tab's date stamps too, via .hn-note-item instead of this class -
           left untouched) - background matches the user's current Passage
           Background setting (see --passage-bg in applyPassageBg(),
           app.js), per explicit request. Falls back to --bg-card if that
           variable isn't set yet (e.g. before a passage has ever loaded). */
        .hn-highlight-item .hn-note-date {
            display: inline-block;
            background-color: var(--passage-bg, var(--bg-card));
            padding: 2px 8px;
            border-radius: 999px;
        }

        /* Favorite Highlight Colors modal - same positioning rules as the
           dialogs above. */
        /* z-index 62/63 (not the usual 60/61) - this one stacks ON TOP of
           Verse Actions rather than replacing it, since it's opened from a
           control that lives inside that dialog (see the JS comment on
           openFavoriteColors()). */
        #favorite-colors-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 62;
        }
        #favorite-colors-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 63;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Note Editor modal - same stacked-on-top-of-Verse-Actions z-index
           treatment as Favorite Colors above (opened from a control inside
           that dialog too - see openNoteEditor()). Never open at the same
           time as Favorite Colors in practice, so sharing the same 62/63
           tier is fine. */
        #note-editor-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 62;
        }
        #note-editor-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 63;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Clear History modal - same stacked-on-top treatment as Favorite
           Colors/Note Editor (opened from a control inside Bible
           Navigation). */
        /* Above the History dialog (62/63), which is what opens this - at
           the old 62/63 this backdrop sat level with History instead of
           over it, so opening Clear History dimmed nothing. */
        #clear-history-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 64;
        }
        #clear-history-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 65;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Delete Note confirmation - stacks on top of the Note Editor
           (z-62/63), so it needs one tier deeper: 64/65. */
        #delete-note-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 64;
        }
        #delete-note-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 65;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Cross-reference verse preview - can open from inside Verse
           Actions (z-60/61) or directly from the chapter-level section in
           the passage view (nothing else stacked underneath at all) - 66/67
           clears every other dialog in this file either way. */
        #xref-preview-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 66;
        }
        #xref-preview-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 67;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Commentary content modal - stacks on top of Verse Actions the
           same way the cross-reference preview above does, so it shares
           that same z-index tier (66/67, clearing Verse Actions' 60/61). */
        #commentary-content-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 66;
        }
        #commentary-content-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 67;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Reference popup - stacks on top of Commentary content (68/69,
           clearing its 66/67), same tier pattern as everything else. */
        #commentary-ref-popup-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 68;
        }
        #commentary-ref-popup-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 69;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        .commentary-ref-popup-label {
            margin: 0 0 4px;
            font-size: calc(1.3rem * var(--ui-scale));
            letter-spacing: 0.04em;
            text-transform: uppercase;
            font-weight: 700;
            color: var(--text-main);
            opacity: 0.85;
        }
        .commentary-ref-popup-text {
            margin: 0 0 18px;
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            color: var(--text-main);
        }
        .commentary-ref-popup-goto {
            width: 100%;
        }
        /* Inline, not block - these sit mid-sentence inside ordinary
           commentary prose (see linkifyCommentaryText() in app.js), so a
           real <button> here needs to look and flow like a text link, not
           a UI control. */
        .commentary-ref-link {
            display: inline;
            background: none;
            border: none;
            padding: 0;
            margin: 0;
            font: inherit;
            color: var(--accent-secondary);
            text-decoration: underline;
            text-decoration-style: dotted;
            text-underline-offset: 2px;
            cursor: pointer;
        }

        .verse-actions-section {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        /* The plain class rule above and the browser's own default
           [hidden] { display: none; } UA rule are equal specificity (0,1,0
           either way) - at a tie, an AUTHOR stylesheet rule always beats
           the UA default regardless of which one comes later in the
           cascade, so .verse-actions-section's own display:flex silently
           overrode `hidden` entirely (confirmed: the hidden PROPERTY read
           true while the element still rendered, getComputedStyle showing
           display:flex). Explicit [hidden] override restores the normal
           behavior - needed for isCommentaryFeatureEnabled()'s
           beta-vs-production gate (app.js) to actually hide the
           Commentaries/Current Chapter/Current Book sections it sets
           .hidden on, not just mark them hidden in the DOM while still
           showing their heading with nothing under it. */
        .verse-actions-section[hidden] {
            display: none;
        }
        .verse-actions-section + .verse-actions-section {
            /* Gap above each section's heading - doubled again (was 2px,
               then 14px), per explicit request. */
            margin-top: 28px;
            padding-top: 0;
        }
        .verse-actions-label {
            display: block;
            font-weight: 700;
        }
        .highlight-swatch-row {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 10px;
        }
        /* The [hidden] attribute alone isn't enough here - .highlight-swatch-row's
           own display:flex above (an author-origin rule) beats the
           browser's default [hidden]{display:none} (user-agent origin)
           regardless of selector specificity, since author styles always
           win over UA styles for a matched property. This explicit
           ID+[hidden] override is what actually keeps the secondary row
           collapsed. */
        #highlight-swatch-row-secondary[hidden] {
            display: none;
        }
        /* Extra clearance below the color picker specifically while it's
           expanded (More Colors open) - .verse-actions-section +
           .verse-actions-section's own 2px gap to the Share/Note section
           below reads as too cramped once this row's grown to 20 colors'
           worth of wrapped swatches. */
        #highlight-swatch-row-secondary:not([hidden]) {
            margin-bottom: 16px;
        }
        .highlight-swatch {
            position: relative;
            width: 44px;
            height: 44px;
            border-radius: 50%;
            border: none;
            cursor: pointer;
            flex-shrink: 0;
        }
        /* Neon yellow - deliberately not var(--accent-focus) (the pale
           pastel used for focus rings elsewhere), per explicit request for
           a more vivid/saturated yellow highlight option. Same static-hex
           treatment (not theme-swapped) as blue/orange/purple/teal below. */
        .highlight-swatch-yellow { background-color: #f5ff00; }
        /* Both of these used to track their theme-flipping accent variable
           instead of a static hex like every other swatch here - fine by
           coincidence while the accent happened to match the actual
           highlight color, but --accent-primary is a very dark maroon in
           light mode (#800d48, darker than the Dark Pink swatch itself),
           and stayed tied to whatever the accent was even after Light
           Green's own highlight color changed independently of it. Static
           hex keeps the swatch matching its real hue family regardless of
           theme or unrelated accent-color changes. */
        .highlight-swatch-green { background-color: #86efac; }
        .highlight-swatch-pink { background-color: #db2777; }
        .highlight-swatch-blue { background-color: #3b82f6; }
        .highlight-swatch-orange { background-color: #f97316; }
        .highlight-swatch-purple { background-color: #a855f7; }
        /* Red - a solid, unambiguous red (not var(--error-color), which in
           dark mode is a pale coral-ish red easy to confuse with orange at
           a glance) so it reads clearly distinct from Orange above. */
        .highlight-swatch-red { background-color: #dc2626; }
        .highlight-swatch-teal { background-color: #14b8a6; }
        .highlight-swatch-cyan { background-color: #22d3ee; }
        .highlight-swatch-gray { background-color: #9ca3af; }
        .highlight-swatch-brown { background-color: #92400e; }
        /* Magenta - deliberately sits between Pink and Purple in hue. */
        .highlight-swatch-magenta { background-color: #d946ef; }
        .highlight-swatch-lavender { background-color: #b6a0fc; }
        .highlight-swatch-dark-purple { background-color: #581c87; }
        .highlight-swatch-light-pink { background-color: #f9a8d4; }
        .highlight-swatch-dark-pink { background-color: #9d174d; }
        .highlight-swatch-dark-red { background-color: #7f1d1d; }
        .highlight-swatch-dark-teal { background-color: #115e59; }
        .highlight-swatch-dark-blue { background-color: #1e3a8a; }
        .highlight-swatch-dark-green { background-color: #14532d; }
        /* These four were added along with the colors themselves but never
           got a swatch rule here - with no matching class, they silently
           fell through to this button's own unstyled default background
           (reading as gray in dark mode, near-white in light mode) instead
           of their actual color, even though the highlight itself (using
           --highlight-light-blue/etc. above) was always correct. */
        .highlight-swatch-light-blue { background-color: #93c5fd; }
        .highlight-swatch-bright-green { background-color: #22c55e; }
        .highlight-swatch-light-yellow { background-color: #fef9c3; }
        .highlight-swatch-dark-yellow { background-color: #b8860b; } /* true gold tone, matching the actual highlight color */
        /* "Remove highlight" swatch - only rendered (first, before the
           color choices) when the verse currently has a highlight, see
           renderHighlightSwatches(). Empty/outlined rather than a solid
           fill so it doesn't read as just another color option, with a big
           X in the same color as body text so it reads clearly against
           either theme - white on dark, dark on light (handled by the
           :root[data-theme="light"] override below, same mechanism as the
           .icon-for-dark/.icon-for-light swap elsewhere in this file). */
        .highlight-remove-swatch {
            background-color: transparent;
            border: 2px solid var(--border-color);
        }
        .highlight-remove-swatch::after {
            content: "";
            position: absolute;
            inset: 0;
            background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><line x1='6' y1='6' x2='18' y2='18'/><line x1='18' y1='6' x2='6' y2='18'/></svg>");
            background-repeat: no-repeat;
            background-position: center;
            background-size: 55%;
        }
        :root[data-theme="light"] .highlight-remove-swatch::after {
            background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111827' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><line x1='6' y1='6' x2='18' y2='18'/><line x1='18' y1='6' x2='6' y2='18'/></svg>");
        }
        /* Thicker outline (was 3px) + a checkmark drawn over the swatch
           itself, so the selected color is unambiguous even for users who
           have trouble distinguishing the outline color from the swatch's
           own color. */
        .highlight-swatch[aria-pressed="true"],
        .highlight-swatch[aria-checked="true"] {
            outline: 5px solid var(--text-main);
            outline-offset: 3px;
        }
        .highlight-swatch[aria-pressed="true"]::after,
        .highlight-swatch[aria-checked="true"]::after {
            content: "";
            position: absolute;
            inset: 0;
            background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='5 13 10 18 19 7'/></svg>");
            background-repeat: no-repeat;
            background-position: center;
            background-size: 60%;
            /* White halo behind the black check strokes so it stays
               visible against the darker swatch colors (red/brown/teal/gray) -
               three stacked layers (was two at 1.5px) for a more intense glow. */
            filter: drop-shadow(0 0 2.5px white) drop-shadow(0 0 2.5px white) drop-shadow(0 0 2.5px white);
        }
        .highlight-swatch:hover, .highlight-swatch:focus-visible {
            filter: brightness(1.1);
        }
        /* Favorite Colors grid only - swatches beyond the 4-color limit are
           real disabled buttons (see renderFavoriteColorsGrid()), dimmed so
           it reads as "can't pick this one right now" rather than a normal
           unselected option. */
        .highlight-swatch:disabled {
            opacity: 0.35;
            cursor: not-allowed;
        }
        .highlight-swatch:disabled:hover {
            filter: none;
        }
        #verse-note-input {
            resize: vertical;
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .verse-note-actions {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            /* Separates Save/Delete from the textarea above them. */
            margin-top: 6px;
        }
        /* Save/Delete share the row equally when there's room, wrapping to
           their own full-width rows only if the modal is too narrow. */
        .hn-note-action-btn {
            flex: 1 1 120px;
        }
        /* Green outline (was the default pink accent-border). */
        #verse-note-save-btn {
            border-color: var(--accent-secondary) !important;
        }
        @supports (background-color: color-mix(in srgb, red, blue)) {
            /* Green tint (was inheriting .app-btn-primary's pink tint,
               mismatched against this button's own green border), per
               explicit request. */
            #verse-note-save-btn {
                background-color: color-mix(in srgb, var(--accent-secondary) 15%, var(--bg-card)) !important;
            }
            #verse-note-save-btn:hover, #verse-note-save-btn:focus-visible {
                background-color: color-mix(in srgb, var(--accent-secondary) 28%, var(--bg-card)) !important;
            }
        }
        /* Share and Add Note are both full-width, stacked. */
        .verse-full-width-btn {
            width: 100%;
            margin-bottom: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        .verse-share-icon {
            width: 20px;
            height: 20px;
        }
        /* Yellow border instead of the default pink accent-border, plus a
           forced-white icon (ID beats .btn-theme svg's theme-following
           color/stroke, which would otherwise recolor it per theme). */
        #verse-share-btn {
            /* Pink (was yellow), per explicit request. */
            border-color: var(--accent-primary);
            /* Extra breathing room above Share specifically (on top of the
               .verse-actions-section + .verse-actions-section gap above
               its whole section), per explicit request. */
            margin-top: 16px;
        }
        #verse-share-btn svg {
            stroke: #FFFFFF;
            color: #FFFFFF;
        }
        /* The saved note preview is a real <button> (tap/click to edit) -
           reset text-align/font/width away from the button defaults so it
           still reads as a content card, not an obviously-a-button. */
        .verse-note-display {
            width: 100%;
            margin-top: 4px;
            /* More vertical breathing room above/below the note text. */
            padding: 18px 12px;
            border-radius: 8px;
            border: 2px solid var(--divider-color);
            background-color: var(--bg-body);
            text-align: left;
            font-family: inherit;
            cursor: pointer;
        }
        .verse-note-display:hover, .verse-note-display:focus-visible {
            border-color: var(--accent-primary);
        }
        /* Clamped to 12 lines by default - renderVerseNotesDisplay() in
           app.js only reveals a "Show More"/"Show Less" toggle (below)
           when a note's text actually overflows this, and that toggle
           adds/removes .expanded to lift the clamp. */
        .verse-note-display-text {
            margin: 0;
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-main);
            white-space: pre-line;
            overflow-wrap: break-word;
            display: -webkit-box;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: 12;
            overflow: hidden;
        }
        .verse-note-display-text.expanded {
            display: block;
            -webkit-line-clamp: unset;
            overflow: visible;
        }
        /* Styled like "Show More/Less Colors" (.more-colors-toggle-btn) -
           a real nested button (not just a styled span) so it's
           independently focusable/keyboard-activatable; its click
           handler stops propagation so tapping it doesn't also open the
           Note Editor via the card's own click handler. */
        .verse-note-more-toggle {
            display: inline-block;
            background: none;
            border: none;
            color: var(--accent-focus);
            font-weight: 700;
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            cursor: pointer;
            padding: 4px 0;
            /* Was 4px - too tight against the note text right above it
               (most noticeable in Verse Actions' own Notes section, below
               the Share/Add Note buttons), per explicit request. */
            margin-top: 12px;
        }
        .verse-note-more-toggle:hover, .verse-note-more-toggle:focus-visible {
            text-decoration: underline;
        }
        .verse-note-more-toggle[hidden] {
            display: none;
        }
        .verse-note-display-date {
            display: block;
            margin-top: 6px;
            /* Matches .verse-note-display-text's own explicit size exactly
               (was 1em - relative to the surrounding <button>'s font-size,
               which is unset/browser-default since buttons don't inherit
               font-size by default, so this never actually matched the
               note text's size at all). */
            font-size: calc(1.3rem * var(--ui-scale));
            /* --text-main + opacity (was var(--text-muted)), per explicit
               request - same muted-text approach as .settings-field-note/
               .hn-note-date elsewhere in this file. */
            color: var(--text-main);
            opacity: 0.85;
        }

        /* Cross References - Verse Actions' per-verse section (before
           Notes). Votes decide the sort order server-side only - never
           shown to the user, so there's nothing else here but the label
           itself. Never smaller than 1.375rem like every other reference/
           text-preview element in this file - references specifically
           use 1.45rem per explicit request, one step up from that floor. */
        /* Bullets are purely visual grouping here - shared by Cross
           References and all three Commentaries lists (they all use this
           class). A native <li> marker (list-style, ::marker) is never
           exposed to the accessibility tree by any screen reader - it's
           CSS-rendered, not real text - so this needs no aria-hidden or
           other extra work to stay silent for screen reader users. */
        .xrefs-list {
            list-style: disc;
            margin: 0;
            /* Indented further (was 1.3em), per explicit follow-up. */
            padding: 0 0 0 2em;
        }
        .xrefs-list li::marker {
            color: #FFFFFF;
            /* Was 2em (double) - too big, scaled back to a quarter bigger
               than normal per explicit follow-up. */
            font-size: 1.25em;
        }
        /* The "No cross references yet" placeholder is still an <li> (see
           renderXrefEntries()) so it lines up with real rows, but it's a
           status message, not an actual list entry - no bullet. */
        .xrefs-list li.xrefs-empty {
            list-style: none;
        }
        .xref-link-btn {
            display: block;
            width: 100%;
            text-align: left;
            background: none;
            border: none;
            cursor: pointer;
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 600;
            color: var(--accent-secondary);
            text-decoration: underline;
            padding: 6px 0;
        }
        .xref-link-btn:hover, .xref-link-btn:focus-visible {
            opacity: 0.8;
        }
        .xrefs-empty {
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-main);
            opacity: 0.85;
            margin: 0;
        }
        /* Matches .verse-note-more-toggle/.more-colors-toggle-btn's own
           "Show More" convention, including the same rotating chevron
           (.more-colors-chevron) - built in JS (see
           ensureXrefsMoreBtnStructure()) since these buttons are shared
           between the static Verse Actions one and however many
           chapter-level ones get created per chapter. */
        .xrefs-more-btn {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            background: none;
            border: none;
            color: var(--accent-focus);
            font-weight: 700;
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            cursor: pointer;
            padding: 4px 0;
            margin-top: 2px;
        }
        .xrefs-more-btn:hover, .xrefs-more-btn:focus-visible {
            text-decoration: underline;
        }
        .xrefs-more-btn[hidden] {
            display: none;
        }
        .xrefs-more-chevron {
            width: 18px;
            height: 18px;
            flex-shrink: 0;
            transition: transform 0.2s;
        }
        .xrefs-more-btn[aria-expanded="true"] .xrefs-more-chevron {
            transform: rotate(90deg);
        }

        /* Cross-reference verse preview modal (tapping a reference link,
           either here or in the chapter-level section near the copyright
           footer) - plain text + a "Go to" button, same app-menu-card
           shell every other small dialog in this file already uses. */
        .xref-preview-text {
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            color: var(--text-main);
            margin: 0 0 20px;
        }
        /* vertical-align:middle on the arrow alone is font-metric-dependent
           and was rendering visibly off-center - flexbox on the button
           itself aligns the text and arrow reliably regardless of glyph
           metrics, so the arrow no longer needs any alignment rule of its
           own. */
        #xref-preview-goto-btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
        }
        .xref-goto-arrow {
            font-weight: bold;
        }

        /* Commentary content modal (commentary-content-dialog) - shared by
           every tappable row in the Commentaries/Current Chapter/Current
           Book sections. Groups the Play button with Close so .app-menu-header's
           justify-content:space-between only splits two things (heading vs.
           this group), not three floating apart across the header. */
        .commentary-content-header-btns {
            display: flex;
            align-items: center;
            gap: 8px;
            flex-shrink: 0;
        }
        /* Same box size AND font-size (the em unit below is relative to
           this, not ambient text) as .app-menu-close-btn, so the two sit
           at identical size and scale together via --ui-scale exactly the
           same way, rather than via --toolbar-scale like the toolbar's own
           Play/Pause does. Pill radius (999px) per explicit request - on
           this square box it renders as a circle, same silhouette as
           Close, while still literally being a pill. Colors/border/hover
           lifted directly from .play-pause-btn (the toolbar's own Play/
           Pause) instead of the toolbar's toolbar-scale-based sizing. */
        .commentary-content-play-btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
            width: 1.8em;
            height: 1.8em;
            font-size: calc(1.3rem * var(--ui-scale));
            border-width: 2px;
            border-style: solid;
            border-color: var(--accent-focus);
            border-radius: 999px;
            background-color: var(--bg-card);
            color: var(--text-main);
            cursor: pointer;
        }
        @supports (background-color: color-mix(in srgb, red, blue)) {
            .commentary-content-play-btn {
                background-color: color-mix(in srgb, var(--accent-focus) 15%, var(--bg-card));
            }
        }
        .commentary-content-play-btn[aria-pressed="true"] {
            color: #FFFFFF;
            background-color: #DC2626;
        }
        .commentary-content-play-btn:hover, .commentary-content-play-btn:focus-visible {
            filter: brightness(1.1);
        }
        .commentary-content-play-icon {
            width: 55%;
            height: 55%;
        }
        /* "Matthew Henry on Genesis 1:1-2" - the source name plus the
           actual range THIS entry covers, shown separately from the
           dialog's own title (which is always the reference the user
           asked about, e.g. the single verse they tapped) since a prose
           entry's real range can run wider than that - see
           formatCommentaryReference()/formatCommentarySourceLine() in
           app.js. */
        .commentary-content-source {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            color: var(--accent-secondary);
            margin: 0 0 12px;
        }
        .commentary-content-attribution {
            font-size: calc(1.3rem * var(--ui-scale));
            color: var(--text-main);
            opacity: 0.8;
            margin: 0 0 16px;
        }
        .commentary-content-attribution a {
            color: var(--accent-secondary);
        }
        .commentary-content-text p {
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            color: var(--text-main);
            margin: 0 0 16px;
        }
        .commentary-content-text p:last-child {
            margin-bottom: 0;
        }
        /* Thru the Bible's outline format (format:'outline' from
           commentary.php) - a nested I/A/1/a structure, so each line
           indents by its outline_level rather than reading as flat
           paragraphs like every other (prose) commentary source. */
        .commentary-outline-line {
            margin-left: calc(var(--commentary-outline-indent, 0) * 1.25em);
        }
        .commentary-outline-level-0 { --commentary-outline-indent: 0; font-weight: 700; }
        .commentary-outline-level-1 { --commentary-outline-indent: 1; }
        .commentary-outline-level-2 { --commentary-outline-indent: 2; }
        .commentary-outline-level-3 { --commentary-outline-indent: 3; }
        /* The all-caps section name split out of a line's start by
           splitOutlineHeading() in commentary.php (e.g. "CREATION" out of
           "CREATION, Chapters 1") - styled as a real heading (bigger,
           accent color, letter-spaced) above its own paragraph, same
           indent level as the line it came from. text-transform is a
           display-only safety net - the text is already uppercase from
           the source data, this just guarantees it stays that way
           regardless. */
        .commentary-outline-heading {
            margin-left: calc(var(--commentary-outline-indent, 0) * 1.25em);
            margin-bottom: 4px;
            font-size: calc(1.1em);
            font-weight: 800;
            letter-spacing: 0.04em;
            text-transform: uppercase;
            color: var(--accent-secondary);
        }
        .commentary-outline-heading + .commentary-outline-line {
            margin-top: 0;
        }

        /* format:'blocks' entries (commentary.php, mcgee-book-content.sqlite)
           - hand-reviewed, book-level-only content with real headings/
           tables/images instead of one flattened text blob. Genesis is the
           first book built this way (2026-08-06); see
           commentaries/mcgee-book-content.sqlite and
           commentaries/mcgee-assets/. Rendered by renderCommentaryBlocks()
           in app.js. Section headings (h3) and the Outline's own I./II.
           divisions (h4) are deliberately the SAME font-size as the
           surrounding body text - real headings, not bigger ones, per
           explicit request. */
        .commentary-block-h3 {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: bold;
            margin: 26px 0 8px;
            padding-bottom: 6px;
            border-bottom: 1px solid var(--text-main);
            color: var(--text-main);
        }
        @supports (border-color: color-mix(in srgb, red, blue)) {
            .commentary-block-h3 {
                border-bottom-color: color-mix(in srgb, var(--text-main) 75%, transparent);
            }
        }
        .commentary-block-h3:first-child {
            margin-top: 0;
        }
        .commentary-block-h4 {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 600;
            margin: 20px 0 8px;
            color: var(--text-main);
        }
        /* "Key Verse" (Exodus and any future book that has one) - a
           direct Scripture quote pulled out of the surrounding prose,
           same left-border-accent convention as a normal blockquote. */
        .commentary-block-quote {
            margin: 0 0 16px;
            padding: 12px 16px;
            border-left: 3px solid var(--accent-secondary);
            background: var(--bg-expanded);
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            font-style: italic;
            color: var(--text-main);
        }
        .commentary-block-list {
            margin: 0 0 16px;
            padding-left: 22px;
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            color: var(--text-main);
        }
        .commentary-block-list li {
            margin-bottom: 4px;
        }
        /* Applied by renderCommentaryBlocks() in app.js when every item
           already leads with its own "1. "/"2 - " number as literal text -
           a bullet on top of that would double-mark the list. */
        .commentary-block-list-numbered {
            list-style: none;
            padding-left: 0;
        }
        .commentary-block-table {
            width: 100%;
            border-collapse: collapse;
            font-size: calc(1.3rem * var(--ui-scale));
            margin: 4px 0 18px;
            border: 1px solid var(--text-main);
            color: var(--text-main);
        }
        .commentary-block-table th, .commentary-block-table td {
            text-align: left;
            padding: 8px 10px;
            border: 1px solid var(--text-main);
            vertical-align: top;
            /* Neither column ever exceeds half the table's width, on
               mobile OR desktop - a long description column would
               otherwise crush the short reference/label column to
               near-nothing. max-width (not width) so a column narrower
               than half - the common case for a short first column - can
               still size down to its actual content instead of being
               forced to exactly 50%. Requires dropping white-space:nowrap
               below - nowrap would keep the first column trying to fit
               its text on one line, overflowing straight through this
               cap instead of wrapping to respect it. Every format:'blocks'
               table happens to be 2 columns so far - a 3+ column table is
               a "figure it out when it happens", not guessed at now. */
            max-width: 50%;
            overflow-wrap: break-word;
        }
        @supports (border-color: color-mix(in srgb, red, blue)) {
            .commentary-block-table, .commentary-block-table th, .commentary-block-table td {
                border-color: color-mix(in srgb, var(--text-main) 85%, transparent);
            }
        }
        .commentary-block-table th {
            background: var(--bg-expanded);
            font-size: calc(1.3rem * var(--ui-scale));
            letter-spacing: 0.04em;
            text-transform: uppercase;
        }
        .commentary-block-table td:first-child {
            font-weight: 700;
        }
        /* The chart/diagram image is a white-background screenshot of the
           actual source PDF page (not a redrawn approximation) - wrap's
           own background is a fixed white for the same reason .commentary-
           content-text itself isn't (this needs to invert IN STEP with the
           image on top of it, not according to the theme tokens), so
           wrap+image invert together as one seamless unit in dark mode
           instead of a white box appearing around a darkened image. */
        /* Border color is a fixed literal, like the background above -
           var(--text-main) would be WHITE in dark mode, invisible against
           this wrap's own always-white background before the filter:
           invert(1) below ever gets a chance to run. A fixed dark gray
           inverts to a visible light gray right along with everything
           else in the wrap, the same way the white background becomes
           black - matching .commentary-block-table's border weight
           (color-mix at 85%) isn't possible here for the same reason. */
        .commentary-block-image-wrap {
            padding: 14px;
            background: #FFFFFF;
            border-radius: 8px;
            margin: 6px 0 4px;
            border: 1px solid #444444;
        }
        .commentary-block-image-wrap img {
            display: block;
            width: 100%;
            height: auto;
        }
        [data-theme="dark"] .commentary-block-image-wrap { filter: invert(1); }
        [data-theme="light"] .commentary-block-image-wrap { filter: none; }
        @media (prefers-color-scheme: dark) {
            :root:not([data-theme="light"]) .commentary-block-image-wrap { filter: invert(1); }
        }
        /* The inline chart/diagram is a real <a target="_blank"> (see
           renderCommentaryBlocks() in app.js) that opens the image file
           directly in a new tab - the browser's own native viewer handles
           zoom/pan from there, no custom dialog needed. */
        a.commentary-block-image-wrap {
            display: block;
            width: 100%;
            text-decoration: none;
            /* border intentionally NOT reset here - the 1px #444444 border
               above is wanted on the link too. */
            font: inherit;
            cursor: zoom-in;
            position: relative;
        }
        a.commentary-block-image-wrap:hover,
        a.commentary-block-image-wrap:focus-visible {
            filter: brightness(0.92);
        }
        [data-theme="dark"] a.commentary-block-image-wrap:hover,
        [data-theme="dark"] a.commentary-block-image-wrap:focus-visible {
            filter: invert(1) brightness(0.92);
        }
        @media (prefers-color-scheme: dark) {
            :root:not([data-theme="light"]) a.commentary-block-image-wrap:hover,
            :root:not([data-theme="light"]) a.commentary-block-image-wrap:focus-visible {
                filter: invert(1) brightness(0.92);
            }
        }
        a.commentary-block-image-wrap:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        /* Small "opens in a new tab" affordance in the corner - purely
           decorative (aria-hidden), the link's own accessible name is what
           actually tells a screen reader user what tapping it does. */
        .commentary-block-image-zoom-hint {
            position: absolute;
            bottom: 8px;
            right: 8px;
            width: 28px;
            height: 28px;
            border-radius: 999px;
            background: rgba(17, 24, 39, 0.75);
            color: #FFFFFF;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .commentary-block-image-zoom-hint svg {
            width: 16px;
            height: 16px;
        }
        /* Hidden behind a disclosure (see the <details> in
           renderCommentaryBlocks() in app.js) rather than always shown -
           the image itself is the point, the description is there for
           screen reader/low-vision users who want it, not a mandatory
           wall of text under every chart. Same +/- marker convention as
           .chapter-xrefs-summary/.settings-group summary elsewhere in
           this file. */
        .commentary-block-caption-details {
            margin: 8px 0 18px;
        }
        .commentary-block-caption-summary {
            cursor: pointer;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            color: var(--accent-secondary);
            list-style: none;
            display: flex;
            align-items: center;
            gap: 0.5em;
        }
        .commentary-block-caption-summary::-webkit-details-marker { display: none; }
        .commentary-block-caption-summary::before {
            content: "+";
            display: inline-block;
            position: relative;
            top: -2px;
            color: var(--accent-primary);
            font-weight: 800;
            transform: scale(1.3);
        }
        .commentary-block-caption-details[open] > .commentary-block-caption-summary::before {
            content: "\2212";
        }
        /* Same size as ordinary body text (matches .commentary-content-text
           p's own calc(1.375rem * var(--ui-scale))) once expanded - this
           is real content someone chose to read, not a small muted
           footnote. */
        .commentary-block-caption {
            margin: 8px 0 0;
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            color: var(--text-main);
            opacity: 0.85;
        }
        .commentary-block-outline ol {
            list-style: none;
            margin: 0;
            padding-left: 1.25em;
        }
        .commentary-block-outline li {
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
            color: var(--text-main);
            margin-bottom: 6px;
        }
        .commentary-block-outline .commentary-division-label {
            display: block;
            font-weight: 700;
        }
        .commentary-block-download {
            margin: 30px 0 0;
            padding-top: 20px;
            border-top: 1px solid var(--text-main);
        }
        @supports (border-color: color-mix(in srgb, red, blue)) {
            .commentary-block-download {
                border-top-color: color-mix(in srgb, var(--text-main) 75%, transparent);
            }
        }
        .commentary-block-download a {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 2px;
            text-decoration: none;
        }
        .commentary-block-download .commentary-block-download-meta {
            display: block;
            font-weight: normal;
            font-size: calc(1.3rem * var(--ui-scale));
            opacity: 0.85;
        }

        /* Chapter-level Cross References - collapsible section between
           the passage and the copyright footer. Same +/- marker
           convention as .settings-group/.hn-testament-group elsewhere in
           this file. Collapsed by default - a whole chapter's combined
           list can run well past 100 links. */
        .chapter-xrefs-section {
            margin: 24px 0;
        }
        .chapter-xrefs-summary {
            cursor: pointer;
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 800;
            color: var(--accent-secondary);
            list-style: none;
            display: flex;
            align-items: center;
            gap: 0.5em;
        }
        .chapter-xrefs-summary::-webkit-details-marker { display: none; }
        .chapter-xrefs-summary::before { content: "+"; display: inline-block; position: relative; top: -2px; color: var(--accent-primary); font-weight: 800; transform: scale(1.3); }
        .chapter-xrefs-section[open] > .chapter-xrefs-summary::before { content: "\2212"; }
        .chapter-xrefs-body {
            padding-top: 16px;
        }
        .chapter-xref-verse-group {
            margin-bottom: 20px;
        }
        .chapter-xref-verse-group:last-child {
            margin-bottom: 0;
        }
        .chapter-xref-verse-label {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            color: var(--text-main);
            margin: 0 0 4px;
        }

        /* "More Colors" accordion toggle - chevron rotates 90deg when the
           secondary swatch row (#highlight-swatch-row-secondary) is
           expanded, matching the +/- convention used by .settings-group
           elsewhere in this file (a visual "this reveals more" cue). */
        .more-colors-toggle-btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
            background: none;
            border: none;
            color: var(--accent-focus);
            font-weight: 700;
            /* <button> elements don't inherit font-size from their parent
               by default - this was silently falling back to the
               browser's own default form-control size instead of the
               page's actual (scaled) body text. */
            font-size: calc(1.3rem * var(--ui-scale));
            cursor: pointer;
            padding: 16px 0;
            width: 100%;
        }
        .more-colors-toggle-btn:hover, .more-colors-toggle-btn:focus-visible {
            text-decoration: underline;
        }
        .more-colors-chevron {
            width: 18px;
            height: 18px;
            transition: transform 0.2s;
        }
        .more-colors-toggle-btn[aria-expanded="true"] .more-colors-chevron {
            transform: rotate(90deg);
        }
        /* "..." button at the end of the expanded color list - opens the
           Favorite Highlight Colors modal. Same round shape as the color
           swatches, but themed like a plain toolbar button instead of a
           solid color, so it doesn't read as an actual color choice. */
        .more-colors-manage-btn {
            width: 44px;
            height: 44px;
            border-radius: 50%;
            border-width: 2px;
            border-style: solid;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            flex-shrink: 0;
        }
        .more-colors-manage-icon {
            width: 24px;
            height: 24px;
        }

        /* Bible Navigation Style "List & Chapters Grid" (Settings > App
           Display Options) - alternative to the Book/Chapter/Verse
           dropdowns, built by buildBibleNavBookList() in app.js. */
        .bible-nav-book-list {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }
        /* Same [hidden]-vs-author-origin-display fix used elsewhere in this
           file (#verse-range-add-btn, .more-colors-toggle-btn, etc.) -
           .bible-nav-book-list/.settings-group-fields's own display:flex
           (author-origin) otherwise beats the browser's default
           [hidden]{display:none} (user-agent origin) regardless of
           specificity, so toggling .hidden in JS did nothing without this.
           Covers #bible-nav-dropdowns and #bible-nav-list (swapped by the
           Bible Navigation Style setting) and every per-book
           .bible-nav-chapter-grid (only one should ever be open, per
           buildBibleNavBookList()'s click handler). */
        #bible-nav-dropdowns[hidden],
        #bible-nav-list[hidden],
        #bible-nav-history-shortcut-btn[hidden],
        .bible-nav-chapter-grid[hidden] {
            display: none;
        }
        .bible-nav-testament-heading {
            margin: 20px 0 8px;
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 800;
            /* Green (was yellow), per explicit request. */
            color: var(--accent-secondary);
        }
        .bible-nav-book-item {
            /* Softened from a solid --border-color: 66 of these stacked down
               the list made the dividers louder than the book names they
               separate. Derived from the same token rather than a new hex,
               so it still follows the theme. */
            border-bottom: 2px solid var(--divider-color);
        }
        /* Revelation - nothing follows it, so its divider was just a line
           hanging under the end of the list. */
        .bible-nav-book-item:last-child {
            border-bottom: none;
        }
        /* Book name size matches the Text Size setting - applyTextSize()
           in app.js keeps the same .size-* class on #bible-nav-list that
           #verse-text carries, and font-size:inherit here picks that up.
           <button> doesn't inherit font-size the way most elements do by
           default, so this needs to be explicit. */
        .bible-nav-book-btn {
            display: block;
            width: 100%;
            text-align: left;
            background: none;
            border: none;
            padding: 10px 4px;
            /* Back on the passage Text Size (was calc(1.2rem *
               var(--ui-scale)), which the one-size-for-everything pass
               swept it into) - applyTextSize() in app.js puts the same
               .size-* class on #bible-nav-list that #verse-text carries,
               and these four inherits carry size, family, weight and
               colour down from it. A <button> inherits none of them by
               default - the UA sheet sets its own font shorthand - so
               each one has to be asked for explicitly. */
            font-size: inherit;
            font-family: var(--passage-font-family, inherit);
            font-weight: inherit;
            color: inherit;
            cursor: pointer;
            /* em (not px) so this scales with whatever size the book title
               itself currently renders at - scrollIntoView({block:'start'})
               (see the click handlers in app.js) respects this, leaving a
               little breathing room above instead of scrolling the title
               flush against the modal's top edge. --bible-nav-sticky-height
               adds the sticky Translation field's own current height on
               top of that (List & Chapters Grid style only - see
               #bible-nav-translation-field above), so a book title lands
               visibly below it rather than tucked underneath - measured
               fresh in app.js since it can vary slightly (label wrapping,
               text size setting), defaulting to 0px when it isn't sticky.
               Kept small (0.5em, was 3em before the sticky field existed)
               since that field's own real height is now doing most of the
               clearance work - 3em on top of it was scrolling noticeably
               too far past the book title. */
            scroll-margin-top: calc(0.5em + var(--bible-nav-sticky-height, 0px));
        }
        /* :focus-visible only, not :hover - hover is sticky on touch
           (there's no real pointer to "unhover" after a tap until the next
           one lands elsewhere), so tapping a book to open its chapters was
           leaving its text tinted as if still hovered. :focus-visible only
           applies for keyboard focus, which correctly clears when focus
           moves elsewhere. */
        .bible-nav-book-btn:focus-visible {
            color: var(--accent-primary);
        }
        /* 1fr columns (always exactly 5 side by side, sharing whatever
           width is available) inside a capped-width container, rather than
           minmax(0, 60px) columns filling the full modal width - relying on
           a fixed-max track to shrink itself below that max on a narrow
           screen is less consistent across browsers than just capping the
           container and letting 1fr divide whatever's actually available.
           No justify-content needed either - a block-level element with
           max-width naturally sits at its parent's left edge already,
           matching every book name above it. */
        /* REVERT NOTE: was a flat "max-width: 300px; gap: 6px", which made
           every box exactly 55px wide no matter what either size setting
           said. Now both scale with the App Size setting so the boxes and
           the numbers inside them stay in the same proportion to each
           other at every scale. Still capped by the container, so the
           large scales can't push the grid wider than the modal. */
        .bible-nav-chapter-grid {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            max-width: calc(300px * var(--ui-scale));
            gap: calc(6px * var(--ui-scale));
            padding-bottom: 12px;
        }
        /* Square (aspect-ratio) with its own container-query context, so
           the number inside (.bible-nav-chapter-num) can be sized in cqi
           units. The number inside is sized directly off the App Size
           setting now (see .ch-digits-* below), so this no longer inherits
           the book-title text size - nothing reads an em off it any more. */
        .bible-nav-chapter-btn {
            aspect-ratio: 1;
            container-type: inline-size;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0;
            border: none;
            border-radius: 6px;
            /* Accent green at half strength, derived from the variable
               rather than hardcoded, so a box still follows whatever colour
               someone picks in Settings > Theme Colors. Relative colour
               syntax is already used for the verse highlights above, so
               it's a supported pattern here, not a new dependency. */
            background-color: rgb(from var(--accent-secondary) r g b / 0.35);
            color: #FFFFFF;
            cursor: pointer;
        }
        .bible-nav-chapter-btn:hover, .bible-nav-chapter-btn:focus-visible {
            background-color: rgb(from var(--accent-secondary) r g b / 0.5);
        }
        /* Marks the chapter currently loaded in the passage view (see
           syncBibleNavToCurrentChapter() in app.js), so reopening Bible
           Navigation shows the reader where they already are. outline
           instead of border - doesn't add to the button's own box, so it
           can't perturb this one cell's size relative to its neighbors in
           the grid (this app never sets box-sizing:border-box globally). */
        .bible-nav-chapter-btn.bible-nav-chapter-current {
            outline: 2px solid var(--accent-focus);
            outline-offset: -2px;
            /* Same tint a highlighted verse gets (--highlight-yellow),
               layered over the button's own dark background instead of
               replacing it. */
            background-color: var(--highlight-yellow);
        }
        /* Light mode keeps the same green fill as dark (this superseded an
           earlier request for transparent boxes here), but takes
           --text-main rather than white: the light theme's green is a much
           darker hex, so at 50% over a light dialog it lands as a pale
           green that white text can't sit on.
           :not(.bible-nav-chapter-current) keeps the current chapter's own
           highlight-yellow background untouched regardless of selector
           specificity. */
        :root[data-theme="light"] .bible-nav-chapter-btn:not(.bible-nav-chapter-current) {
            color: var(--text-main);
        }
        :root[data-theme="light"] .bible-nav-chapter-btn:not(.bible-nav-chapter-current):hover,
        :root[data-theme="light"] .bible-nav-chapter-btn:not(.bible-nav-chapter-current):focus-visible {
            background-color: rgb(from var(--accent-secondary) r g b / 0.5);
        }
        .bible-nav-chapter-num {
            font-weight: 700;
            line-height: 1;
        }
        /* clamp()'s middle (cqi) value is what actually drives size most
           of the time - still shrinking/growing with the button exactly
           as before. The 1.25em ceiling (relative to the book-title size
           this button just inherited) only kicks in once cqi would
           otherwise make a number more than 1.25x the size of the book
           names above it - a second, independent cap alongside the
           chapter grid's own 60px column-width cap (.bible-nav-chapter-
           grid above), since that alone doesn't account for how small the
           book titles themselves currently are. */
        /* Verse grid ("List, Chapters & Verses Grid" style) - sits INSIDE
           the chapter grid, spanning every column, so it opens as its own
           full-width row directly beneath the chapter that was tapped. The
           boxes themselves are .bible-nav-chapter-btn, so they inherit the
           chapter styling above (size, color, light mode, current-chapter
           outline) rather than repeating it here. */
        /* The chapter whose verses are currently open - deliberately the
           brightest thing in the grid (near-white on dark, near-black on
           light) rather than the softer --border-color the verse panel
           uses, so the tapped box is unmistakable. outline rather than
           border for the same reason .bible-nav-chapter-current uses one:
           this app doesn't set box-sizing globally, so a border would
           resize this one box relative to its grid neighbours. Skipped
           when the box is also the currently-loaded chapter, so this can't
           quietly replace that yellow "you are here" marker. */
        .bible-nav-chapter-btn[aria-expanded="true"]:not(.bible-nav-chapter-current) {
            outline: 2px solid #f0f0f0;
            outline-offset: -2px;
        }
        :root[data-theme="light"] .bible-nav-chapter-btn[aria-expanded="true"]:not(.bible-nav-chapter-current) {
            outline-color: #111111;
        }
        .bible-nav-verse-grid {
            /* Sits after the chapter grid as its sibling, not inside it, so
               the chapter numbers stay one unbroken block. Same max-width
               as that grid so the two line up. */
            max-width: 300px;
            /* toggleVerseGrid() scrolls this panel to the top of the
               scrollport, and in List & Chapters Grid style the Translation
               dropdown is pinned there - without this the "Choose verse"
               heading lands underneath it. Same variable .bible-nav-book-btn
               uses, kept in sync by updateBibleNavStickyOffset(). */
            scroll-margin-top: calc(0.5em + var(--bible-nav-sticky-height, 0px));
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            /* Wider than the chapter grid's own 6px, deliberately - the
               verse boxes sit inside a narrower panel, so they need a bit
               more air between them to not read as crowded. */
            gap: 8px;
            /* Tighter above than below: the divider line does the work of
               separating this from the chapter grid now, so the gap there
               doesn't have to. */
            margin: 12px 8px 26px;
            padding: 10px;
            /* No box around the panel and no fill - only a rule along the
               top, marking where the chapters end and the verses begin.
               The shorthand has to come FIRST: written after border-top it
               resets it back to 0 and the divider silently disappears. */
            border: none;
            border-top: 2px solid rgb(from var(--border-color) r g b / 0.45);
            background-color: transparent;
        }
        .bible-nav-verse-grid[hidden] {
            display: none;
        }
        .bible-nav-verse-grid-head {
            grid-column: 1 / -1;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
        }
        /* Up-arrow that closes the picker and scrolls back to the book
           title - the way back out, since the panel can now open a long
           way below the chapter that was tapped. */
        .bible-nav-verse-collapse {
            flex-shrink: 0;
            /* Nudged up against the label, which carries a 2px bottom
               margin - relative offset rather than a negative margin so it
               shifts the full 2px instead of half of it (flex centring
               splits margin changes between both edges). */
            position: relative;
            top: -2px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 42px;
            height: 42px;
            padding: 0;
            background: none;
            border: none;
            border-radius: 6px;
            color: var(--text-main);
            cursor: pointer;
        }
        .bible-nav-verse-collapse svg {
            width: 34px;
            height: 34px;
        }
        .bible-nav-verse-collapse:hover, .bible-nav-verse-collapse:focus-visible {
            background-color: var(--bg-hover);
        }
        .bible-nav-verse-grid-label span {
            display: block;
        }
        .bible-nav-verse-grid-sub {
            font-weight: 700;
        }
        .bible-nav-verse-grid-label {
            margin: 0 0 2px;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            line-height: 1.25;
            /* Same green the Old/New Testament headings use, so this reads
               as part of the same heading language. */
            color: var(--accent-secondary);
        }
        /* Verse boxes share .bible-nav-chapter-btn for shape and sizing but
           take the PINK accent where the chapter boxes take the green one,
           both at the same half strength - so the two grids are told apart
           by hue rather than by brightness, and both still follow whatever
           colours someone picks in Settings > Theme Colors.
           These rules deliberately sit AFTER the .bible-nav-chapter-btn
           rules above - equal specificity, so source order settles it. */
        .bible-nav-verse-grid .bible-nav-chapter-btn {
            background-color: rgb(from var(--accent-primary) r g b / 0.35);
        }
        .bible-nav-verse-grid .bible-nav-chapter-btn:hover,
        .bible-nav-verse-grid .bible-nav-chapter-btn:focus-visible {
            background-color: rgb(from var(--accent-primary) r g b / 0.5);
        }
        :root[data-theme="light"] .bible-nav-verse-grid .bible-nav-chapter-btn {
            color: var(--text-main);
        }
        /* Tapping a chapter opens the verses instead of loading it, so this
           is how the whole chapter is still reachable - full width above
           the verse numbers, sized like the app's other ordinary buttons
           rather than like a numbered box. */
        .bible-nav-whole-chapter-btn {
            grid-column: 1 / -1;
            margin-bottom: 3px;
        }
        /* REVERT NOTE: these were
             .ch-digits-1 { font-size: clamp(1px, 90cqi, 1.25em); }
             .ch-digits-2 { font-size: clamp(1px, 68cqi, 1.25em); }
             .ch-digits-3 { font-size: clamp(1px, 54cqi, 1.25em); }
           which tied the numbers to the passage Text Size (the 1.25em cap,
           inherited from #bible-nav-list) and let cqi push them to ~50px at
           the larger text sizes. They now scale with the App Size setting
           like the rest of the app's chrome, keeping the same taper by
           digit count so a three-digit chapter still fits its box. Landed
           between the two extremes on request: the old cqi rule ran up to
           ~50px at the larger Text Sizes, the first pass at this dropped
           to 24px and read as too small, so a single digit sits on 33.6px
           at App Size Medium - a bit over 60% of the box width. */
        .ch-digits-1 .bible-nav-chapter-num { font-size: calc(2.1rem * var(--ui-scale)); }
        .ch-digits-2 .bible-nav-chapter-num { font-size: calc(1.75rem * var(--ui-scale)); }
        .ch-digits-3 .bible-nav-chapter-num { font-size: calc(1.35rem * var(--ui-scale)); }

        /* Bible Navigation - History section (last 7 days of loaded
           passages). */
        .bible-nav-history-section {
            margin-top: 24px;
            padding-top: 20px;
            border-top: 2px solid var(--divider-color);
        }
        .bible-nav-history-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 12px;
        }
        #bible-nav-history-heading {
            margin: 0;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            color: var(--text-main);
            /* scrollIntoView({block:'start'}) (see the History shortcut
               button's click handler in app.js) was landing this heading
               flush against the scroll container's top edge, right under
               the sticky Translation field (which only ever shows
               alongside this History shortcut button - same Bible
               Navigation Style condition), hiding it underneath -
               --bible-nav-sticky-height clears that (same variable/value
               .bible-nav-book-btn below uses, which already gets this
               right). The previous few attempts at tuning this value
               never actually changed anything live - the real bug was
               that updateBibleNavStickyOffset() (app.js) was setting this
               variable on #bible-nav-list specifically, and this heading
               is a sibling of that element, not a descendant, so the
               variable never cascaded here at all - it's set on
               document.documentElement now, so this plain formula (no
               extra buffer or subtraction) should behave correctly. */
            scroll-margin-top: var(--bible-nav-sticky-height, 0px);
        }
        .history-menu-btn {
            width: 36px;
            height: 36px;
            border-radius: 50%;
            border-width: 2px;
            border-style: solid;
            border-color: var(--border-color);
            background: none;
            color: var(--text-main);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            flex-shrink: 0;
        }
        .history-menu-btn .more-colors-manage-icon {
            width: 20px;
            height: 20px;
        }
        .history-menu-btn:hover, .history-menu-btn:focus-visible {
            border-color: var(--accent-primary);
        }
        .history-date-heading {
            margin: 16px 0 8px;
            /* Matches .bible-nav-testament-heading - the two are the same
               kind of thing (a heading over a list of taps) and read as
               mismatched at different sizes. */
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 800;
            text-transform: uppercase;
            letter-spacing: 0.04em;
            /* Green (was yellow), per explicit request. */
            color: var(--accent-secondary);
        }
        .history-date-heading:first-child {
            margin-top: 0;
        }
        .history-entry {
            display: block;
            width: 100%;
            text-align: left;
            /* Left padding bumped to indent the reference text a bit,
               per explicit request - the date headings above these stay
               unindented. */
            padding: 8px 4px 8px 16px;
            margin-bottom: 2px;
            background: none;
            border: none;
            border-radius: 6px;
            color: inherit;
            /* Same size as the book list picker - applyTextSize() puts the
               same .size-* class on #bible-nav-history-list that it puts on
               #bible-nav-list. A <button> inherits none of these by
               default, so each is asked for explicitly - same four as
               .bible-nav-book-btn, so the two lists match.
               (Was calc(1.3rem * var(--ui-scale)), which followed App Size
               rather than the passage like the book list it sits beside.) */
            font-size: inherit;
            font-family: var(--passage-font-family, inherit);
            font-weight: inherit;
            cursor: pointer;
        }
        .history-entry:hover, .history-entry:focus-visible {
            background-color: var(--bg-hover);
        }
        .bible-nav-history-empty {
            color: var(--text-muted);
            font-style: italic;
            margin: 0;
        }

        /* Favorite Highlight Colors modal - grid of every color as a
           checkbox-like swatch (role="checkbox"/aria-checked, since up to 4
           can be selected at once - not a single-choice picker like the
           main swatch row). Same swatch/outline/checkmark styling as the
           main picker (.highlight-swatch), reused via that class. */
        .favorite-colors-grid {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 14px;
            margin: 16px 0;
        }
        .favorite-colors-status {
            font-weight: 700;
            margin: 0;
        }

        /* How to Use content - matches Access-a-Lyrics' .shortcuts-list/kbd
           styling (border-separated rows, monospace key badges) instead of
           the dead Tailwind spacing utilities it used to lean on. */
        #how-to-use-content {
            /* Inherits body's 19px base now rather than its own separately-
               tuned (and slightly smaller) value. */
            line-height: 1.6;
        }
        #how-to-use-content > * + * {
            margin-top: 2rem;
            padding-top: 1.5rem;
            border-top: 2px solid var(--divider-color);
        }
        #how-to-use-content h3 {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            padding-bottom: 0.5rem;
            border-bottom: 2px solid var(--accent-primary);
            margin-bottom: 1rem;
        }
        #how-to-use-content ul {
            padding-left: 1.25rem;
            margin: 0;
        }
        #how-to-use-content ul li {
            margin-bottom: 0.75rem;
        }

        /* Inline colon-labels inside Voice Command/list items (e.g.
           "Navigation:") and the Welcome modal's "Important Note!" heading -
           a purpose-named replacement for the old Tailwind font-bold. */
        .emphasis-label {
            font-weight: 700;
        }

        /* Copyright modal section headings - was font-bold + inline style. */
        .copyright-section-heading {
            font-weight: 700;
            color: var(--accent-primary);
            border-bottom: 2px solid var(--accent-primary);
            display: inline-block;
            margin-bottom: 8px;
        }

        #kb-shortcut-list {
            list-style: none;
            padding-left: 0;
        }
        #kb-shortcut-list li {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 8px;
            padding: 0.75em 0;
            border-bottom: 2px solid var(--divider-color);
        }
        #kb-shortcut-list li:last-child { border-bottom: none; }
        #kb-shortcut-list .kb-key {
            background-color: var(--bg-input);
            border: 2px solid var(--accent-focus);
            color: var(--text-main);
            padding: 0.1em 0.5em;
            border-radius: 4px;
            font-family: monospace;
            font-size: 0.9em;
        }
        #kb-shortcut-list .kb-shortcut-desc {
            margin-left: 0.5rem;
        }

        /* Footer copyright link - was w-full text-center p-4 mb-24 text-xl
           font-medium italic (Tailwind). */
        #copyright-footer {
            width: 100%;
            text-align: center;
            padding: 1rem;
            margin-bottom: 2rem;
        }
        #copyright {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 500;
            font-style: italic;
        }

        /* Copyright modal - same positioning rules as the two dialogs above. */
        #copyright-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
            z-index: 60;
        }
        #copyright-modal:not([hidden]) {
            position: fixed;
            inset: 0;
            z-index: 61;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }

        /* Welcome + Fallback (Voice Input) modals - these two still use the
           older class="hidden"/"flex" toggle + trapFocus() (app.js), not the
           hidden-attribute + inert pattern the three dialogs above use, so
           position/inset/background/centering are set unconditionally here
           rather than gated on :not([hidden]) - only `display` itself comes
           from the .hidden/.flex classes (see the safety-net rules for
           those near the top of this file). Without this, removing Tailwind
           left these rendering inline in the page instead of as an overlay -
           the actual bug behind "the welcome message is visible on the page". */
        #welcome-modal, #fallback-modal {
            position: fixed;
            inset: 0;
            z-index: 61;
            background: rgba(0, 0, 0, 0.85);
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        #welcome-content, #fallback-modal-content {
            position: relative;
            background-color: var(--bg-card);
            border: 2px solid var(--divider-color);
            border-radius: 12px;
            padding: 24px;
            width: 100%;
            max-width: 32rem;
            max-height: 80vh;
            overflow-y: auto;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
            color: var(--text-main);
        }
        /* Welcome message's body copy inherits body's 19px base now (was
           its own separately-tuned, slightly smaller value). */
        #welcome-content > * + *,
        #fallback-modal-content > * + * {
            margin-top: 1.5rem;
        }
        #welcome-content ul { padding-left: 1.25rem; }
        #welcome-content h3 {
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 700;
            border-bottom: 2px solid var(--accent-primary);
            padding-bottom: 0.5rem;
            margin-bottom: 0.75rem;
        }
        /* Same size as .app-menu-close-btn (1.8em box, 1.375rem font),
           per explicit request - was a bigger, differently-proportioned
           one-off (1.9rem font, sized by padding rather than an explicit
           box). Position (absolute, top-right) is unrelated to size and
           stays as its own thing - Welcome doesn't share the standard
           .app-menu-header row every other modal's close button sits in. */
        #welcome-close-btn {
            position: absolute;
            top: 1rem;
            right: 1rem;
            width: 1.8em;
            height: 1.8em;
            background: transparent;
            border: none;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: bold;
            line-height: 1;
            cursor: pointer;
            color: var(--text-main);
        }
        #welcome-modal-title {
            text-align: center;
            font-size: calc(1.65rem * var(--ui-scale));
            font-weight: 800;
            color: var(--accent-focus);
            margin: 2.5rem 0 0;
        }
        .welcome-important-note {
            border: 2px solid var(--accent-secondary);
            border-radius: 8px;
            padding: 16px;
            background-color: var(--bg-body);
        }
        .welcome-important-note ol { padding-left: 1.25rem; margin: 8px 0 0; }
        .welcome-important-note li { margin-bottom: 8px; }
        #welcome-verse {
            font-style: italic;
            border-left: 4px solid var(--accent-primary);
            padding-left: 16px;
            margin-left: 16px;
        }
        #fallback-input-row { display: flex; gap: 8px; }
        #fallback-input-row input { flex-grow: 1; }
        #fallback-cancel-btn {
            display: block;
            width: 100%;
            text-align: center;
            background: transparent;
            border: none;
            color: var(--text-muted);
            text-decoration: underline;
            cursor: pointer;
            font-size: calc(1.3rem * var(--ui-scale));
            font-family: inherit;
        }
        #fallback-cancel-btn:hover, #fallback-cancel-btn:focus-visible { color: var(--text-main); }

        /* Yellow-accent button variant - light yellow tint background
           (15% accent-focus blended into --bg-card), matches Access-a-
           Lyrics' .accent-yellow ("+ New Playlist") button's border/tint
           treatment. Text/icon color used to be a flat white "always" -
           correct in dark mode, where --bg-card is near-black so the tint
           stays dark enough for white to read - but --bg-card is pure
           white in light mode, so the same 15% tint stays almost entirely
           white too, making white-on-white text/icon nearly invisible
           there. var(--text-main) tracks the same base --bg-card the tint
           itself is blended from, so it stays readable in both themes. */
        .btn-accent-yellow {
            border-color: var(--accent-focus) !important;
            color: var(--text-main) !important;
            background-color: color-mix(in srgb, var(--accent-focus) 15%, var(--bg-card));
        }
        .btn-accent-yellow:hover, .btn-accent-yellow:focus-visible {
            background-color: color-mix(in srgb, var(--accent-focus) 28%, var(--bg-card));
        }
        .btn-accent-yellow svg {
            color: var(--text-main) !important;
            stroke: var(--text-main) !important;
        }
        /* "+ Add Note" - same tinted-background treatment as
           .btn-accent-yellow above, green instead - same light-mode
           white-on-white fix applied too. */
        .btn-accent-green {
            border-color: var(--accent-secondary) !important;
            color: var(--text-main) !important;
            background-color: color-mix(in srgb, var(--accent-secondary) 15%, var(--bg-card));
        }
        .btn-accent-green:hover, .btn-accent-green:focus-visible {
            background-color: color-mix(in srgb, var(--accent-secondary) 28%, var(--bg-card));
        }
        /* "Listen to Welcome Message" - icon centered on top, label below,
           both centered, same yellow/white/tint treatment as above. */
        #welcome-read-btn {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 8px;
            width: 100%;
            margin: 24px 0;
            padding: 20px;
            border-width: 2px;
            border-style: solid;
            border-radius: 12px;
            font-weight: 700;
            font-size: calc(1.3rem * var(--ui-scale));
            font-family: inherit;
            cursor: pointer;
        }

        /* ================= My Notebook =================
           Journal + Prayer (notebook.js). Everything sizes off
           rem * --ui-scale like the rest of the app - nothing here is a
           fixed pixel size, and chips are the same size as body text. */

        /* ---- Placement ----
           Every dialog in this file is placed by its own ID rule (there is
           no shared modal class), so without these nine the notebook's
           dialogs un-hid as ordinary blocks at the foot of the page,
           fifteen thousand pixels below the toolbar that opened them.
           Tiers follow who opens whom, same ladder as the rest of the
           file:
             60/61  the notebook       - from the toolbar, a peer of My
                                         Verses and Resources
             62/63  entry view, prayer view, filter - from a card or button
                                         inside the notebook
             64/65  entry form, prayer form, mark answered - Edit closes the
                                         view before opening the form, but
                                         the tier is kept so an overlap
                                         still stacks the right way
             66/67  link / verse, text color / highlight - from the
                                         entry form's toolbar
             68/69  delete confirm     - always above whatever asked */
        #notebook-backdrop,
        #nb-view-backdrop, #nb-pview-backdrop, #nb-filter-backdrop,
        #nb-entry-backdrop, #nb-prayer-backdrop, #nb-answer-backdrop,
        #nb-link-backdrop, #nb-palette-backdrop, #nb-props-backdrop, #nb-verse-backdrop,
        #nb-confirm-backdrop, #nb-leave-backdrop {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.7);
        }
        #notebook-dialog:not([hidden]),
        #nb-view-dialog:not([hidden]), #nb-pview-dialog:not([hidden]), #nb-filter-dialog:not([hidden]),
        #nb-entry-dialog:not([hidden]), #nb-prayer-dialog:not([hidden]), #nb-answer-dialog:not([hidden]),
        #nb-link-dialog:not([hidden]), #nb-palette-dialog:not([hidden]), #nb-props-dialog:not([hidden]), #nb-verse-dialog:not([hidden]),
        #nb-confirm-dialog:not([hidden]), #nb-leave-dialog:not([hidden]) {
            position: fixed;
            inset: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1rem;
        }
        #notebook-backdrop { z-index: 60; }
        #notebook-dialog:not([hidden]) { z-index: 61; }
        #nb-view-backdrop, #nb-pview-backdrop, #nb-filter-backdrop { z-index: 62; }
        #nb-view-dialog:not([hidden]), #nb-pview-dialog:not([hidden]), #nb-filter-dialog:not([hidden]) { z-index: 63; }
        #nb-entry-backdrop, #nb-prayer-backdrop, #nb-answer-backdrop { z-index: 64; }
        #nb-entry-dialog:not([hidden]), #nb-prayer-dialog:not([hidden]), #nb-answer-dialog:not([hidden]) { z-index: 65; }
        #nb-link-backdrop, #nb-palette-backdrop, #nb-props-backdrop, #nb-verse-backdrop { z-index: 66; }
        #nb-link-dialog:not([hidden]), #nb-palette-dialog:not([hidden]), #nb-props-dialog:not([hidden]), #nb-verse-dialog:not([hidden]) { z-index: 67; }
        #nb-confirm-backdrop { z-index: 68; }
        #nb-confirm-dialog:not([hidden]) { z-index: 69; }
        /* Unsaved-changes question: above everything of ours. */
        #nb-leave-backdrop { z-index: 70; }
        #nb-leave-dialog:not([hidden]) { z-index: 71; }

        /* ---- Size ----
           The five full-content dialogs (the notebook, both views, both
           forms) take the same footprint as My Verses and Resources: 95%
           of the screen up to 33rem, side padding dropped from the wrapper
           so the percentage is of the screen and not of a padded box (see
           the note on #highlights-notes-dialog). The notebook itself is a
           fixed 90vh, not a cap, for the same reason Resources is - its
           two tabs hold different amounts and the card should not jump as
           you switch; the two views are fixed too, per request. The forms
           cap at 90vh and shrink to fit.
           Filter, Link, Confirm and Answered keep the base card. */
        #notebook-dialog:not([hidden]),
        #nb-view-dialog:not([hidden]), #nb-pview-dialog:not([hidden]),
        #nb-entry-dialog:not([hidden]), #nb-prayer-dialog:not([hidden]) {
            padding-left: 0;
            padding-right: 0;
        }
        #notebook-dialog .app-menu-card,
        #nb-view-dialog .app-menu-card, #nb-pview-dialog .app-menu-card,
        #nb-entry-dialog .app-menu-card, #nb-prayer-dialog .app-menu-card {
            width: 99%;
            max-width: min(33rem, 99vw);
            max-height: 90vh;
        }
        #notebook-dialog .app-menu-card,
        #nb-view-dialog .app-menu-card,
        #nb-pview-dialog .app-menu-card {
            height: 90vh;
        }

        /* ---- hidden means hidden ----
           Everything the notebook shows and hides (the empty-state
           placeholder, the Filter badge, the tag suggestions, the color
           popovers, the tab panels, the link dialog's two fields) is
           toggled with the hidden attribute, and most of them carry an
           author display of their own (flex, grid, inline-grid) - which,
           as noted at .toolbar-btn[hidden], outranks the browser's
           [hidden]{display:none}. One scoped rule so none of them need
           remembering individually. The dialogs themselves are excluded:
           they already gate their display on :not([hidden]). */
        #notebook-dialog [hidden], #nb-view-dialog [hidden], #nb-pview-dialog [hidden],
        #nb-filter-dialog [hidden], #nb-entry-dialog [hidden], #nb-prayer-dialog [hidden],
        #nb-answer-dialog [hidden], #nb-link-dialog [hidden], #nb-palette-dialog [hidden],
        #nb-props-dialog [hidden], #nb-leave-dialog [hidden], #nb-verse-dialog [hidden], #nb-confirm-dialog [hidden] {
            display: none !important;
        }

        /* Search and Filter share a line. */
        .nb-search-row {
            display: flex;
            gap: 8px;
            align-items: stretch;
            margin: 14px 0 10px;
        }
        .nb-search-row .nb-search {
            flex: 1 1 auto;
            min-width: 0;
            margin: 0;
        }
        .nb-filter-btn {
            flex: 0 0 auto;
            display: inline-flex;
            align-items: center;
            gap: 6px;
            white-space: nowrap;
        }
        .nb-filter-count {
            display: inline-grid;
            place-items: center;
            min-width: 1.5em;
            height: 1.5em;
            padding: 0 0.4em;
            border-radius: 999px;
            background-color: var(--accent-primary);
            color: var(--bg-body);
            font-size: 0.85em;
            font-weight: 800;
        }
        .nb-count {
            margin: 0 0 12px;
        }
        /* + Entry / + New, below the pinned search row, not part of it. */
        .nb-new-btn { margin: 4px 0 12px; }

        /* Segmented control - Open/Answered/All, Request/Praise. Same
           box as .theme-toggle-group so it reads as the Dark/Light
           switch's sibling. */
        .nb-seg {
            display: flex;
            gap: 4px;
            border: 2px solid var(--divider-color);
            border-radius: 8px;
            padding: 3px;
            width: fit-content;
            max-width: 100%;
            margin: 0 0 12px;
        }
        .nb-seg-btn {
            background-color: transparent;
            color: var(--text-main);
            border: none;
            border-radius: 6px;
            padding: 6px 14px;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: bold;
            cursor: pointer;
            font-family: inherit;
        }
        .nb-seg-btn[aria-checked="true"] {
            background-color: var(--accent-primary);
            color: var(--bg-body);
        }
        .nb-seg-btn:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }

        /* Lists group by time once they get long (see nbTimeGroups() in
           notebook.js) - a heading per group, older ones start folded. */
        .nb-list {
            display: flex;
            flex-direction: column;
            gap: 14px;
        }
        .nb-group {
            display: flex;
            flex-direction: column;
            gap: 14px;
        }
        .nb-group-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 10px;
            width: 100%;
            padding: 8px 0;
            background: none;
            border: none;
            border-bottom: 2px solid var(--divider-color);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            color: var(--accent-secondary);
            text-align: left;
            cursor: pointer;
        }
        .nb-group-head .nb-group-n {
            color: var(--text-secondary);
            font-weight: 600;
        }
        .nb-group-head::after {
            content: "\25BE";
            color: var(--text-secondary);
            transition: transform 0.18s ease;
        }
        .nb-group-head[aria-expanded="false"]::after {
            transform: rotate(-90deg);
        }
        @media (prefers-reduced-motion: reduce) {
            .nb-group-head::after { transition: none; }
        }
        .nb-group-head:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }

        /* The card. Same bones as .hn-note-item: a bold first line, a
           two-line preview, dates. The whole card is the button. */
        .nb-card {
            display: flex;
            flex-direction: column;
            gap: 6px;
            width: 100%;
            padding: 12px 14px;
            border: 2px solid var(--divider-color);
            border-radius: 10px;
            /* No fill of its own (was --bg-expanded) per explicit request
               - only an entry with a chosen background gets colour. */
            background-color: transparent;
            color: var(--text-main);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            text-align: left;
            cursor: pointer;
        }
        .nb-card:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        .nb-card-title {
            font-weight: 800;
            /* Same size as a modal's own title (.app-menu-heading), per
               explicit request. */
            font-size: calc(1.55rem * var(--ui-scale));
            overflow-wrap: anywhere;
        }
        /* Four lines of preview (was two), per explicit request. A
           prayer's text keeps its line breaks and gets Show More /
           Show Less under it (the verse-note control) when it overflows
           - an entry has its own view for the rest. */
        .nb-card-text {
            color: var(--text-main);
            /* A little air above and below the preview, on top of the
               card's own 6px gap, per request. */
            margin: 4px 0;
            display: -webkit-box;
            -webkit-line-clamp: 4;
            -webkit-box-orient: vertical;
            overflow: hidden;
        }
        .nb-card-text-prayer { white-space: pre-line; overflow-wrap: anywhere; }
        .nb-card-text.expanded {
            display: block;
            -webkit-line-clamp: unset;
            overflow: visible;
        }
        .nb-card .verse-note-more-toggle {
            /* Pulled up under the text (the text's 4px + the card's 6px
               gap would otherwise sit between them), and the same air
               below it as above the preview. */
            margin: -8px 0 4px;
            padding: 2px 0;
            align-self: flex-start;
        }
        .nb-card-pin {
            font-size: 0.85em;
            color: var(--text-secondary);
            font-weight: 600;
        }
        .nb-dates {
            margin: 0;
            font-size: calc(1.15rem * var(--ui-scale));
            color: var(--text-secondary);
            font-variant-numeric: tabular-nums;
        }
        /* Prayer: kind is the left edge, state is a chip. */
        .nb-card.request { border-left: 5px solid var(--accent-primary); }
        .nb-card.praise  { border-left: 5px solid var(--accent-focus); }
        .nb-card-row {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 8px;
        }
        .nb-kind {
            font-size: 0.8em;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            font-weight: 800;
        }
        .request .nb-kind { color: var(--accent-primary); }
        .praise .nb-kind  { color: var(--accent-focus); }
        .nb-state {
            font-size: 0.85em;
            font-weight: 800;
            padding: 2px 9px;
            border-radius: 999px;
            border: 1.5px solid var(--accent-secondary);
            color: var(--accent-secondary);
            background-color: rgb(from var(--accent-secondary) r g b / 0.12);
        }
        /* The latest update (or the answer), up to four lines. Extra
           room below it before the date line, per request. */
        .nb-card-update {
            border-top: 1px solid var(--divider-color);
            padding-top: 6px;
            margin-bottom: 8px;
            color: var(--text-secondary);
            display: -webkit-box;
            -webkit-line-clamp: 4;
            -webkit-box-orient: vertical;
            overflow: hidden;
            white-space: pre-line;
            overflow-wrap: anywhere;
        }
        .nb-card-update b { color: var(--text-main); font-weight: 600; }

        /* Chips - tags, people, verses. Body-size text, per explicit
           request; only the outline says which kind. */
        .nb-chips {
            display: flex;
            flex-wrap: wrap;
            gap: 6px;
        }
        .nb-chip {
            display: inline-flex;
            align-items: center;
            gap: 0.35em;
            padding: 3px 11px;
            border-radius: 999px;
            border: 1.5px solid var(--divider-color);
            background: none;
            color: var(--text-main);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 600;
            line-height: 1.3;
        }
        .nb-chip.person { border-color: var(--accent-secondary); }
        .nb-chip.person::before {
            content: "";
            display: inline-block;
            width: 0.55em;
            height: 0.55em;
            border-radius: 50%;
            background-color: var(--accent-secondary);
        }
        .nb-chip.verse { border-color: var(--accent-primary); cursor: pointer; }
        .nb-chip.verse:focus-visible, .nb-chip-x:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        /* In an editor the chip carries its own remove button. */
        .nb-chip-x {
            background: none;
            border: none;
            padding: 0 0 0 2px;
            font: inherit;
            font-size: 1.1em;
            line-height: 1;
            color: var(--text-secondary);
            cursor: pointer;
        }
        .nb-chip-x:hover { color: var(--text-main); }

        /* View mode. Title in the body, wrapping freely. */
        .nb-view-body { display: flex; flex-direction: column; gap: 12px; }
        .nb-view-title {
            margin: 0;
            font-size: calc(1.6rem * var(--ui-scale));
            font-weight: 800;
            line-height: 1.25;
            overflow-wrap: anywhere;
            color: var(--text-main);
        }
        /* The Back button above the title: the body is a flex column, so
           without this it would stretch to the full card width. */
        .nb-view-back { align-self: flex-start; }
        .nb-content { border: none; }
        /* Pin / Edit / Delete (and the prayer view's Mark answered /
           Edit / Delete) live in a footer below the scrolling body, so
           they are always in reach however long the entry is. */
        .nb-footer {
            flex-shrink: 0;
            padding: 10px 10px calc(10px + env(safe-area-inset-bottom, 0px));
            border-top: 2px solid var(--divider-color);
            background-color: var(--bg-body);
        }
        .nb-btn-row {
            display: flex;
            gap: 8px;
            margin-top: 6px;
        }
        .nb-footer .nb-btn-row { margin-top: 0; }
        .nb-btn-row .app-btn { flex: 1 1 auto; }
        .nb-btn-row .nb-icon-btn {
            flex: 0 0 auto;
            display: inline-grid;
            place-items: center;
            min-width: calc(3rem * var(--ui-scale));
        }
        .nb-icon-btn svg {
            width: calc(1.5rem * var(--ui-scale));
            height: calc(1.5rem * var(--ui-scale));
        }
        .nb-state-line {
            margin: 0;
            color: var(--accent-secondary);
            font-weight: 700;
        }
        .nb-prayer-text {
            white-space: pre-wrap;
            overflow-wrap: anywhere;
        }
        /* Each update in a prayer's view: green rule on the left (was
           the divider grey), more room between them and inside each,
           per request. */
        .nb-updates { display: flex; flex-direction: column; gap: 16px; margin: 4px 0; }
        .nb-update {
            border-left: 3px solid var(--accent-secondary);
            padding: 6px 0 6px 12px;
            white-space: pre-wrap;
            overflow-wrap: anywhere;
        }
        .nb-update b {
            display: block;
            color: var(--text-secondary);
            font-weight: 600;
            font-size: calc(1.15rem * var(--ui-scale));
            margin-bottom: 2px;
        }
        .nb-add-update { display: flex; flex-direction: column; gap: 8px; }
        /* Editing updates in the prayer form - same green rule as the
           view, a date and a remove control over each field. */
        .nb-updates-edit { display: flex; flex-direction: column; gap: 12px; }
        .nb-update-edit {
            display: flex;
            flex-direction: column;
            gap: 4px;
            border-left: 3px solid var(--accent-secondary);
            padding: 2px 0 2px 12px;
        }
        .nb-update-edit-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
            color: var(--text-secondary);
            font-size: calc(1.15rem * var(--ui-scale));
            font-weight: 600;
        }
        .nb-update-x { font-size: calc(1.5rem * var(--ui-scale)); padding: 0 6px; }
        .nb-update-edit .nb-textarea { min-height: 5.5em; }

        /* Editors. */
        .nb-field { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
        .nb-field-label { display: block; }
        .nb-input {
            width: 100%;
            box-sizing: border-box;
            padding: 9px 12px;
            border: 2px solid var(--divider-color);
            border-radius: 8px;
            background-color: var(--bg-expanded);
            color: var(--text-main);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .nb-input:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 1px;
        }
        .nb-textarea { resize: vertical; min-height: 3.5em; }

        /* Details on a prayer - taller (was four rows), per request. */
        .nb-prayer-details { min-height: 9.5em; }
        /* The chosen chips sit between the label and the box; when there
           are none the row takes no room, so the gaps do not double. */
        .nb-chips-edit:empty { display: none; }
        /* Tag / person picker - one box with one border: the find-or-add
           field on top (its chevron borderless, in the third accent),
           a hairline, then "Saved tags" and the chips. */
        .nb-tagbox {
            display: flex;
            flex-direction: column;
            gap: 8px;
            /* Doubles the field's 8px gap above the box, per request. */
            margin-top: 8px;
            padding: 4px 10px 10px;
            border: 2px solid var(--divider-color);
            border-radius: 8px;
            background-color: var(--bg-expanded);
        }
        .nb-tagbox-search {
            display: flex;
            align-items: center;
            padding: 4px 0 2px;
        }
        /* A full border of its own, per explicit request (was borderless
           with a hairline under it). */
        .nb-tag-input {
            flex: 1 1 auto;
            min-width: 0;
            padding: 8px 10px;
            border: 2px solid var(--divider-color);
            border-radius: 8px;
            background-color: var(--bg-body);
            color: var(--text-main);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
        }
        .nb-tag-input::placeholder { color: var(--text-muted); }
        .nb-tag-input:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 1px;
        }
        .nb-tagbox-label {
            font-size: calc(1.1rem * var(--ui-scale));
            letter-spacing: 0.08em;
            text-transform: uppercase;
            font-weight: 800;
            color: var(--accent-secondary);
        }
        /* Show More / Show Less, inline after the last chip. */
        .nb-suggest .nb-tag-showmore {
            margin: 0;
            padding: 0 4px;
            align-self: center;
        }
        .nb-suggest {
            display: flex;
            flex-wrap: wrap;
            gap: 6px;
        }
        /* Saved-but-not-added chips sit back in the secondary text tone
           so the added ones above (full text colour) read as the
           active set. */
        .nb-suggest .nb-chip.saved {
            color: var(--text-secondary);
            cursor: pointer;
        }
        .nb-suggest .nb-chip.saved:hover, .nb-suggest .nb-chip.saved:focus-visible {
            color: var(--text-main);
            border-color: var(--accent-primary);
        }
        .nb-suggest .nb-chip.is-new { border-style: dashed; cursor: pointer; }
        .nb-suggest .nb-chip.edit {
            border-color: var(--accent-focus);
            color: var(--accent-focus);
            cursor: pointer;
        }
        .nb-suggest .nb-chip:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        .nb-suggest-note {
            width: 100%;
            margin: 0;
            font-size: calc(1.15rem * var(--ui-scale));
            color: var(--text-secondary);
        }

        /* Toolbar, popovers and editor stack with no gaps: the toolbar's
           bottom border IS the editor's top border. */
        .nb-editor-stack { display: flex; flex-direction: column; }
        /* Same arrangement as the pinned tab heads: a sticky element pins
           to the body's CONTENT edge, so the body's own top padding would
           be a strip the entry scrolls through above the pinned toolbar.
           The padding moves onto the first block instead. */
        #nb-entry-dialog .app-menu-body { padding-top: 0; }
        #nb-entry-dialog .app-menu-body > :first-child { margin-top: 20px; }
        /* ...and the Title block's own Settings-style top padding would
           stack on that (36px), so it goes. */
        #nb-entry-dialog .app-menu-body > .settings-group-fields:first-child { padding-top: 0; }
        /* The formatting toolbar. Touch-scrolls sideways on a phone with
           no scrollbar; wraps onto more rows on a wide screen. Pinned to
           the top of the scrolling body while the editor is in view, so
           it never has to be scrolled back to. */
        /* The editor card's footer (see the markup): a plain strip at the
           bottom of the card, above the phone's keyboard by construction. */
        .nb-toolbar-wrap {
            flex-shrink: 0;
            border-top: 2px solid var(--divider-color);
            background-color: var(--bg-expanded);
            padding-bottom: env(safe-area-inset-bottom, 0px);
        }
        .nb-toolbar {
            display: flex;
            gap: 4px;
            /* Top/bottom doubled from 6px, per request: the phone's own
               Cut / Copy bar (which sits just above a selection) then
               has somewhere to land besides the buttons. */
            padding: 12px 6px;
            overflow-x: auto;
            scrollbar-width: none;
            -webkit-overflow-scrolling: touch;
        }
        /* Phones: the editor card fills the screen edge to edge, so the
           footer toolbar meets the keyboard with nothing in between. */
        @media (max-width: 639px) {
            #nb-entry-dialog:not([hidden]) { padding: 0; }
            #nb-entry-dialog .app-menu-card {
                width: 100%;
                max-width: none;
                height: 100%;
                max-height: none;
                border: none;
                border-radius: 0;
            }
        }
        .nb-toolbar::-webkit-scrollbar { display: none; }
        @media (min-width: 640px) {
            .nb-toolbar { flex-wrap: wrap; overflow-x: visible; }
        }
        .nb-tb-group {
            display: flex;
            gap: 4px;
            flex: 0 0 auto;
            padding-right: 6px;
            border-right: 1px solid var(--divider-color);
        }
        .nb-tb-group:last-child { border-right: 0; padding-right: 0; }
        /* Every button the same box, whether it holds letters or an icon,
           so A and the highlighter are no smaller than H1. */
        .nb-tb-btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 0.3em;
            min-width: calc(2.6rem * var(--ui-scale));
            height: calc(2.6rem * var(--ui-scale));
            padding: 0 8px;
            border: 1.5px solid transparent;
            border-radius: 6px;
            background: none;
            color: var(--text-main);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            font-weight: 800;
            cursor: pointer;
            white-space: nowrap;
        }
        .nb-tb-icon {
            width: calc(1.5rem * var(--ui-scale));
            height: calc(1.5rem * var(--ui-scale));
        }
        /* Image / Verse / Link carry a word next to the icon. */
        .nb-tb-word { font-size: calc(1.15rem * var(--ui-scale)); font-weight: 700; }
        /* Quill marks the active format with .ql-active; the custom
           buttons set aria-pressed themselves. Same look for both. */
        .nb-tb-btn.ql-active,
        .nb-tb-btn[aria-pressed="true"],
        .nb-tb-btn[aria-expanded="true"] {
            border-color: var(--accent-primary);
            color: var(--accent-primary);
        }
        .nb-tb-btn:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 1px;
        }

        /* Color / Highlight popovers, and the Background row. */
        .nb-palette-card { max-width: min(19rem, 92vw); }
        /* Highlight previews: the tint (translucent) over the editor's
           real ground, both set per swatch by openNotebookPalette() -
           outranks the swatch's own .ql-bg-* background. */
        #nb-highlight-pop .nb-swatch[data-value] {
            background-color: transparent;
            background-image: linear-gradient(var(--nb-sw-tint), var(--nb-sw-tint)),
                              linear-gradient(var(--nb-sw-ground), var(--nb-sw-ground));
        }
        #nb-palette-dialog .app-menu-body { padding: 14px; }
        .nb-pop .nb-swatches { gap: 10px; }
        .nb-swatches {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
        }
        .nb-swatch {
            width: calc(2.2rem * var(--ui-scale));
            height: calc(2.2rem * var(--ui-scale));
            border-radius: 50%;
            border: 2px solid var(--divider-color);
            padding: 0;
            cursor: pointer;
            display: grid;
            place-items: center;
            font-family: inherit;
            /* The "A" previews at body text's own weight (was 800): bold
               light strokes read brighter than the regular text that
               actually lands, so the preview flattered. A touch larger
               to stay legible in the ring. */
            font-size: calc(1.45rem * var(--ui-scale));
            font-weight: 400;
            color: var(--text-main);
            background-color: var(--bg-expanded);
            position: relative;
            overflow: hidden;
        }
        /* "None": a diagonal slash in the ring's own colour, per explicit
           request. */
        .nb-swatch.none::after {
            content: "";
            position: absolute;
            left: 50%;
            top: -20%;
            width: 2px;
            height: 140%;
            background-color: var(--divider-color);
            transform: rotate(45deg);
            transform-origin: center;
        }
        .nb-swatch[aria-checked="true"], .nb-swatch[aria-pressed="true"] {
            outline: 3px solid var(--accent-primary);
            outline-offset: 2px;
        }
        .nb-swatch:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }

        /* The editor. Starts tall enough for a few paragraphs, grows with
           what you write up to three-quarters of the screen, then scrolls
           inside itself so Tags and Save stay reachable (per explicit
           request). nbKeepCaretVisible() scrolls this box and then the
           dialog body to keep the caret on screen. Written as
           .nb-editor.ql-container, one element: Quill puts ql-container ON
           #nb-editor, so the old ".nb-editor .ql-container" descendant
           rule never matched and its cap silently never applied. */
        .nb-editor {
            border: 2px solid var(--divider-color);
            border-top: 0;
            border-radius: 0 0 8px 8px;
            background-color: var(--bg-expanded);
        }
        .nb-editor.ql-container {
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            min-height: calc(12rem * var(--ui-scale));
        }
        /* Quill's own stylesheet makes the inner element the scroller
           (height 100%, overflow auto); the outer box is the one here. */
        .nb-editor .ql-editor {
            height: auto;
            overflow: visible;
            min-height: calc(12rem * var(--ui-scale));
            /* Top/bottom up from 12px, per request - same reason as the
               toolbar's padding above. */
            padding: 26px 14px;
            color: var(--text-main);
            line-height: 1.5;
        }
        .nb-editor .ql-editor.ql-blank::before {
            color: var(--text-muted);
            font-style: normal;
            left: 14px;
            right: 14px;
        }
        /* Rendered content - view mode and the editor alike. Quill's own
           stylesheet loads after this one and zeroes every margin under
           .ql-editor at (0,1,1), so these are written at (0,2,1) - the
           .nb-content prefix on the view side is what wins, not
           decoration. Paragraphs get real space between them (Enter
           reads as a paragraph break, per explicit request). */
        .nb-content .nb-rendered, .nb-editor .ql-editor {
            font-size: calc(1.3rem * var(--ui-scale));
            line-height: 1.5;
        }
        .nb-content .nb-rendered { padding: 0; }
        .nb-content .nb-rendered h1, .nb-editor .ql-editor h1 { font-size: calc(1.8rem * var(--ui-scale)); margin: 0.8em 0 0.35em; }
        .nb-content .nb-rendered h2, .nb-editor .ql-editor h2 { font-size: calc(1.55rem * var(--ui-scale)); margin: 0.8em 0 0.35em; }
        .nb-content .nb-rendered h3, .nb-editor .ql-editor h3 { font-size: calc(1.4rem * var(--ui-scale)); margin: 0.8em 0 0.35em; }
        .nb-content .nb-rendered p, .nb-editor .ql-editor p { margin: 0 0 0.9em; }
        .nb-content .nb-rendered ol, .nb-editor .ql-editor ol,
        .nb-content .nb-rendered ul, .nb-editor .ql-editor ul { margin: 0 0 0.9em; }
        /* Lists. Quill indents a list 3em before the first letter (1.5em
           on the list, 1.5em on each item) - too much, per explicit
           request - so the list's own share goes; the item keeps room
           for its marker. A sub-item (Indent) steps in 1.5em more, not
           Quill's 3em. Quill numbers a sub-item a. b. c. by itself. The
           :not() is not decorative: it lifts these above Quill's own
           equally-specific rules, which load after this sheet. */
        .nb-editor .ql-editor ol { padding-left: 0.2em; }
        .nb-editor .ql-editor li.ql-indent-1:not(.ql-direction-rtl) { padding-left: 3em; }
        /* The saved entry is plain <ul>/<ol> (nested for sub-items), with
           none of Quill's marker spans - so the view draws real markers
           where Quill's stylesheet had switched them off. */
        .nb-content .nb-rendered ul, .nb-content .nb-rendered ol { padding-left: 1.6em; }
        .nb-content .nb-rendered li { list-style-type: inherit; padding-left: 0.2em; }
        .nb-content .nb-rendered ul { list-style-type: disc; }
        .nb-content .nb-rendered ol { list-style-type: decimal; }
        .nb-content .nb-rendered ul ul { list-style-type: circle; margin-bottom: 0; }
        .nb-content .nb-rendered ol ol { list-style-type: lower-alpha; margin-bottom: 0; }
        /* A numbered item that starts at N (typed "N. " - see the Space
           binding in notebook.js): the item sets the counter to N, and
           the ones after it count on from there. counter-set is what
           makes "2." show as 2 rather than 1. The view side needs
           nothing: the saved entry is a real <ol start="N">. */
        .nb-editor .ql-editor li.ql-start-2 { counter-set: list-0 2; }
        .nb-editor .ql-editor li.ql-start-3 { counter-set: list-0 3; }
        .nb-editor .ql-editor li.ql-start-4 { counter-set: list-0 4; }
        .nb-editor .ql-editor li.ql-start-5 { counter-set: list-0 5; }
        .nb-editor .ql-editor li.ql-start-6 { counter-set: list-0 6; }
        .nb-editor .ql-editor li.ql-start-7 { counter-set: list-0 7; }
        .nb-editor .ql-editor li.ql-start-8 { counter-set: list-0 8; }
        .nb-editor .ql-editor li.ql-start-9 { counter-set: list-0 9; }
        .nb-editor .ql-editor li.ql-start-10 { counter-set: list-0 10; }
        .nb-editor .ql-editor li.ql-start-11 { counter-set: list-0 11; }
        .nb-editor .ql-editor li.ql-start-12 { counter-set: list-0 12; }
        .nb-editor .ql-editor li.ql-start-13 { counter-set: list-0 13; }
        .nb-editor .ql-editor li.ql-start-14 { counter-set: list-0 14; }
        .nb-editor .ql-editor li.ql-start-15 { counter-set: list-0 15; }
        .nb-editor .ql-editor li.ql-start-16 { counter-set: list-0 16; }
        .nb-editor .ql-editor li.ql-start-17 { counter-set: list-0 17; }
        .nb-editor .ql-editor li.ql-start-18 { counter-set: list-0 18; }
        .nb-editor .ql-editor li.ql-start-19 { counter-set: list-0 19; }
        .nb-editor .ql-editor li.ql-start-20 { counter-set: list-0 20; }
        .nb-editor .ql-editor li.ql-start-21 { counter-set: list-0 21; }
        .nb-editor .ql-editor li.ql-start-22 { counter-set: list-0 22; }
        .nb-editor .ql-editor li.ql-start-23 { counter-set: list-0 23; }
        .nb-editor .ql-editor li.ql-start-24 { counter-set: list-0 24; }
        .nb-editor .ql-editor li.ql-start-25 { counter-set: list-0 25; }
        .nb-editor .ql-editor li.ql-start-26 { counter-set: list-0 26; }
        .nb-editor .ql-editor li.ql-start-27 { counter-set: list-0 27; }
        .nb-editor .ql-editor li.ql-start-28 { counter-set: list-0 28; }
        .nb-editor .ql-editor li.ql-start-29 { counter-set: list-0 29; }
        .nb-editor .ql-editor li.ql-start-30 { counter-set: list-0 30; }
        .nb-editor .ql-editor li.ql-start-31 { counter-set: list-0 31; }
        .nb-editor .ql-editor li.ql-start-32 { counter-set: list-0 32; }
        .nb-editor .ql-editor li.ql-start-33 { counter-set: list-0 33; }
        .nb-editor .ql-editor li.ql-start-34 { counter-set: list-0 34; }
        .nb-editor .ql-editor li.ql-start-35 { counter-set: list-0 35; }
        .nb-editor .ql-editor li.ql-start-36 { counter-set: list-0 36; }
        .nb-editor .ql-editor li.ql-start-37 { counter-set: list-0 37; }
        .nb-editor .ql-editor li.ql-start-38 { counter-set: list-0 38; }
        .nb-editor .ql-editor li.ql-start-39 { counter-set: list-0 39; }
        .nb-editor .ql-editor li.ql-start-40 { counter-set: list-0 40; }
        .nb-editor .ql-editor li.ql-start-41 { counter-set: list-0 41; }
        .nb-editor .ql-editor li.ql-start-42 { counter-set: list-0 42; }
        .nb-editor .ql-editor li.ql-start-43 { counter-set: list-0 43; }
        .nb-editor .ql-editor li.ql-start-44 { counter-set: list-0 44; }
        .nb-editor .ql-editor li.ql-start-45 { counter-set: list-0 45; }
        .nb-editor .ql-editor li.ql-start-46 { counter-set: list-0 46; }
        .nb-editor .ql-editor li.ql-start-47 { counter-set: list-0 47; }
        .nb-editor .ql-editor li.ql-start-48 { counter-set: list-0 48; }
        .nb-editor .ql-editor li.ql-start-49 { counter-set: list-0 49; }
        .nb-editor .ql-editor li.ql-start-50 { counter-set: list-0 50; }
        .nb-editor .ql-editor li.ql-start-51 { counter-set: list-0 51; }
        .nb-editor .ql-editor li.ql-start-52 { counter-set: list-0 52; }
        .nb-editor .ql-editor li.ql-start-53 { counter-set: list-0 53; }
        .nb-editor .ql-editor li.ql-start-54 { counter-set: list-0 54; }
        .nb-editor .ql-editor li.ql-start-55 { counter-set: list-0 55; }
        .nb-editor .ql-editor li.ql-start-56 { counter-set: list-0 56; }
        .nb-editor .ql-editor li.ql-start-57 { counter-set: list-0 57; }
        .nb-editor .ql-editor li.ql-start-58 { counter-set: list-0 58; }
        .nb-editor .ql-editor li.ql-start-59 { counter-set: list-0 59; }
        .nb-editor .ql-editor li.ql-start-60 { counter-set: list-0 60; }
        .nb-editor .ql-editor li.ql-start-61 { counter-set: list-0 61; }
        .nb-editor .ql-editor li.ql-start-62 { counter-set: list-0 62; }
        .nb-editor .ql-editor li.ql-start-63 { counter-set: list-0 63; }
        .nb-editor .ql-editor li.ql-start-64 { counter-set: list-0 64; }
        .nb-editor .ql-editor li.ql-start-65 { counter-set: list-0 65; }
        .nb-editor .ql-editor li.ql-start-66 { counter-set: list-0 66; }
        .nb-editor .ql-editor li.ql-start-67 { counter-set: list-0 67; }
        .nb-editor .ql-editor li.ql-start-68 { counter-set: list-0 68; }
        .nb-editor .ql-editor li.ql-start-69 { counter-set: list-0 69; }
        .nb-editor .ql-editor li.ql-start-70 { counter-set: list-0 70; }
        .nb-editor .ql-editor li.ql-start-71 { counter-set: list-0 71; }
        .nb-editor .ql-editor li.ql-start-72 { counter-set: list-0 72; }
        .nb-editor .ql-editor li.ql-start-73 { counter-set: list-0 73; }
        .nb-editor .ql-editor li.ql-start-74 { counter-set: list-0 74; }
        .nb-editor .ql-editor li.ql-start-75 { counter-set: list-0 75; }
        .nb-editor .ql-editor li.ql-start-76 { counter-set: list-0 76; }
        .nb-editor .ql-editor li.ql-start-77 { counter-set: list-0 77; }
        .nb-editor .ql-editor li.ql-start-78 { counter-set: list-0 78; }
        .nb-editor .ql-editor li.ql-start-79 { counter-set: list-0 79; }
        .nb-editor .ql-editor li.ql-start-80 { counter-set: list-0 80; }
        .nb-editor .ql-editor li.ql-start-81 { counter-set: list-0 81; }
        .nb-editor .ql-editor li.ql-start-82 { counter-set: list-0 82; }
        .nb-editor .ql-editor li.ql-start-83 { counter-set: list-0 83; }
        .nb-editor .ql-editor li.ql-start-84 { counter-set: list-0 84; }
        .nb-editor .ql-editor li.ql-start-85 { counter-set: list-0 85; }
        .nb-editor .ql-editor li.ql-start-86 { counter-set: list-0 86; }
        .nb-editor .ql-editor li.ql-start-87 { counter-set: list-0 87; }
        .nb-editor .ql-editor li.ql-start-88 { counter-set: list-0 88; }
        .nb-editor .ql-editor li.ql-start-89 { counter-set: list-0 89; }
        .nb-editor .ql-editor li.ql-start-90 { counter-set: list-0 90; }
        .nb-editor .ql-editor li.ql-start-91 { counter-set: list-0 91; }
        .nb-editor .ql-editor li.ql-start-92 { counter-set: list-0 92; }
        .nb-editor .ql-editor li.ql-start-93 { counter-set: list-0 93; }
        .nb-editor .ql-editor li.ql-start-94 { counter-set: list-0 94; }
        .nb-editor .ql-editor li.ql-start-95 { counter-set: list-0 95; }
        .nb-editor .ql-editor li.ql-start-96 { counter-set: list-0 96; }
        .nb-editor .ql-editor li.ql-start-97 { counter-set: list-0 97; }
        .nb-editor .ql-editor li.ql-start-98 { counter-set: list-0 98; }
        .nb-editor .ql-editor li.ql-start-99 { counter-set: list-0 99; }
        /* Quotes: a regular quote in the secondary accent (green), a quoted
           Bible passage (Verse quote, ql-quote-bible) in the primary accent
           (pink) - the same two in the editor and the view. */
        /* The view selector carries .ql-editor on purpose: Quill's theme
           writes ".ql-snow .ql-editor blockquote" (grey rule) at the same
           weight as a plain ".nb-content .nb-rendered blockquote" and
           loads later - the view's quotes came out grey. */
        .nb-content .ql-editor.nb-rendered blockquote, .nb-editor .ql-editor blockquote {
            margin: 0 0 0.9em;
            padding: 4px 0 4px 14px;
            border-left: 4px solid var(--accent-secondary);
        }
        .nb-content .ql-editor.nb-rendered blockquote.ql-quote-bible, .nb-editor .ql-editor blockquote.ql-quote-bible {
            border-left-color: var(--accent-primary);
        }
        /* A quote that runs to another paragraph keeps one continuous rule:
           each paragraph is its own <blockquote>, so the next is pulled
           up over the gap the first leaves. */
        .nb-content .ql-editor.nb-rendered blockquote + blockquote, .nb-editor .ql-editor blockquote + blockquote {
            margin-top: -0.9em;
        }
        .nb-content .nb-rendered > :first-child, .nb-editor .ql-editor > :first-child { margin-top: 0; }
        .nb-content .nb-rendered > :last-child, .nb-editor .ql-editor > :last-child { margin-bottom: 0; }
        .nb-content .nb-rendered img, .nb-editor .ql-editor img {
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            display: block;
            margin: 0.5em 0;
        }
        .nb-content .nb-rendered a, .nb-editor .ql-editor a {
            color: var(--accent-secondary);
            text-decoration: underline;
            text-decoration-thickness: 1.5px;
            text-underline-offset: 2px;
            font-weight: 600;
        }
        /* Verse links are pink; web links green - two kinds of tap. */
        .nb-content .nb-rendered a[href^="#verse:"], .nb-editor .ql-editor a[href^="#verse:"] {
            color: var(--accent-primary);
        }
        .nb-content.ql-snow .ql-editor { padding: 0; }
        /* Quill's own tooltip is replaced by the Link dialog. */
        .nb-editor .ql-tooltip { display: none !important; }

        /* Coloured text. The highlight tokens are translucent tints made
           to sit BEHIND text, so used as ink some of them (dark blue, dark
           purple, brown, gray...) barely cleared the dark ground. Each is
           taken to a readable lightness for its theme in OKLCH, keeping
           its own hue: on dark, lightness at least 0.87 (was 0.8 - a
           bit lighter still, per request); on light, at most 0.47.
           Colours already light or dark enough are left alone.
           "/ 1" drops the token's alpha - ink is never see-through.
           Written at (0,3,0) on purpose: Quill's own stylesheet carries
           ".ql-editor .ql-color-red { #e60000 }" (and yellow, orange,
           green, blue, purple, and .ql-bg-* of the same) at (0,2,0),
           loaded after this sheet - so at any less those six landed as
           Quill's hard-coded hex in the editor and the view while the
           swatches showed ours. */
        .nb-editor .ql-editor .ql-color-yellow, .nb-content .ql-editor .ql-color-yellow, .nb-swatch.ql-color-yellow { color: oklch(from var(--highlight-yellow) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-yellow, :root[data-theme="light"] .nb-content .ql-editor .ql-color-yellow, :root[data-theme="light"] .nb-swatch.ql-color-yellow { color: oklch(from var(--highlight-yellow) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-yellow, .nb-content .ql-editor .ql-bg-yellow, .nb-swatch.ql-bg-yellow { background-color: var(--highlight-yellow); }
        .nb-bg-yellow { background-color: rgb(from var(--highlight-yellow) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-green, .nb-content .ql-editor .ql-color-green, .nb-swatch.ql-color-green { color: oklch(from var(--highlight-green) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-green, :root[data-theme="light"] .nb-content .ql-editor .ql-color-green, :root[data-theme="light"] .nb-swatch.ql-color-green { color: oklch(from var(--highlight-green) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-green, .nb-content .ql-editor .ql-bg-green, .nb-swatch.ql-bg-green { background-color: var(--highlight-green); }
        .nb-bg-green { background-color: rgb(from var(--highlight-green) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-pink, .nb-content .ql-editor .ql-color-pink, .nb-swatch.ql-color-pink { color: oklch(from var(--highlight-pink) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-pink, :root[data-theme="light"] .nb-content .ql-editor .ql-color-pink, :root[data-theme="light"] .nb-swatch.ql-color-pink { color: oklch(from var(--highlight-pink) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-pink, .nb-content .ql-editor .ql-bg-pink, .nb-swatch.ql-bg-pink { background-color: var(--highlight-pink); }
        .nb-bg-pink { background-color: rgb(from var(--highlight-pink) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-blue, .nb-content .ql-editor .ql-color-blue, .nb-swatch.ql-color-blue { color: oklch(from var(--highlight-blue) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-blue, :root[data-theme="light"] .nb-content .ql-editor .ql-color-blue, :root[data-theme="light"] .nb-swatch.ql-color-blue { color: oklch(from var(--highlight-blue) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-blue, .nb-content .ql-editor .ql-bg-blue, .nb-swatch.ql-bg-blue { background-color: var(--highlight-blue); }
        .nb-bg-blue { background-color: rgb(from var(--highlight-blue) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-orange, .nb-content .ql-editor .ql-color-orange, .nb-swatch.ql-color-orange { color: oklch(from var(--highlight-orange) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-orange, :root[data-theme="light"] .nb-content .ql-editor .ql-color-orange, :root[data-theme="light"] .nb-swatch.ql-color-orange { color: oklch(from var(--highlight-orange) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-orange, .nb-content .ql-editor .ql-bg-orange, .nb-swatch.ql-bg-orange { background-color: var(--highlight-orange); }
        .nb-bg-orange { background-color: rgb(from var(--highlight-orange) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-purple, .nb-content .ql-editor .ql-color-purple, .nb-swatch.ql-color-purple { color: oklch(from var(--highlight-purple) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-purple, :root[data-theme="light"] .nb-content .ql-editor .ql-color-purple, :root[data-theme="light"] .nb-swatch.ql-color-purple { color: oklch(from var(--highlight-purple) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-purple, .nb-content .ql-editor .ql-bg-purple, .nb-swatch.ql-bg-purple { background-color: var(--highlight-purple); }
        .nb-bg-purple { background-color: rgb(from var(--highlight-purple) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-red, .nb-content .ql-editor .ql-color-red, .nb-swatch.ql-color-red { color: oklch(from var(--highlight-red) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-red, :root[data-theme="light"] .nb-content .ql-editor .ql-color-red, :root[data-theme="light"] .nb-swatch.ql-color-red { color: oklch(from var(--highlight-red) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-red, .nb-content .ql-editor .ql-bg-red, .nb-swatch.ql-bg-red { background-color: var(--highlight-red); }
        .nb-bg-red { background-color: rgb(from var(--highlight-red) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-teal, .nb-content .ql-editor .ql-color-teal, .nb-swatch.ql-color-teal { color: oklch(from var(--highlight-teal) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-teal, :root[data-theme="light"] .nb-content .ql-editor .ql-color-teal, :root[data-theme="light"] .nb-swatch.ql-color-teal { color: oklch(from var(--highlight-teal) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-teal, .nb-content .ql-editor .ql-bg-teal, .nb-swatch.ql-bg-teal { background-color: var(--highlight-teal); }
        .nb-bg-teal { background-color: rgb(from var(--highlight-teal) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-cyan, .nb-content .ql-editor .ql-color-cyan, .nb-swatch.ql-color-cyan { color: oklch(from var(--highlight-cyan) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-cyan, :root[data-theme="light"] .nb-content .ql-editor .ql-color-cyan, :root[data-theme="light"] .nb-swatch.ql-color-cyan { color: oklch(from var(--highlight-cyan) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-cyan, .nb-content .ql-editor .ql-bg-cyan, .nb-swatch.ql-bg-cyan { background-color: var(--highlight-cyan); }
        .nb-bg-cyan { background-color: rgb(from var(--highlight-cyan) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-gray, .nb-content .ql-editor .ql-color-gray, .nb-swatch.ql-color-gray { color: oklch(from var(--highlight-gray) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-gray, :root[data-theme="light"] .nb-content .ql-editor .ql-color-gray, :root[data-theme="light"] .nb-swatch.ql-color-gray { color: oklch(from var(--highlight-gray) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-gray, .nb-content .ql-editor .ql-bg-gray, .nb-swatch.ql-bg-gray { background-color: var(--highlight-gray); }
        .nb-bg-gray { background-color: rgb(from var(--highlight-gray) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-brown, .nb-content .ql-editor .ql-color-brown, .nb-swatch.ql-color-brown { color: oklch(from var(--highlight-brown) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-brown, :root[data-theme="light"] .nb-content .ql-editor .ql-color-brown, :root[data-theme="light"] .nb-swatch.ql-color-brown { color: oklch(from var(--highlight-brown) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-brown, .nb-content .ql-editor .ql-bg-brown, .nb-swatch.ql-bg-brown { background-color: var(--highlight-brown); }
        .nb-bg-brown { background-color: rgb(from var(--highlight-brown) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-magenta, .nb-content .ql-editor .ql-color-magenta, .nb-swatch.ql-color-magenta { color: oklch(from var(--highlight-magenta) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-magenta, :root[data-theme="light"] .nb-content .ql-editor .ql-color-magenta, :root[data-theme="light"] .nb-swatch.ql-color-magenta { color: oklch(from var(--highlight-magenta) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-magenta, .nb-content .ql-editor .ql-bg-magenta, .nb-swatch.ql-bg-magenta { background-color: var(--highlight-magenta); }
        .nb-bg-magenta { background-color: rgb(from var(--highlight-magenta) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-lavender, .nb-content .ql-editor .ql-color-lavender, .nb-swatch.ql-color-lavender { color: oklch(from var(--highlight-lavender) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-lavender, :root[data-theme="light"] .nb-content .ql-editor .ql-color-lavender, :root[data-theme="light"] .nb-swatch.ql-color-lavender { color: oklch(from var(--highlight-lavender) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-lavender, .nb-content .ql-editor .ql-bg-lavender, .nb-swatch.ql-bg-lavender { background-color: var(--highlight-lavender); }
        .nb-bg-lavender { background-color: rgb(from var(--highlight-lavender) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-dark-purple, .nb-content .ql-editor .ql-color-dark-purple, .nb-swatch.ql-color-dark-purple { color: oklch(from var(--highlight-dark-purple) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-dark-purple, :root[data-theme="light"] .nb-content .ql-editor .ql-color-dark-purple, :root[data-theme="light"] .nb-swatch.ql-color-dark-purple { color: oklch(from var(--highlight-dark-purple) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-dark-purple, .nb-content .ql-editor .ql-bg-dark-purple, .nb-swatch.ql-bg-dark-purple { background-color: var(--highlight-dark-purple); }
        .nb-bg-dark-purple { background-color: rgb(from var(--highlight-dark-purple) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-light-pink, .nb-content .ql-editor .ql-color-light-pink, .nb-swatch.ql-color-light-pink { color: oklch(from var(--highlight-light-pink) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-light-pink, :root[data-theme="light"] .nb-content .ql-editor .ql-color-light-pink, :root[data-theme="light"] .nb-swatch.ql-color-light-pink { color: oklch(from var(--highlight-light-pink) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-light-pink, .nb-content .ql-editor .ql-bg-light-pink, .nb-swatch.ql-bg-light-pink { background-color: var(--highlight-light-pink); }
        .nb-bg-light-pink { background-color: rgb(from var(--highlight-light-pink) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-dark-pink, .nb-content .ql-editor .ql-color-dark-pink, .nb-swatch.ql-color-dark-pink { color: oklch(from var(--highlight-dark-pink) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-dark-pink, :root[data-theme="light"] .nb-content .ql-editor .ql-color-dark-pink, :root[data-theme="light"] .nb-swatch.ql-color-dark-pink { color: oklch(from var(--highlight-dark-pink) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-dark-pink, .nb-content .ql-editor .ql-bg-dark-pink, .nb-swatch.ql-bg-dark-pink { background-color: var(--highlight-dark-pink); }
        .nb-bg-dark-pink { background-color: rgb(from var(--highlight-dark-pink) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-dark-red, .nb-content .ql-editor .ql-color-dark-red, .nb-swatch.ql-color-dark-red { color: oklch(from var(--highlight-dark-red) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-dark-red, :root[data-theme="light"] .nb-content .ql-editor .ql-color-dark-red, :root[data-theme="light"] .nb-swatch.ql-color-dark-red { color: oklch(from var(--highlight-dark-red) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-dark-red, .nb-content .ql-editor .ql-bg-dark-red, .nb-swatch.ql-bg-dark-red { background-color: var(--highlight-dark-red); }
        .nb-bg-dark-red { background-color: rgb(from var(--highlight-dark-red) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-dark-teal, .nb-content .ql-editor .ql-color-dark-teal, .nb-swatch.ql-color-dark-teal { color: oklch(from var(--highlight-dark-teal) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-dark-teal, :root[data-theme="light"] .nb-content .ql-editor .ql-color-dark-teal, :root[data-theme="light"] .nb-swatch.ql-color-dark-teal { color: oklch(from var(--highlight-dark-teal) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-dark-teal, .nb-content .ql-editor .ql-bg-dark-teal, .nb-swatch.ql-bg-dark-teal { background-color: var(--highlight-dark-teal); }
        .nb-bg-dark-teal { background-color: rgb(from var(--highlight-dark-teal) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-dark-blue, .nb-content .ql-editor .ql-color-dark-blue, .nb-swatch.ql-color-dark-blue { color: oklch(from var(--highlight-dark-blue) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-dark-blue, :root[data-theme="light"] .nb-content .ql-editor .ql-color-dark-blue, :root[data-theme="light"] .nb-swatch.ql-color-dark-blue { color: oklch(from var(--highlight-dark-blue) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-dark-blue, .nb-content .ql-editor .ql-bg-dark-blue, .nb-swatch.ql-bg-dark-blue { background-color: var(--highlight-dark-blue); }
        .nb-bg-dark-blue { background-color: rgb(from var(--highlight-dark-blue) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-dark-green, .nb-content .ql-editor .ql-color-dark-green, .nb-swatch.ql-color-dark-green { color: oklch(from var(--highlight-dark-green) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-dark-green, :root[data-theme="light"] .nb-content .ql-editor .ql-color-dark-green, :root[data-theme="light"] .nb-swatch.ql-color-dark-green { color: oklch(from var(--highlight-dark-green) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-dark-green, .nb-content .ql-editor .ql-bg-dark-green, .nb-swatch.ql-bg-dark-green { background-color: var(--highlight-dark-green); }
        .nb-bg-dark-green { background-color: rgb(from var(--highlight-dark-green) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-light-blue, .nb-content .ql-editor .ql-color-light-blue, .nb-swatch.ql-color-light-blue { color: oklch(from var(--highlight-light-blue) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-light-blue, :root[data-theme="light"] .nb-content .ql-editor .ql-color-light-blue, :root[data-theme="light"] .nb-swatch.ql-color-light-blue { color: oklch(from var(--highlight-light-blue) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-light-blue, .nb-content .ql-editor .ql-bg-light-blue, .nb-swatch.ql-bg-light-blue { background-color: var(--highlight-light-blue); }
        .nb-bg-light-blue { background-color: rgb(from var(--highlight-light-blue) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-bright-green, .nb-content .ql-editor .ql-color-bright-green, .nb-swatch.ql-color-bright-green { color: oklch(from var(--highlight-bright-green) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-bright-green, :root[data-theme="light"] .nb-content .ql-editor .ql-color-bright-green, :root[data-theme="light"] .nb-swatch.ql-color-bright-green { color: oklch(from var(--highlight-bright-green) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-bright-green, .nb-content .ql-editor .ql-bg-bright-green, .nb-swatch.ql-bg-bright-green { background-color: var(--highlight-bright-green); }
        .nb-bg-bright-green { background-color: rgb(from var(--highlight-bright-green) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-light-yellow, .nb-content .ql-editor .ql-color-light-yellow, .nb-swatch.ql-color-light-yellow { color: oklch(from var(--highlight-light-yellow) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-light-yellow, :root[data-theme="light"] .nb-content .ql-editor .ql-color-light-yellow, :root[data-theme="light"] .nb-swatch.ql-color-light-yellow { color: oklch(from var(--highlight-light-yellow) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-light-yellow, .nb-content .ql-editor .ql-bg-light-yellow, .nb-swatch.ql-bg-light-yellow { background-color: var(--highlight-light-yellow); }
        .nb-bg-light-yellow { background-color: rgb(from var(--highlight-light-yellow) r g b / calc(alpha - 0.1)); }
        .nb-editor .ql-editor .ql-color-dark-yellow, .nb-content .ql-editor .ql-color-dark-yellow, .nb-swatch.ql-color-dark-yellow { color: oklch(from var(--highlight-dark-yellow) max(l, 0.87) c h / 1); }
        :root[data-theme="light"] .nb-editor .ql-editor .ql-color-dark-yellow, :root[data-theme="light"] .nb-content .ql-editor .ql-color-dark-yellow, :root[data-theme="light"] .nb-swatch.ql-color-dark-yellow { color: oklch(from var(--highlight-dark-yellow) min(l, 0.47) c h / 1); }
        .nb-editor .ql-editor .ql-bg-dark-yellow, .nb-content .ql-editor .ql-bg-dark-yellow, .nb-swatch.ql-bg-dark-yellow { background-color: var(--highlight-dark-yellow); }
        .nb-bg-dark-yellow { background-color: rgb(from var(--highlight-dark-yellow) r g b / calc(alpha - 0.1)); }

        /* Filter checklist. */
        .nb-filter-section { margin-bottom: 16px; }
        .nb-filter-head {
            margin: 0 0 6px;
            font-size: calc(1.15rem * var(--ui-scale));
            letter-spacing: 0.08em;
            text-transform: uppercase;
            font-weight: 800;
            color: var(--text-secondary);
        }
        .nb-check {
            display: flex;
            align-items: center;
            gap: 10px;
            width: 100%;
            padding: 9px 0;
            border: none;
            border-bottom: 1px solid var(--divider-color);
            background: none;
            color: var(--text-main);
            font-family: inherit;
            font-size: calc(1.3rem * var(--ui-scale));
            text-align: left;
            cursor: pointer;
        }
        .nb-check:last-child { border-bottom: 0; }
        .nb-check-box {
            width: calc(1.5rem * var(--ui-scale));
            height: calc(1.5rem * var(--ui-scale));
            border: 2px solid var(--divider-color);
            border-radius: 5px;
            display: grid;
            place-items: center;
            flex: 0 0 auto;
            font-size: 0.85em;
            font-weight: 800;
        }
        .nb-check[aria-checked="true"] .nb-check-box {
            border-color: var(--accent-secondary);
            background-color: var(--accent-secondary);
            color: var(--bg-body);
        }
        .nb-check .nb-check-n {
            margin-left: auto;
            color: var(--text-secondary);
        }
        .nb-check:focus-visible {
            outline: 3px solid var(--accent-focus);
            outline-offset: 2px;
        }
        .nb-check-swatches { padding: 6px 0; }
