/* ================================================================
   Dhamma Photo Powerful Viewer — viewer.css
   Dark mode default · warm gold accent · graceful animations
   ================================================================ */

/* ── CSS Variables ─────────────────────────────────────────────── */
:root {
    --bg:           #0f0a05;   /* warm near-black — flatters the gold borders */
    --bg-nav:       #1a1a1a;
    --bg-panel:     #1e1e1e;
    --bg-thumb:     #252525;
    --bg-input:     #2a2a2a;
    --accent:       #c8a96e;
    --accent-dim:   rgba(200, 169, 110, 0.18);
    --accent-glow:  rgba(200, 169, 110, 0.35);
    /* "On this device" — the Catalogue's saved/downloaded states (library.css
       221, 431/433, 439/441). These were CONSUMED there but defined nowhere,
       in either palette, so every one of them fell through to a hardcoded mint
       #7bbf8a — a green belonging to no palette in this app, showing in BOTH
       themes. Deliberately a QUIETER member of the accent family rather than
       its own hue: --accent-dim (0.18) is the actionable "Get" pill, so a
       confirmatory state that is the same family but visibly softer reads as
       "done" instead of as a second button — which is what library.css's own
       comment says it is for. */
    --ok:           #a99065;
    --ok-dim:       rgba(200, 169, 110, 0.10);
    --text:         #e8e0d0;
    --text-dim:     #777;
    --text-muted:   #555;
    --border:       #2e2e2e;
    --border-mid:   #3a3a3a;
    --bookmark-col: #e8a020;
    --danger:       #d95f5f;

    --nav-h:        165px;
    --top-h:        48px;
    --toggle-h:     28px;
    --radius:       8px;
    --ease:         cubic-bezier(0.4, 0, 0.2, 1);
    --ease-out:     cubic-bezier(0.22, 1, 0.36, 1);   /* shared motion signature */
    --t:            0.3s;
    --immersive-overlay: rgba(10, 10, 10, 0.80);
}

/* Light mode — warm brown palette · menu #ceab71 · picture bg #a36029 */
body.light-mode {
    --bg:           #e2c888;
    --bg-nav:       #d4ba80;
    --bg-panel:     #e8d4a0;
    /* --bg-thumb/--bg-input are meant as a SUBTLE step off --bg-panel (a
       hover fill, an input field) — in dark mode that step is near-black to
       dark-grey. These were still saturated mid-tones (#c49050/#bb8840,
       darker AND more saturated than the panel around them), so instead of
       reading as "a field" they read as a mismatched colour block dropped
       onto the panel. Kept in the same warm family, just much closer in
       lightness to --bg-panel, same relationship dark mode already has. */
    --bg-thumb:     #d9c090;
    --bg-input:     #eddcb8;
    /* Was #5c2e08 — a near-black, low-saturation brown with none of dark
       mode's gold "pop" (--accent there is a warm, fairly bright #c8a96e).
       Every pill/button/dot/scrubber/loading-wheel in light mode reads off
       this one variable, so a flat dark accent made ALL of them read as
       muddy at once. A richer, more saturated burnt-orange keeps enough
       contrast for text use against the pale panel/bg tones while actually
       having some warmth and life to it, matching dark mode's energy
       instead of just being its darkest-possible inverse. */
    --accent:       #a8460a;
    --accent-dim:   rgba(168, 70, 10, 0.16);
    --accent-glow:  rgba(168, 70, 10, 0.32);
    /* Light-mode "on this device" — same reasoning as the dark pair above, in
       the burnt-orange family. Kept DARKER than --accent rather than lighter:
       on parchment, a paler tone would lose contrast against --bg-panel
       (#e8d4a0), which is the surface these pills actually sit on. */
    --ok:           #8a5a2a;
    --ok-dim:       rgba(168, 70, 10, 0.09);
    --text:         #2c1408;
    --text-dim:     #7a4820;
    --text-muted:   #9a6840;
    --border:       #b07830;
    --border-mid:   #986020;
    --bookmark-col: #c06820;
    --immersive-overlay: rgba(180, 130, 60, 0.88);
}

/* Light mode accent pills (chapter-info, cover-info, help "Got it", zoom
   exit) — dark-brown-on-tan already has correct contrast, but sat flat
   against the terracotta picture background with no separation. A light
   ring + real shadow lifts them off the backdrop instead of blending in. */
body.light-mode .chapter-overlay__btn,
body.light-mode .cover-frame__btn,
body.light-mode .help-got-it,
body.light-mode .zoom-exit-btn:hover {
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.28), 0 0 0 1.5px rgba(255, 246, 225, 0.55);
}

/* Light mode picture background — rich terracotta + noise grain.
   Three separate elements all cover the stage at different moments
   (.pswp__bg always, .pswp__item behind a still-loading image, .page-loader
   during a far-jump decode wait) — all three must share this override or
   whichever one is exposed at the moment flashes the wrong (dark-theme)
   colour. Root-caused from two user screenshots: a pale-tan mismatch
   against the item background mid-swipe, and a solid near-black loading
   wheel backdrop in light mode — neither had a light-mode override before. */
body.light-mode .pswp__bg,
body.light-mode .pswp__item,
body.light-mode .page-loader {
    background: #a36029 url('../images/noise.png') !important;
}

/* ── Reset ─────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
    height: 100%;
    overflow: hidden;
    background: var(--bg) url('../images/noise.png');
    color: var(--text);
    font-family: 'Roboto', 'Segoe UI', sans-serif;
    font-size: 14px;
    transition: background var(--t) var(--ease), color var(--t) var(--ease);
}

button { cursor: pointer; border: none; background: none; color: inherit; font-family: inherit; }
input, select { font-family: inherit; }
/* A bare <a> with no more specific class (Contact & Support's "Dharma
   Records"/"exabytes.my"/"Support the work" links, none of them inside
   .about-text) fell through to the browser's own default blue/purple —
   jarring against the warm theme. One global default, same as every other
   themed element already assumes, closes that gap wherever a future link
   gets added without its own colour rule. */
a { text-decoration: none; color: var(--accent); }

/* ── Top bar ────────────────────────────────────────────────────── */
.top-bar {
    position: fixed;
    inset: 0 0 auto 0;
    height: var(--top-h);
    background: var(--bg-nav);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 16px;
    z-index: 1600;
    transition: background var(--t) var(--ease), opacity 0.25s ease;
}

/* flex:1 1 auto + min-width:0 on the LEFT side, flex:0 0 auto on the RIGHT —
   so the title shrinks to whatever room the icon row (which never shrinks,
   they're tap targets) actually leaves, instead of a fixed 55vw guess that
   took no account of how much space .top-bar__right really needs and could
   overlap it on a narrow phone with a long book title. */
.top-bar__left  { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; }
.top-bar__logo  { font-size: 22px; line-height: 1; flex: 0 0 auto; }
.top-bar__title {
    font-size: 15px;
    font-weight: 500;
    color: var(--accent);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1 1 auto;
    min-width: 0;
    max-width: 480px;   /* a ceiling on a wide screen; .top-bar__left's real
                           available width is what actually constrains it everywhere else */
}
.top-bar__right { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }

.icon-btn {
    width: 36px; height: 36px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 18px;
    transition: background var(--t), transform 0.15s;
}
.icon-btn:hover { background: var(--accent-dim); transform: scale(1.12); }
.icon-btn:active { transform: scale(0.92); }

/* Nav menu button (☰ Show/Hide) */
.nav-menu-btn {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 5px 10px;
    border-radius: 6px;
    border: 1px solid var(--border-mid);
    color: var(--text-dim);
    font-size: 13px;
    transition: color 0.2s, border-color 0.2s, background 0.2s;
    white-space: nowrap;
}
.nav-menu-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-dim);
}
.nav-menu-icon { font-size: 16px; line-height: 1; }
.nav-menu-label { font-size: 12px; letter-spacing: 0.3px; }

/* Divider between nav button and other controls */
.top-bar__divider {
    width: 1px;
    height: 22px;
    background: var(--border-mid);
    margin: 0 4px;
    flex-shrink: 0;
}

/* ── Main viewer — size gauge between top-bar and nav-panel ─────── */
/* PhotoSwipe lives at body level; this div's bounds are watched by
   ResizeObserver so parseVerticalMargin keeps image margins correct */
.viewer-main {
    position: fixed;
    top: var(--top-h);
    left: 0; right: 0;
    bottom: var(--nav-h);
    overflow: hidden;
    transition: top var(--t) var(--ease), bottom var(--t) var(--ease);
}

.viewer-main.nav-hidden {
    bottom: var(--toggle-h);
}

/* PhotoSwipe background matches app theme */
.pswp__bg {
    background: var(--bg) url('../images/noise.png') !important;
}
.pswp__img--placeholder--blank {
    background: var(--bg-thumb);
}
/* .pswp__img itself has no background in the library's own CSS — while a
   large JPG is still downloading/decoding, the empty <img> box shows
   whatever's behind it. .pswp__bg covers the stage too, but it's a
   SEPARATE element with its own independent opacity transition (open/
   close animation) — the two can be a frame or two out of sync. Setting
   the background directly on .pswp__item (the img's own immediate
   container, always fully painted, no transition of its own) closes that
   gap so a slow-loading photo never flashes the browser's default white
   instead of the app's dark theme. */
.pswp__item {
    background: var(--bg) url('../images/noise.png');
}

/* PhotoSwipe cursor — remove default zoom-in magnifier everywhere */
.pswp--zoom-allowed .pswp__img {
    cursor: default !important;
}
.pswp--dragging .pswp__img {
    cursor: grabbing !important;
}

/* Zoom mode: restore zoom cursors only when user has explicitly toggled zoom mode (Z) */
.pswp.pswp--zoom-mode .pswp__img                  { cursor: zoom-in  !important; }
.pswp.pswp--zoom-mode.pswp--zoomed-in .pswp__img  { cursor: zoom-out !important; }

/* Pointer cursor while over an active page-turn edge (JS-toggled) */
.pswp.edge-nav-cursor .pswp__img { cursor: pointer !important; }

/* ── Page-change transitions ─────────────────────────────────────
   Driven from viewer.js with the Web Animations API — pure JS motion
   that no stylesheet rule can suppress. Adjacent turns animate the
   engine's own container transform (glides like a swipe); ready jumps
   are instant; not-yet-decoded pages show the dhamma-wheel precursor.
   Exactly mirrors the approved bench (test-transition.html). */

/* Promote ONLY the element we actually animate — the container. Do NOT add
   will-change / backface-visibility to its descendants (zoom-wrap, img):
   a composited CHILD layer inside a container whose transform is being
   WAAPI-animated is positioned independently by the compositor in some
   browsers, so the OUTGOING page stops riding the glide and appears to
   VANISH while the new page slides in. The bench promotes only the container
   and its glide is clean — match it. (Zoom flicker is handled by the
   ZOOM_FIT_EPS park, not by promoting the zoom-wrap.) */
.pswp__container { will-change: transform; }

/* Dhamma-wheel precursor — covers the stage (below the top-bar/nav
   chrome at 1600) when a jumped-to page is still decoding, so a far
   jump never reveals black. Opacity is driven from JS (Web Animations)
   so no stylesheet rule can suppress it. */
.page-loader {
    position: fixed;
    inset: 0;
    z-index: 1560;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #1c130a;              /* warm brown, never black */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}
.page-loader__wheel {
    font-size: 72px;
    line-height: 1;
    color: var(--accent, #c8a96e);
    /* This glow is a warm-gold halo tuned to show up against dark mode's
       near-black backdrop — in light mode --accent is a dark brown and the
       backdrop is already warm/terracotta, so the same glow all but
       vanishes and the wheel just reads flat. A pale cream glow instead
       gives light mode its own equivalent "lit from within" look. */
    text-shadow: 0 0 26px rgba(200, 169, 110, 0.35);
    animation: page-loader-spin 3.4s linear infinite;
}
body.light-mode .page-loader__wheel {
    /* Not a shadow/glow problem — a contrast one. --accent in light mode is
       a dark burnt-orange sitting on an equally-dark terracotta backdrop
       (.page-loader's own bg), so it reads flat no matter what glow is
       added — barely any lightness gap between glyph and backdrop. Dark
       mode's version works for the opposite reason: light gold ON near-
       black is a huge lightness gap. Mirroring that relationship here
       (light glyph on the dark terracotta) instead of reusing --accent. */
    /* Real gold, not cream/white — #f5ead2 fixed the contrast but lost the
       actual gold character dark mode's #c8a96e has, reading as washed-out
       rather than equally rich. This is the same gold family, just lifted
       enough in lightness to still contrast against the terracotta. */
    color: #e8b854;
    text-shadow: 0 0 22px rgba(255, 216, 140, 0.55);
}
@keyframes page-loader-spin { to { transform: rotate(360deg); } }

/* Diagnostics overlay (D key) — recordings carry the evidence */
.diag {
    position: fixed;
    top: calc(var(--top-h) + 10px);
    left: 10px;
    z-index: 2100;
    background: rgba(0, 0, 0, 0.78);
    color: #9fd49f;
    font: 11px/1.65 Consolas, monospace;
    padding: 8px 11px;
    border-radius: 8px;
    border: 1px solid #2c2c2c;
    pointer-events: none;
    white-space: pre;
}

/* Build stamp at the foot of Settings */
.setting-build {
    position: relative;               /* anchors the double-click build-log popover */
    margin-top: 3px;
    font-size: 10.5px;
    color: var(--text-muted);         /* was text-dim @ 0.7 opacity — same dimness, but
                                         opacity would ghost the popover child too */
    text-align: left;                 /* flows line-after-line under the libraries,
                                         never a centred, separate footer */
}

/* Build-log popover — double-click the stamp. Tiny, quiet, click-away to close.
   Shows ~8 rows and scrolls (newest on top); the box never outgrows this. */
.build-log {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 0;                          /* the stamp is left-aligned now */
    max-height: 168px;
    overflow-y: auto;
    padding: 8px 12px;
    background: var(--bg-panel);
    border: 1px solid var(--border-mid);
    border-radius: 8px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
    font-size: 10.5px;
    line-height: 1.8;
    color: var(--text-dim);
    text-align: left;
    white-space: nowrap;
    z-index: 20;
}
.build-log__head {
    font-weight: 600;
    color: var(--text-dim);
    border-bottom: 1px solid var(--border);
    margin-bottom: 3px;
    padding-bottom: 2px;
}

/* ── Cover peek — soft-box preview of the Book Information cover ── */
.cover-peek {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2400;                    /* above the Book Information popup */
    padding: 10px;
    background: var(--bg-panel);
    border: 1px solid var(--border-mid);
    border-radius: 12px;
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
    pointer-events: none;             /* moving the mouse away always dismisses */
}
.cover-peek img {
    display: block;
    max-width: min(60vw, 420px);
    max-height: 60vh;
    border-radius: 6px;
}
/* .pswp__img is intentionally NOT layer-promoted (no backface-visibility /
   will-change) — same reason as the zoom-wrap above: promoting a descendant
   of the animating container dropped the outgoing image out of the glide. */

/* Active state for toolbar toggle buttons */
.icon-btn.active {
    background: var(--accent-dim);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
}

/* ── Bookmark ribbon hotspot + PNG image ───────────────────────── */

/* Position + SIZE are driven from JS (_positionRibbon) so the ribbon rides
   the PAGE's own top-right corner and scales WITH the page (Kindle-style).
   On a wide screen the image is inset from the screen edge, so the ribbon
   sits FREE in the letterbox margin with a gap, clear of the page edge;
   when the image nearly fills the width it tucks onto the page instead of
   being marooned in the corner under the toolbar. The values below are
   fallbacks until the first _positionRibbon(). */
/* ── Bookmark ribbon ────────────────────────────────────────────
   A ribbon on a page that IS bookmarked, exactly as one left in a real book —
   and, on a mouse, a target you can click to set it.

   The grey hover-preview was removed once, for a real reason: the zone rides
   the image's top-right corner, which on a wide screen sits on the path to the
   top bar, so a plain :hover faded a grey ribbon in over the page every time
   you reached for Settings. Restored on a DWELL instead (see "The ribbon is a
   TARGET again" below): the pointer must rest ~160ms, so a pass-through never
   wakes it. Touch still takes no pointer — the ★ and the B key cover it. */
.ribbon-zone {
    position: fixed;
    top: var(--top-h);
    right: 6px;
    width: 64px;
    height: 116px;
    z-index: 1550;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

/* Nothing at all on an unmarked page. Like a real ribbon left in a book, it is
   either in there or it is not — there is no ghost of one. */
.page-ribbon {
    position: absolute;
    inset: 0;                     /* fill the JS-sized zone */
    background-size: contain;
    background-repeat: no-repeat;
    background-position: top center;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s ease;
}

.page-ribbon.bookmarked {
    background-image: url('../images/bookmark-sipa.png');
    opacity: 0.92;
}

/* ── The ribbon is a TARGET again ──────────────────────────────────
   It is an extra way to bookmark: rest the pointer where the ribbon lives
   and it appears — grey if this page is unmarked, the brown sipa ribbon if
   it is — and clicking it toggles the bookmark.

   It was removed once for a real reason: the ribbon rides the PAGE's
   top-right corner, which on a wide screen sits directly on the path the
   mouse takes to reach Settings, so a plain :hover faded a grey ribbon in
   over the page every time you went for the toolbar.

   The fix is dwell, not repositioning (the position is deliberate). The
   pointer must REST here ~160ms; a pass-through on the way to the top bar
   never wakes it. Mouse only — on touch there is no hover, and the ★ and
   the B key already cover it. */
@media (hover: hover) and (pointer: fine) {
    .ribbon-zone {
        pointer-events: auto;
        cursor: pointer;
    }
}

/* Unmarked page, pointer resting: the grey ghost says "a bookmark goes here". */
.page-ribbon.preview:not(.bookmarked) {
    background-image: url('../images/bookmark-grey.png');
    opacity: 0.5;
}

/* Already bookmarked: it is on screen anyway — just lift it, so the target
   still answers the pointer and reads as clickable. */
.page-ribbon.bookmarked.preview {
    opacity: 1;
}

/* ── Immersive bookmark star ────────────────────────────────────
   In full-view the ribbon fades away with the chrome; this whisper-faint
   star takes its place (top-right, screen-fixed) so the bookmark cue never
   disappears — essential on touch, where the ribbon is invisible. Hidden
   until immersive + bookmarked; the crossfade timing lives on the
   body.immersive rules so it rides the top bar's own 0.5s/2s cadence. */
.page-star {
    position: fixed;
    top: 18px;
    right: 20px;
    width: 26px;
    height: 26px;
    opacity: 0;                     /* faded fully out unless immersive + bookmarked */
    pointer-events: none;           /* never steals a tap/click */
    z-index: 1540;                  /* below the top bar (1600) so a hover-peek covers it */
    /* A soft DARK shadow (not a glow) so the star never dissolves into the
       gold border art — the "whisper" comes from the fill's own alpha, not
       from element opacity, so this shadow stays full-strength & legible. */
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.55)) drop-shadow(0 0 1px rgba(0,0,0,0.7));
    transition: opacity 0.3s ease;  /* quick fade-OUT when controls return */
}
.page-star svg  { display: block; width: 100%; height: 100%; }
.page-star path { fill: rgba(242, 231, 206, 0.66); }   /* warm parchment — a whisper */

/* "5/32" readout beside the ☆ in the nav bar — bookmarks used vs the per-book
   cap (ceil(pages / 5)). Tabular figures so it doesn't jiggle as it counts. */
.bookmark-count {
    font-size: 12px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    min-width: 2.4em;
    text-align: left;
    user-select: none;
}

/* ══ Q — page image quality, in the reading bar ═══════════════════
   Sits after the ☆ and its count, because that is where a reader already
   is when the pictures look wrong. One letter: this row is the tightest
   space in the app and already collapses its labelled buttons into ⋯ More
   below 560px, so a word here would push the counter around. The words
   themselves are in the popover, and the same setting is spelled out in
   Preferences > Image quality.

   The popover opens UPWARD — the bar is at the foot of the screen and a
   downward menu would open off it. */
.quality-q { position: relative; display: inline-flex; }
/* No box. Its neighbours in this row are the ☆ and the "5/32" count, both
   plain text on the bar itself, and a bordered pill next to them read as a
   stray second control — there is already one box in the row, the page
   number input, and a second one made the group look like two halves of
   different designs. So the Q is set as text and behaves like the ☆: muted,
   accent on hover, accent when it is doing something. */
.quality-q__btn {
    cursor: pointer;
    background: none;
    border: 0;
    padding: 0 2px;
    margin-left: 4px;
    color: var(--text-dim);
    font-size: 12px;
    font-weight: normal;
    line-height: 1;
    letter-spacing: .02em;
    transition: color .2s, transform .2s;
}
.quality-q__btn:hover { color: var(--accent); transform: scale(1.2); }
/* A downloaded book fixes its own size, and the Q used to look identical
   whether or not it could move — so the only way to discover it was locked
   was to try. The mark says so before it is touched. */
.quality-q__btn.is-locked::after {
    content: '·';
    font-size: 11px;
    vertical-align: super;
    opacity: .8;
}
.quality-q__btn.is-locked { opacity: .75; }
.quality-q__btn.is-medium { }
.quality-q__pop {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 0;
    z-index: 60;
    min-width: 128px;
    max-width: 240px;
    padding: 6px;
    border-radius: 10px;
    background: var(--bg-panel, #1e1e1e);
    border: 1px solid var(--border-mid);
    box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
}
.quality-q__opt {
    display: block;
    width: 100%;
    text-align: left;
    cursor: pointer;
    background: none;
    border: 0;
    border-radius: 6px;
    color: var(--text);
    font: 13px/1.4 inherit;
    padding: 7px 10px;
}
.quality-q__opt:hover:not(:disabled) { background: var(--bg-thumb); }
.quality-q__opt.is-active { color: var(--accent); font-weight: 600; }
.quality-q__opt.is-active::after { content: ' ✓'; }
/* Locked, not hidden. A downloaded book's pages exist at one size only, and
   a reader who reaches for this control deserves to be told that rather
   than to find the control missing. */
.quality-q__opt:disabled { cursor: default; opacity: .5; }
.quality-q__note {
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--text-dim);
    padding: 6px 10px 2px;
    border-top: 1px solid var(--border);
    margin-top: 4px;
}

/* A setting whose control is too wide to sit beside its label — the pill
   goes under the text rather than squeezing both onto one line. */
.setting-row--stack {
    display: block;
}
.setting-row--stack .segmented-pill { margin-top: 8px; }

/* ══ How to hard refresh ══════════════════════════════════════════
   Written for someone who has never heard the word "cache". The reader's
   own browser is boxed at the top because a table of eight is what makes
   a non-technical reader close the popup. */
.hr-intro, .hr-foot {
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-dim);
    margin: 0 0 14px;
}
.hr-foot { margin: 14px 0 0; }
.hr-yours {
    padding: 12px 14px;
    border-radius: 10px;
    border: 1px solid var(--accent);
    background: var(--bg-thumb);
    margin-bottom: 18px;
}
.hr-yours__label {
    font-size: 10px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.hr-yours__name { font-size: 14px; font-weight: 600; color: var(--accent); margin: 2px 0 6px; }
.hr-yours__keys { font-size: 13px; line-height: 1.5; color: var(--text); }
.hr-all__head {
    font-size: 10px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-muted);
    font-weight: 500;
    margin: 0 0 8px;
}
.hr-list { margin: 0; font-size: 12.5px; line-height: 1.5; }
.hr-list dt { color: var(--text); font-weight: 600; margin-top: 8px; }
.hr-list dd { color: var(--text-dim); margin: 2px 0 0; }
.hr-list kbd {
    display: inline-block;
    padding: 1px 5px;
    border-radius: 4px;
    border: 1px solid var(--border-mid);
    background: var(--bg-thumb);
    font: 600 11.5px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: var(--text);
}

/* Foot of the Bookmarks panel — icon actions, kept clear of the list. */
.overlay-panel__footer {
    flex: 0 0 auto;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
}

/* Notepad + trash-can row, grouped together on the right: trash deletes
   bookmarks AND clears the notepad, so it sits directly beside the notepad
   button it now also acts on, rather than drifting to the opposite edge. */
.overlay-panel__footer--icons {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}
.panel-icon-btn {
    width: 40px;
    height: 40px;
    border-radius: 8px;
    background: transparent;
    border: 1px solid var(--border-mid, rgba(255, 255, 255, 0.15));
    color: var(--text-dim);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s var(--ease), border-color 0.15s var(--ease),
                color 0.15s var(--ease);
}
.panel-icon-btn:hover {
    background: var(--bg-thumb);
    color: var(--text);
}
/* Only turns red on hover — a resting red button is too loud in a reading app. */
.panel-icon-btn--danger:hover {
    background: rgba(200, 80, 80, 0.12);
    border-color: #d98c8c;
    color: #d98c8c;
}

/* ── Notepad panel — one plain textarea, no toolbar ─────────────── */
.overlay-panel__body--notepad {
    display: flex;
    padding: 12px 14px;
}
.notepad-textarea {
    flex: 1;
    width: 100%;
    resize: none;
    background: var(--bg-input);
    border: 1px solid var(--border-mid);
    border-radius: 8px;
    padding: 12px 14px;
    color: var(--text);
    font: inherit;
    font-size: 14px;
    line-height: 1.6;
}
.notepad-textarea:focus {
    outline: none;
    border-color: var(--accent);
}
.notepad-textarea::placeholder { color: var(--text-dim); }

/* ── Confirm dialog — small centered popup for destructive actions.
   Built in JS (viewer.js _confirmDialog), styled here so it matches the
   app's own chrome instead of a system window.confirm(). Sits above the
   overlay panels (1701) and the top-level popups (1750). ─────────────── */
.confirm-dialog {
    position: fixed;
    inset: 0;
    z-index: 1800;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, 0.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s var(--ease);
}
.confirm-dialog.open {
    opacity: 1;
    pointer-events: auto;
}
.confirm-dialog__card {
    width: 100%;
    max-width: 340px;
    border-radius: 14px;
    border: 1px solid var(--border-mid);
    background: var(--bg-panel);
    box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
    padding: 20px 20px 16px;
    transform: translateY(8px) scale(0.98);
    transition: transform 0.18s var(--ease);
}
.confirm-dialog.open .confirm-dialog__card {
    transform: none;
}
.confirm-dialog__title {
    margin: 0 0 8px;
    font-size: 16px;
    font-weight: 600;
    color: var(--accent);
}
.confirm-dialog__msg {
    margin: 0 0 18px;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--text-dim);
}
.confirm-dialog__actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}
.confirm-dialog__btn {
    padding: 9px 16px;
    border-radius: 8px;
    font: inherit;
    font-size: 13.5px;
    font-weight: 500;
    border: 1px solid var(--border-mid);
    background: transparent;
    color: var(--text);
    cursor: pointer;
    transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.confirm-dialog__btn:hover { background: var(--bg-thumb); }
.confirm-dialog__btn--danger {
    background: rgba(200, 80, 80, 0.14);
    border-color: #d98c8c;
    color: #d98c8c;
}
.confirm-dialog__btn--danger:hover { background: rgba(200, 80, 80, 0.26); }

/* Download buttons in nav bar. A single ⬇ icon: saving a page is rare, so it
   does not get two labelled buttons stretching across the bar - it asks which
   copy you want, in a panel with room to explain that "high resolution" needs
   a connection. Nothing is mirrored into Settings any more: the ⬇ collapses
   into ⋯ More with the rest, where it is spelled out in words. */
.save-btn {
    border-color: var(--border-mid);
    color: var(--text-dim);
    font-size: 14px;
    padding-left: 11px;
    padding-right: 11px;
}
.save-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-dim);
}

/* No-book placeholder */
.no-book {
    display: none;
    position: absolute;
    inset: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    color: var(--text-dim);
    background: var(--bg) url('../images/noise.png');
}
.no-book.visible { display: flex; }
.no-book__icon { width: 56px; height: 56px; opacity: 0.25; }
.no-book__text { font-size: 16px; }
.no-book__btn {
    padding: 8px 20px;
    border: 1px solid var(--accent);
    border-radius: 20px;
    color: var(--accent);
    font-size: 13px;
    transition: background 0.2s, color 0.2s;
}
.no-book__btn:hover { background: var(--accent); color: var(--bg); }

/* Hidden utility */
.hidden { display: none !important; }

/* ================================================================
   SIDE NAVIGATION ZONES — invisible arrow hints on left/right edges
   ================================================================ */
.side-nav {
    position: fixed;
    top: var(--top-h);
    bottom: var(--nav-h);
    width: 18%;
    /* Above the cover/chapter overlays (1520), not just the page (1500) —
       same reasoning as .img-shade just above: their opaque backdrop
       otherwise paints over the ❮/❯ arrow glyph on a cover/marker page. */
    z-index: 1525;
    pointer-events: none;   /* clicks fall through to PhotoSwipe beneath */
    display: flex;
    align-items: center;
    transition: top var(--t) var(--ease), bottom var(--t) var(--ease);
}
.side-nav--left  { left: 0;  justify-content: flex-start; }
.side-nav--right { right: 0; justify-content: flex-end;   }

/* ── Edge hover shades — the BUG's look, kept on purpose.
   The accidental panel-shadow bleed (found in the build-56 hunt) was exactly
   the shadow wanted: DARK, hugging the SCREEN edge, fading inward over the
   arrow zone. So these reproduce it — same darkness, same narrowness — but
   only while the matching ❮ / ❯ arrow is hovered, and bounded to the reading
   band (top bar → nav panel) so they never touch the thumbnails. */
.img-shade {
    position: fixed;
    top: var(--top-h);
    bottom: var(--nav-h);
    width: 130px;
    /* Above the page (1500) AND above the cover/chapter overlays (1520) —
       otherwise their opaque backdrop paints over this on a cover/marker
       page, so the hover arrow triggers (pointer-events pass through) but
       is invisible. Still below the chrome (1550+). */
    z-index: 1525;
    opacity: 0;
    transition: opacity 0.18s ease;
    pointer-events: none;
}
.img-shade--left  { left: 0;  background: linear-gradient(to right, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.22) 45%, transparent); }
.img-shade--right { right: 0; background: linear-gradient(to left,  rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.22) 45%, transparent); }
.img-shade.show   { opacity: 1; }
body.immersive  .img-shade,
body.fullscreen .img-shade { top: 0; bottom: 0; }
body.zoom-mode  .img-shade { opacity: 0 !important; }

/* Chapter frame (TEST feature) — mirrors viewerMain's own fixed box and
   nav-hidden transition exactly (see the HTML comment above this element
   for why it's a body-level sibling, not a viewerMain child). Inside it, an
   aspect-ratio-locked box the same shape as the wheel image (2645:1488) is
   fit by CSS using the same algorithm PhotoSwipe itself uses to fit the
   actual image (centred, capped to the container) — a real CSS layout, not
   a value computed once in JS, so it tracks the image on every resize AND
   smoothly through the nav-hidden transition. Title/button are positioned
   as a percentage of THAT inner box, so they stay in the same spot
   relative to the wheel at any screen size. */
.chapter-frame {
    position: fixed;
    top: var(--top-h);
    left: 0; right: 0;
    bottom: var(--nav-h);
    z-index: 1520;              /* above .pswp's own 1500 */
    pointer-events: none;
    transition: top var(--t) var(--ease), bottom var(--t) var(--ease);
}
.chapter-frame.nav-hidden { bottom: var(--toggle-h); }
.chapter-frame__box {
    position: absolute;
    inset: 0;
    margin: auto;
    aspect-ratio: 2645 / 1488;
    max-width: 100%;
    max-height: 100%;
    container-type: inline-size;   /* lets cqw below scale with THIS box, not the viewport */
}
/* PhotoSwipe gets a blank placeholder for a chapter-marker slide (see
   _initPhotoSwipe) — same reason as the cover overlay: a real photo there
   is meant to be zoomed/panned, but this wheel graphic is a fixed marker
   with text baked into the layout, not content to inspect. Rendering it
   here instead, outside PhotoSwipe entirely, is what actually locks it —
   pinch/scroll/double-click zoom have nothing real underneath to act on. */
.chapter-frame__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
}
.chapter-overlay__title {
    position: absolute;
    left: 50%;
    top: 58%;
    transform: translateX(-50%);
    font-family: 'Segoe Script', 'Bradley Hand', 'Brush Script MT', cursive;
    font-size: clamp(16px, 4.6cqw, 30px);
    color: var(--text);
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.6);
    text-align: center;
    max-width: 90%;
    white-space: nowrap;
}
.chapter-overlay__btn {
    position: absolute;
    left: 50%;
    top: 72%;
    transform: translateX(-50%);
    pointer-events: auto;
    background: var(--accent);
    color: var(--bg);
    border-radius: 999px;
    padding: clamp(6px, 1.8cqw, 10px) clamp(14px, 4cqw, 24px);
    font-size: clamp(11px, 2.6cqw, 14px);
    font-weight: 700;
    letter-spacing: 0.02em;
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
    white-space: nowrap;
}
.chapter-overlay__btn:hover { filter: brightness(1.08); }

/* Cover page overlay (TEST feature) — a real photo, capped at its own native
   pixel size (800x450 for the site's own cover graphics) so it's never
   scaled UP past its source resolution. Scaling down is always crisp;
   scaling up is what causes visible pixelation — this is the fix for that.

   Two layers: a full-bleed BLURRED copy of the same cover as a backdrop
   (also solves the "double image" problem — it deliberately hides
   PhotoSwipe's own sharp/pixelated full-bleed render of the same photo
   underneath, rather than showing both side by side), and the crisp,
   correctly-capped copy on top with its own soft shadow — the same
   blurred-poster-backdrop pattern most media apps use for cover art. */
.cover-frame {
    position: fixed;
    top: var(--top-h);
    left: 0; right: 0;
    bottom: var(--nav-h);
    z-index: 1520;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 22px;
    overflow: hidden;
    background: var(--bg) url('../images/noise.png');   /* same grain texture
                                  every other surface in the app uses (see
                                  html/body above) — base layer that never
                                  lets PhotoSwipe's own render underneath peek
                                  through, even for the brief moment before
                                  the backdrop image itself has loaded */
    pointer-events: none;
    transition: top var(--t) var(--ease), bottom var(--t) var(--ease);
}
.cover-frame.nav-hidden { bottom: var(--toggle-h); }

/* Per-image caption (page.caption) — Bhante's own written text about the
   SPECIFIC photo on screen, not the chapter. A slim bar pinned to the
   bottom edge, not a box laid over the photo itself: it sits below where
   PhotoSwipe's own contained image ends in the normal (non-zoomed) case, so
   it reads as a caption under a photo rather than a watermark on it. Hidden
   by default in markup (8,405 of 8,430 pages have none) — JS only ever
   un-hides it for the ~25 pages that do. */
.page-caption {
    position: fixed;
    left: 0; right: 0;
    bottom: var(--nav-h);
    z-index: 1510;                 /* below chapter/cover frames (1520), above the reader */
    display: flex;
    justify-content: center;
    padding: 8px 16px;
    pointer-events: none;          /* never steals a tap from the side-nav zones beneath it */
    transition: bottom var(--t) var(--ease);
}
.page-caption.nav-hidden { bottom: var(--toggle-h); }
.page-caption__text {
    margin: 0;
    max-width: min(720px, 92%);
    padding: 6px 14px;
    border-radius: 8px;
    /* plain value first: color-mix is Safari 16.2 / Chrome 111 / Firefox 113.
       Without a fallback the whole declaration is dropped and the caption has
       no background at all — text floating on the photo, marginal on a dark
       one. Same cascade-order fallback used for the dividers below. */
    background: rgba(15, 10, 5, 0.78);
    background: color-mix(in srgb, var(--bg) 78%, transparent);
    color: var(--text);
    font-size: clamp(12px, 3vw, 14px);
    line-height: 1.5;
    text-align: center;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* Image-information badge — (i) ON the photo, centre-top, per Kassapa's
   approved design (.scratchpad/map-info-button.html, Option A). Sits at a
   fixed spot in the reading box, same convention every sibling overlay in
   this file uses (.chapter-frame, .cover-frame, .page-caption, .ribbon-
   zone) — position:fixed against the reading box's own top edge
   (var(--top-h)), not position:absolute inside a DOM ancestor: PhotoSwipe
   lives at body level (.viewer-main is a size gauge only, see its own
   comment — not an ancestor of the image), so there is no such ancestor
   to be absolute inside. A small circle, not a full-width row like
   .page-caption above, so it needs no pointer-events trick to stay clear
   of the side-nav zones — its own ~34px never reaches the 18%-wide edge
   strips those occupy. */
.image-info-badge {
    position: fixed;
    top: calc(var(--top-h) + 14px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 1512;                 /* above .page-caption (1510), below chapter/cover frames (1520) */
    width: 34px; height: 34px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--bg);
    border: 1.5px solid rgba(255, 255, 255, 0.55);
    font: italic 700 17px/1 Georgia, serif;
    text-align: center;
    padding: 0;                    /* it is a <button>; without this the UA's own padding fights the fixed 34px box */
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
    transition: filter     0.25s var(--ease),
                box-shadow 0.25s var(--ease),
                transform  0.25s var(--ease);
}
.image-info-badge:hover,
.image-info-badge:focus-visible {
    filter: brightness(1.08);
    outline: none;
    box-shadow: 0 0 0 3px var(--accent-glow), 0 2px 10px rgba(0, 0, 0, 0.4);
    transform: translateX(-50%) scale(1.2);
}

.cover-frame__backdrop {
    position: absolute;
    inset: -40px;                 /* overshoot so the blur never shows a hard edge */
    background-size: cover;
    background-position: center;
    filter: blur(40px) brightness(0.35) saturate(1.1);
    transform: scale(1.15);       /* extra margin against blur's own edge softening */
}
.cover-frame__img {
    position: relative;
    max-width: min(800px, 92%);
    max-height: 70%;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 6px;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.cover-frame__btn {
    position: relative;   /* without this, the positioned backdrop paints
                              OVER it regardless of DOM order — CSS always
                              paints positioned elements above non-positioned
                              ones. This is the actual "invisible until
                              hover" bug: clicks reached it (pointer-events
                              pass through the backdrop) but paint never did. */
    pointer-events: auto;
    background: var(--accent);
    color: var(--bg);
    border: 1.5px solid rgba(255, 255, 255, 0.55);
    border-radius: 999px;
    padding: 10px 24px;
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.02em;
    cursor: pointer;
    /* A golden button read as near-invisible sitting on the blurred
       backdrop of the SAME cover photo (also warm/golden) — same colour
       family, nothing to separate them at rest, only noticeable once
       :hover's brightness bump made it pop. The white border + a dark
       outer ring give it contrast against ANY backdrop colour, not just
       ones that happen to differ from --accent. */
    box-shadow: 0 2px 14px rgba(0, 0, 0, 0.55), 0 0 0 3px rgba(0, 0, 0, 0.25);
}
.cover-frame__btn:hover { filter: brightness(1.08); }

.side-nav::after {
    font-size: 52px;
    line-height: 1;
    font-weight: 300;
    color: #fff;
    opacity: 0;
    text-shadow:
        0 2px 16px rgba(0, 0, 0, 0.95),
        0 0  6px  rgba(0, 0, 0, 0.70);
    transition: opacity 0.18s ease, transform 0.3s var(--ease-out);
    padding: 0 16px;
    user-select: none;
}
/* Arrows rest slightly outside and glide in as the cursor approaches */
.side-nav--left::after  { content: '❮'; transform: translateX(-10px); }
.side-nav--right::after { content: '❯'; transform: translateX(10px); }
/* Opacity driven by JS proximity: var(--zone-opacity) ranges 0.18 → 0.82 */
.side-nav.hovered::after { opacity: var(--zone-opacity, 0.35); transform: translateX(0); }

/* Click acknowledgement — quick press pulse */
@keyframes arrow-press { 40% { transform: scale(0.8); } }
.side-nav.pressed::after { animation: arrow-press 0.28s var(--ease-out); }

/* In immersive mode (nav hidden) or browser fullscreen: zones span full viewport */
body.immersive .side-nav,
body.fullscreen .side-nav {
    top: 0;
    bottom: 0;
}

/* ================================================================
   NAVIGATION PANEL
   ================================================================ */
.nav-panel {
    position: fixed;
    bottom: 0;
    left: 0; right: 0;
    height: var(--nav-h);
    background: var(--bg-nav);
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    z-index: 1600;
    transition: transform var(--t) var(--ease), background var(--t) var(--ease);
}

.nav-panel.collapsed {
    transform: translateY(100%);
}

/* Nav rows */
.nav-row {
    display: flex;
    align-items: center;
    padding: 0 10px;
    gap: 6px;
    flex-shrink: 0;
}

/* Row 1 — thumbnail strip */
.nav-row--thumbs {
    flex: 1;
    padding-top: 8px;
    padding-bottom: 4px;
    overflow: hidden;
    min-height: 0;
}

/* Row 2 — page input / actions */
.nav-row--info { padding-top: 2px; padding-bottom: 4px; position: relative; }

/* Row 3 — scrubber */
.nav-row--scrubber { padding-bottom: 8px; }

/* ── Navigation button groups ───────────────────────────────────── */
.nav-btns { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }

.nav-btn {
    padding: 4px 7px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: -0.5px;
    color: var(--text-dim);
    border: 1px solid transparent;
    transition: color 0.15s, background 0.15s, border-color 0.15s,
                transform 0.15s, box-shadow 0.15s, opacity 0.25s var(--ease);
    white-space: nowrap;
    line-height: 1.6;
    user-select: none;
}
/* Book boundaries: dead buttons say so instead of silently ignoring */
.nav-btn.disabled {
    opacity: 0.26;
    pointer-events: none;
}
.nav-btn:hover {
    color: var(--accent);
    background: var(--accent-dim);
    border-color: rgba(200,169,110,0.4);
    transform: scale(1.1);
    box-shadow: 0 0 8px var(--accent-glow);
}
.nav-btn:active { transform: scale(0.9); }

/* ── Thumbnail strip (Glide) ───────────────────────────────────── */
.thumbnail-wrap {
    flex: 1;
    overflow: hidden;
    height: 100%;
    min-width: 0;
    padding-inline: 6px;
}

.glide        { height: 100%; }
/* visible, not hidden: the first chapter mark sits in .thumbnail-wrap's
   left padding, and the track would cut it off before the wrap clips. */
.glide__track { height: 100%; overflow: visible; }
.glide__slides {
    height: 100%;
    align-items: center;
    will-change: transform;
    /* Load-bearing for .chapter-mark, which is absolute and positioned from
       its target thumb's offsetLeft (navigation.js _positionChapterMarks).
       Drop this and offsetLeft resolves against some ancestor further up and
       every mark lands wrong. Glide's transform would establish the block too,
       but only AFTER the first navigation — this holds from the first render. */
    position: relative;
}

/* Filmstrip focus: the active page holds full presence, the rest
   recede — the eye lands on where you are without hunting */
.thumb-item {
    height: 62px;
    flex-shrink: 0;
    cursor: pointer;
    border-radius: 5px;
    position: relative;
    opacity: 0.72;
    overflow: hidden;
    /* NO background here. The v2 prototype has none and shows no hairlines;
       a fill behind an object-fit:cover image shows through the sub-pixel
       edge when Glide lands a cell on a fractional width, on some slides and
       not others. The transparent chapter-wheel PNG it was added for has its
       own :has() rule below and does not need it here. */
    /* Back from the v2 prototype, where it was always in .thumb-item and has
       been missing here since at least 10 Aug. Two jobs: the hover/active
       rings below fade in instead of snapping, and every thumb keeps a
       defined 1px edge at rest. Glide positions slides on fractional pixels
       and the hover filter re-rasterises the cell, so an edgeless rounded
       box lands on a half pixel and the browser paints a seam on some
       slides and not others. */
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
    transition: box-shadow 0.25s var(--ease),
                filter     0.25s var(--ease),
                opacity    0.25s var(--ease);
}
/* The chapter-wheel thumb (TEST feature) is a transparent PNG — the generic
   flat grey placeholder above showed through its corners as a mismatched
   box next to the warm-toned real photo thumbs either side of it. A warm
   fill (+ the same grain every other surface in the app uses) lets it blend
   tonally while staying visually distinct as a marker via the wheel motif
   itself — same :hover/.active rules as every other thumbnail apply on top,
   nothing thumb-specific added or overridden here. */
.thumb-item:has(img[src*="chapter-wheel-thumb"]) {
    background: linear-gradient(160deg, #2e2013, #1c130a), url('../images/noise.png');
}
body.light-mode .thumb-item:has(img[src*="chapter-wheel-thumb"]) {
    background: linear-gradient(160deg, #a36029, #8a4f1f), url('../images/noise.png');
}
.thumb-item img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
.thumb-item:hover {
    opacity: 0.95;
    filter: brightness(1.12);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.30);
}
.thumb-item.active {
    opacity: 1;
    box-shadow:
        inset 0 0 0 2px var(--accent),
        inset 0 0 14px rgba(200, 169, 110, 0.28);
    filter: brightness(1.06);
}
.thumb-item.active .thumb-num {
    background: rgba(200, 169, 110, 0.72);
    color: #111;
    font-weight: 600;
}
/* No chapter-marker-specific thumbnail styling — it uses the exact same
   markup and CSS as every other thumbnail now (see navigation.js
   _buildThumbnails). The wheel is pre-composed into a plain landscape
   image at the standard thumb shape instead. */
.thumb-num {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    background: rgba(0,0,0,0.62);
    color: #fff;
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 0.3px;
    text-align: center;
    padding: 2px 0;
    pointer-events: none;
}

/* ── Cover tile ─────────────────────────────────────────────────
   A static sibling of the strip, inside .thumbs-content-wrapper. It is NOT
   a Glide slide and not inside #thumbSlides — see navigation.js
   _refreshCoverTile for why that mattered.

   It stays square at every breakpoint because nothing sizes it but these
   rules; Glide never sees it. Deliberately 6px larger than a .thumb-item —
   the tile is the strip's anchor, not one of its pages. */
.thumbs-content-wrapper {
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;      /* lets the row shrink instead of overflowing the panel */
    gap: 0;            /* the cover-to-strip gap is .thumbnail-wrap's padding */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    --cover-tile-w: 62px;
}
.thumbs-content-wrapper::-webkit-scrollbar { display: none; }
/* Full row width, so cover + strip is WIDER than the row and there is
   something to scroll. Without this the strip shrinks to fit beside the cover
   and the cover just sits there permanently, stealing width. */
.thumbs-content-wrapper > .thumbnail-wrap {
    flex: 0 0 100%;
    min-width: 0;
    padding-left: 23px;    /* the gap the first chapter mark sits in */
    padding-right: 23px;   /* MUST match: unequal padding moves the centre */
    /* Not hidden. The first chapter mark straddles the cover-to-strip gap and
       grows on hover, so it has to reach past this box's left edge. The
       scrolling row clips instead, one level out. */
    overflow: visible;
}

/* The cover lead — the strip's first item, built in navigation.js
   _refreshCoverTile. A real cell in the flow, one slide wide, so it takes its
   own slot and scrolls away with the thumbnails rather than sitting over the
   first one. glide__slide--clone keeps it out of Glide's slide list.

   Two earlier shapes, both rejected 2026-08-20 and both measured:
     · flex sibling beside the strip — steals a tile-width from the strip and
       puts the track's centre half a tile right of the row's centre, 37px out
       at every viewport; balancing it with an empty ::after costs a SECOND
       tile-width and took cells to 14.7px at 390.
     · absolutely positioned overlay at left:0 — centring correct, but it
       covered thumbnail 1 and the first chapter mark. */


.cover-info-tile {
    flex-shrink: 0;
    /* Space at the strip's left edge, so the cover doesn't sit against the nav
       buttons. A MARGIN, not padding on the scrolling row — padding there
       shrinks the row's content box, and .thumbnail-wrap's 100% goes with it,
       which pulls the strip off centre by half the padding. */
    margin-left: 8px;
    height: var(--cover-tile-w);
    width: var(--cover-tile-w);
    cursor: pointer;
    border-radius: 8px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: box-shadow 0.15s, filter 0.15s, transform 0.15s;
}
.cover-info-tile[hidden] { display: none; }
/* Exact values from .scratchpad/badge-mockup/preview-cover-svg.html — the
   drawn design, carried over verbatim into the app's real theme classes
   rather than approximated through the generic --bg-panel/--bg-thumb tokens
   (which read flat and grey here). Deliberately 6px LARGER than a .thumb-item
   at every breakpoint: the tile is the strip's anchor, not one of its pages,
   and reading as slightly-oversized is what says so at a glance. */
body.dark-mode .cover-info-tile {
    background: linear-gradient(160deg, #2e2013, #1c130a);
    box-shadow: 0 0 0 1.5px #c8a96e, 0 2px 8px rgba(0, 0, 0, 0.35);
    color: #c8a96e;
}
body.light-mode .cover-info-tile {
    /* Was #fff7ea/#f0e2c8 -- whiter than every real light-mode surface
       (--bg-input, the lightest token, is #eddcb8). Dark mode's version stays
       inside the dark palette's own family (warm brown, not pure black); this
       now does the same in reverse -- --bg-input to --bg-thumb, no new hex. */
    background: linear-gradient(160deg, #eddcb8, #d9c090);
    box-shadow: 0 0 0 1.5px #a8460a, 0 2px 8px rgba(0, 0, 0, 0.12);
    color: #a8460a;
}
.cover-info-tile:hover,
.cover-info-tile:focus-visible { filter: brightness(1.12); }
body.dark-mode .cover-info-tile:hover,
body.dark-mode .cover-info-tile:focus-visible {
    box-shadow: 0 0 0 1.5px #e6c065, 0 0 12px rgba(230, 192, 101, 0.5), 0 2px 8px rgba(0, 0, 0, 0.35);
}
body.light-mode .cover-info-tile:hover,
body.light-mode .cover-info-tile:focus-visible {
    box-shadow: 0 0 0 1.5px #a8460a, 0 0 10px rgba(168, 70, 10, 0.3), 0 2px 8px rgba(0, 0, 0, 0.12);
}
/* Plain scale. The tile is a flex child now and the row centres it; the old
   translateY(-50%) here was the absolute-positioning centring, and left in
   place it shot the tile half its own height upward on every press. */
.cover-info-tile:active { transform: scale(0.94); }
.cover-info-tile svg {
    width: 60%;
    height: 60%;
    flex-shrink: 0;
    transition: transform 0.15s ease;
}
/* The icon grows on hover, not the tile itself — the square's own size stays
   fixed (it's a real Glide clone-slide, other layout depends on its box not
   changing), so only the mark inside gets the "something happens" feedback. */
.cover-info-tile:hover svg,
.cover-info-tile:focus-visible svg { transform: scale(1.15); }

/* ── Chapter marks ──────────────────────────────────────────────
   Same mechanism as .cover-info-tile (real child of #thumbSlides, Glide-
   excluded via glide__slide--clone, position:absolute so it consumes no
   flex space Glide's own perView math accounts for) — see navigation.js
   _buildChapterMarks for the full reasoning. The one real difference: the
   cover tile only ever sits at a constant left:0; each mark's `left` is
   set in JS (_positionChapterMarks) from the REAL rendered position of the
   thumbnail it sits beside, since that position is different per mark and
   changes with viewport width. translate(-50%,-50%) here (not the cover
   tile's translateY(-50%) alone) is what lets _positionChapterMarks set
   only the target's left EDGE and have the mark centre itself on that
   point, regardless of the mark's own current size — relevant because it
   grows on hover, so its own width isn't a fixed number to account for. */
.chapter-mark {
    position: absolute;
    left: 0;
    top: 50%;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
    transition: width 0.16s ease, height 0.16s ease, box-shadow 0.16s ease;
    transform: translate(-50%, -50%);
}
.chapter-mark svg { width: 62%; height: 62%; flex-shrink: 0; }

/* Pulls the FIRST chapter mark toward the cover tile — confirmed live
   2026-08-06 (a -6px pull visibly closed the gap; increased further here).
   Real cause: Glide's Gaps component sets an inline margin-left on every
   .glide__slide, blind to glide__slide--clone, and the JS positioning
   (_positionChapterMarks: mark.style.left = target.offsetLeft) has no way
   to know about it. Harmless for chapter marks 2+ (sitting between two real
   thumbs either side, margin-left there is expected spacing) — only the
   FIRST mark sits right against the cover tile, where that extra margin
   read as a bigger gap than intended. [data-target-idx="0"] is the same
   element for any book regardless of its own page count, not tied to one
   book's specific position in the list. */
.chapter-mark[data-target-idx="0"] { margin-left: var(--chapter-mark-lead-pull, -12px) !important; }

/* Two style variants, dev-switchable (navigation.js _fillChapterMark): the
   hand-made PNG, or the chapter's own ordinal as text. Only the contents
   differ — the circle itself is identical either way, so a switch never
   touches size, position, or Glide.

   Both themed PNGs are always in the DOM; CSS hides the wrong one. That
   keeps a day/night flip free — no JS re-runs, nothing to keep in sync —
   and both files are precached in sw.js, so neither is a live fetch. */
.chapter-mark__icon { width: 62%; height: 62%; object-fit: contain; flex-shrink: 0; }
body.dark-mode  .chapter-mark__icon--light { display: none; }
body.light-mode .chapter-mark__icon--dark  { display: none; }
/* Sized against the circle rather than fixed, so it stays proportionate
   through the hover growth the circle already animates. */
.chapter-mark__num {
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
    user-select: none;
}
.chapter-mark:hover .chapter-mark__num,
.chapter-mark:focus-visible .chapter-mark__num { font-size: 15px; }

/* Exact colours from .scratchpad/badge-mockup/preview-v3.html — not an
   approximation via the app's existing neutral --bg-panel/--bg-thumb
   (which read as flat grey here); this is the mockup's own warm brown/gold
   radial fill, carried over value-for-value into the app's real theme
   classes (body.dark-mode / body.light-mode) rather than the mockup's own
   .night/.day section wrappers. */
body.dark-mode .chapter-mark {
    background: radial-gradient(circle at 35% 30%, #4a3820, #3a2c17 75%);
    box-shadow: 0 0 0 1px rgba(200, 169, 110, 0.5), 0 2px 5px rgba(0, 0, 0, 0.5);
    color: #c8a96e;
}
body.dark-mode .chapter-mark:hover,
body.dark-mode .chapter-mark:focus-visible {
    box-shadow: 0 0 0 1.5px #e6c065, 0 0 10px rgba(230, 192, 101, 0.55), 0 2px 6px rgba(0, 0, 0, 0.5);
}
body.light-mode .chapter-mark {
    /* Same fix as .cover-info-tile just above: was pure #ffffff at centre,
       whiter than any real light-mode token. --bg-input/--bg-thumb instead. */
    background: radial-gradient(circle at 35% 30%, #eddcb8, #d9c090 75%);
    box-shadow: 0 0 0 1px rgba(168, 70, 10, 0.35), 0 2px 5px rgba(0, 0, 0, 0.15);
    color: #a8460a;
}
body.light-mode .chapter-mark:hover,
body.light-mode .chapter-mark:focus-visible {
    box-shadow: 0 0 0 1.5px #a8460a, 0 0 8px rgba(168, 70, 10, 0.3), 0 2px 6px rgba(0, 0, 0, 0.18);
}
.chapter-mark:hover,
.chapter-mark:focus-visible {
    width: 34px;
    height: 34px;
    /* .cover-info-tile has this same brightness bump on hover; chapter-mark
       never did, just its own box-shadow glow below -- adding it so both
       controls read the same way on hover, not just on their resting bg. */
    filter: brightness(1.12);
}

/* The strip is a plain Glide list now: only .thumb-item children, no lead,
   no reserved footprint. The cover tile is a flex sibling (see
   .thumbs-content-wrapper above), so nothing has to make room for it here.
   overflow stays visible so a chapter mark can sit proud of the row; the
   track still clips, which is the edge that matters. */
#thumbSlides { overflow: visible; }

/* chapter-test2 (TEST, see direction.md): hover-peek badge, top-right
   corner of a cover/chapter-marker thumb — inset, not overflowing, since
   .thumb-item clips (overflow:hidden). */
.thumb-peek-btn {
    position: absolute;
    top: 3px; right: 3px;
    width: 16px; height: 16px;
    border-radius: 50%;
    border: none;
    background: rgba(0, 0, 0, 0.62);
    color: #fff;
    font: italic 700 10px/16px Georgia, serif;
    text-align: center;
    padding: 0;
    cursor: pointer;
}
.thumb-peek-btn:hover,
.thumb-peek-btn:focus-visible {
    background: var(--accent);
    color: #111;
}

/* Image-information badge on a filmstrip cell — (i) for any page.caption
   page, real books included, not gated to a test slug like .thumb-peek-btn
   above. Centre-top, INSIDE the cell. It straddled the top edge for a while,
   which needed the image wrapped in an inner clip layer so .thumb-item could
   drop its own overflow:hidden — the exact change navigation.js already
   records as having broken Glide's slide sizing once before. Reverted; the
   cell clips its own children, so anything over the edge is cut and 2px
   keeps the whole badge on the tile, hover growth included. The two badges
   answer different
   questions and never appear on the same cell today (a caption page is
   never also a chapter-marker/cover), but nothing here assumes that stays
   true. */
.thumb-caption-btn {
    position: absolute;
    top: 2px;
    left: 50%;
    transform: translateX(-50%);
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--bg);
    border: 1.5px solid rgba(255, 255, 255, 0.55);
    font: italic 700 10px/13px Georgia, serif;
    text-align: center;
    padding: 0;
    cursor: pointer;
    z-index: 2;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
    transition: filter     0.25s var(--ease),
                box-shadow 0.25s var(--ease),
                transform  0.25s var(--ease);
}
.thumb-caption-btn:hover,
.thumb-caption-btn:focus-visible {
    filter: brightness(1.08);
    outline: none;
    box-shadow: 0 0 0 2px var(--accent-glow), 0 1px 4px rgba(0, 0, 0, 0.45);
    transform: translateX(-50%) scale(1.25);
}

.thumb-peek {
    position: fixed;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2400;
    padding: 10px;
    background: var(--bg-panel);
    border: 1px solid var(--border-mid);
    border-radius: 12px;
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
    text-align: center;
}
.thumb-peek img {
    display: block;
    max-width: min(60vw, 420px);
    max-height: 60vh;
    border-radius: 6px;
    cursor: pointer;
}
.thumb-peek__hint {
    margin-top: 8px;
    font-size: 12px;
    color: var(--text-dim);
}

/* ── Page input row ─────────────────────────────────────────────── */
.page-input-group {
    display: flex;
    align-items: center;
    gap: 5px;
}

.page-input {
    width: 52px;
    background: var(--bg-input);
    border: 1px solid var(--border-mid);
    border-radius: 5px;
    padding: 3px 6px;
    font-size: 14px;
    text-align: center;
    color: var(--text);
    outline: none;
    transition: border-color 0.2s;
    -moz-appearance: textfield;
}
.page-input::-webkit-inner-spin-button,
.page-input::-webkit-outer-spin-button { -webkit-appearance: none; }
.page-input:focus { border-color: var(--accent); }

.page-sep   { color: var(--text-dim); font-size: 13px; }
.page-total { color: var(--text-dim); font-size: 13px; min-width: 28px; }

.star-btn {
    font-size: 22px;
    line-height: 1;
    color: var(--text-muted);
    transition: color 0.2s, transform 0.2s;
    margin-left: 2px;
}
.star-btn:hover       { color: var(--bookmark-col); transform: scale(1.25); }
.star-btn.bookmarked  { color: var(--bookmark-col); }

.nav-actions { display: flex; gap: 6px; margin-left: auto; }

.action-btn {
    padding: 4px 13px;
    border-radius: 14px;
    border: 1px solid var(--border-mid);
    font-size: 12px;
    color: var(--text-dim);
    transition: color 0.2s, border-color 0.2s, background 0.2s, transform 0.15s;
    white-space: nowrap;
}
.action-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-dim);
    transform: translateY(-1px);
}

/* ── Scrubber ────────────────────────────────────────────────────── */
.scrubber {
    flex: 1;
    padding: 12px 0;   /* fat hit zone — the 4px track alone is unhittable */
    cursor: pointer;
    user-select: none;
    touch-action: none;
}

.scrubber-track {
    position: relative;
    height: 4px;
    background: var(--bg-thumb);
    border-radius: 2px;
}

.scrubber-fill {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    background: var(--accent);
    border-radius: 2px;
    pointer-events: none;
    transition: width 0.12s;
}

.scrubber-dot {
    position: absolute;
    top: 50%;
    width: 14px; height: 14px;
    background: var(--accent);
    border-radius: 50%;
    box-shadow: 0 0 8px var(--accent-glow), 0 0 0 2px var(--bg-nav);
    transform: translate(-50%, -50%);
    z-index: 3;
    transition: left 0.12s, transform 0.15s, box-shadow 0.15s;
    cursor: grab;
}
.scrubber-dot:hover,
.scrubber-dot.dragging {
    transform: translate(-50%, -50%) scale(1.45);
    box-shadow: 0 0 14px var(--accent-glow), 0 0 0 3px var(--bg-nav);
    cursor: grabbing;
}

.scrubber-bm-dot {
    position: absolute;
    top: 50%;
    width: 8px; height: 8px;
    background: var(--bookmark-col);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    cursor: pointer;
    box-shadow: 0 0 5px rgba(232,160,32,0.5);
    transition: left 0.2s, transform 0.15s;
}
.scrubber-bm-dot:hover {
    transform: translate(-50%, -50%) scale(1.6);
}

/* Page-number bubble — follows the cursor / finger along the track */
.scrubber-bubble {
    position: absolute;
    bottom: 16px;
    transform: translateX(-50%);
    background: var(--bg-panel);
    border: 1px solid var(--accent);
    color: var(--accent);
    font-size: 11px;
    font-weight: 600;
    padding: 2px 9px;
    border-radius: 10px;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    z-index: 4;
    transition: opacity 0.15s ease;
    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
}
.scrubber-bubble.show { opacity: 1; }

/* ── Nav collapse button — centred inside the info row ─────────── */
.nav-collapse-btn {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    padding: 3px 14px;
    border-radius: 5px;
    font-size: 14px;
    color: var(--text-muted);
    transition: color 0.2s, background 0.2s;
    line-height: 1.4;
    letter-spacing: 1px;
}
.nav-collapse-btn:hover {
    color: var(--accent);
    background: var(--accent-dim);
}
/* Same generous invisible hit area as .nav-toggle below (its complementary
   button — this hides the nav, that one brings it back) — this one was
   missed when that fix was made, leaving it a much narrower target than
   its counterpart despite doing the paired job. */
.nav-collapse-btn::before {
    content: '';
    position: absolute;
    left: -70px; right: -70px;
    top: -14px; bottom: -10px;
}

/* ── Nav restore tab — hidden while nav is visible, appears at bottom
      when nav collapses so the user can bring it back ──────────── */
.nav-toggle {
    position: fixed;
    bottom: 0; left: 50%;
    transform: translateX(-50%);
    width: 56px;
    height: var(--toggle-h);
    background: var(--bg-nav);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: 10px 10px 0 0;
    font-size: 13px;
    color: var(--text-dim);
    z-index: 1601;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease, color 0.2s, background var(--t) var(--ease);
    display: flex; align-items: center; justify-content: center;
    letter-spacing: 1px;
}
.nav-toggle:hover { color: var(--accent); }

/* Generous invisible hit area — a 56px tab at the bottom of the screen
   is too small a fishing target when the nav is hidden */
.nav-toggle::before {
    content: '';
    position: absolute;
    left: -70px; right: -70px;
    top: -14px; bottom: 0;
}

/* Show the restore tab only when the panel has been collapsed */
.nav-panel.collapsed ~ .nav-toggle {
    opacity: 1;
    pointer-events: auto;
}

/* ================================================================
   IMMERSIVE MODE — nav hidden, everything overlaid and invisible
   ================================================================ */

/* Top bar fades out after 2s — all properties together so no floating text glitch.
   Hovering the top edge snaps it back immediately. */
body.immersive .top-bar {
    opacity: 0;
    /* Not a hit target while faded — the hotzone below is the only thing
       that can trigger :hover on this element now. Without this, the full
       48px box stays hoverable even at opacity 0, which was the actual bug:
       any cursor movement through the top of the reading area re-triggered
       it regardless of the hotzone existing at all. */
    pointer-events: none;
    background: transparent !important;
    border-bottom-color: transparent !important;
    transition: opacity      0.5s ease 2s,
                background   0.5s ease 2s,
                border-color 0.5s ease 2s;
}
/* The hotzone is the ONLY always-hoverable trigger (a thin 10px strip at
   the very top edge) — a faded-out .top-bar itself is no longer a hit
   target across its full height, so passing the mouse through the middle
   of the top of the reading area doesn't pop it back unintentionally.
   Once actually revealed, hovering the bar itself (now really visible)
   still holds it open — that half of this rule is unchanged. */
.top-bar-hotzone {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 10px;
    z-index: 1601;
    pointer-events: none;
}
body.immersive .top-bar-hotzone { pointer-events: auto; }
body.immersive .top-bar:hover,
body.immersive:has(.top-bar-hotzone:hover) .top-bar {
    opacity: 1;
    pointer-events: auto;
    background: var(--immersive-overlay) !important;
    border-bottom-color: rgba(128, 128, 128, 0.15) !important;
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    transition: opacity      0.15s ease,
                background   0.15s ease,
                border-color 0.15s ease;
}

/* Nav toggle fades out in sync with the top bar.
   Selector must out-rank ".nav-panel.collapsed ~ .nav-toggle" (0,3,0)
   or the fade silently never applies and the tab stays lit forever. */
body.immersive .nav-panel.collapsed ~ .nav-toggle {
    opacity: 0;
    background: transparent;
    border-color: transparent;
    transition: opacity      0.5s ease 2s,
                background   0.5s ease 2s,
                border-color 0.5s ease 2s;
}
body.immersive .nav-panel.collapsed ~ .nav-toggle:hover {
    opacity: 1;
    background: var(--immersive-overlay);
    border-color: rgba(128, 128, 128, 0.2);
    color: var(--accent);
    transition: opacity      0.15s ease,
                background   0.15s ease,
                border-color 0.15s ease;
}

/* Viewer expands to fill the full viewport — PhotoSwipe inside fills it too */
body.immersive .viewer-main {
    top: 0 !important;
    bottom: 0 !important;
    transition: top var(--t) var(--ease), bottom var(--t) var(--ease);
}

/* Full-view: the ribbon fades out (and stops taking clicks) as the chrome
   goes, and the whisper-star crossfades in to hold the bookmark cue. Both
   ride the top bar's exact 0.5s-ease / 2s-hold cadence so the swap reads as
   one calm transformation, not a pop. (top:0 keeps it pinned to the viewer's
   new top edge for the moment before it's fully gone.) */
body.immersive .ribbon-zone {
    top: 0;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.5s ease 2s;
}
body.immersive .page-star.bookmarked {
    opacity: 1;                     /* fill-alpha carries the whisper; this just crossfades it in */
    transition: opacity 0.5s ease 2s;
}

/* ================================================================
   OVERLAY PANELS — Bookmarks & Settings
   ================================================================ */
.overlay-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    /* -webkit- pair for iOS 15-17: unprefixed backdrop-filter only landed in
       Safari 18. Without it the blur is silently dropped — the dim still
       works, so it degrades quietly, but this is the backdrop behind every
       side panel and the only one of five uses in this file that was missing
       the prefix. */
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    z-index: 1700;
    animation: bd-in 0.2s ease both;
}
@keyframes bd-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.overlay-panel {
    position: fixed;
    top: 0; right: 0; bottom: 0;
    width: min(380px, 92vw);
    background: var(--bg-panel);
    border-left: 1px solid var(--border);
    z-index: 1701;
    display: flex;
    flex-direction: column;
    transform: translateX(100%);
    transition: transform 0.35s var(--ease);
    /* NO resting box-shadow. The five parked panels sit just off-screen at
       translateX(100%), and a shadow on the resting state BLEEDS 40px back
       into the viewport — five stacked shadows made a permanent dark band
       down the app's right edge (found by Puppeteer, build 56). The drawer
       shadow belongs to the OPEN state only. */
}
.overlay-panel.open {
    transform: translateX(0);
    box-shadow: -10px 0 40px rgba(0,0,0,0.45);
}

/* Wide variant — the Contents grid needs room to breathe */
.overlay-panel--wide { width: min(560px, 94vw); }

.overlay-panel__inner {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
}

.overlay-panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px 14px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.overlay-panel__header h2 {
    font-size: 17px;
    font-weight: 500;
    color: var(--accent);
}

.close-btn {
    /* Matches .popup__close — same reasoning, see there. */
    width: 40px; height: 40px;
    border-radius: 50%;
    font-size: 19px;
    color: var(--text-dim);
    display: flex; align-items: center; justify-content: center;
    transition: color 0.2s, background 0.2s;
}
.close-btn:hover { color: var(--text); background: var(--bg-thumb); }

.overlay-panel__body {
    flex: 1;
    overflow-y: auto;
    padding: 16px 20px;
    scrollbar-width: thin;
    scrollbar-color: var(--border-mid) transparent;
}

/* ── Contents panel — filter + chapter sections + page grid ────── */
.contents-filter {
    width: 100%;
    background: var(--bg-input);
    border: 1px solid var(--border-mid);
    border-radius: 8px;
    padding: 8px 14px;
    font-size: 13px;
    color: var(--text);
    outline: none;
    margin-bottom: 16px;
    transition: border-color 0.2s;
}
.contents-filter:focus { border-color: var(--accent); }
.contents-filter::placeholder { color: var(--text-muted); }

/* Bhante's own grouping heading above a run of chapters -- five books.
   Sits above .toc-chapter, so it must not read as another chapter: no rule,
   no pointer, smaller and lettered wide. */
.toc-group {
    margin: 28px 0 4px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
}
.toc-group:first-child { margin-top: 0; }
.toc-group + .toc-section .toc-chapter { margin-top: 0; }

.toc-chapter {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin: 20px 0 10px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
    color: var(--accent);
    font-size: 14px;
    font-weight: 500;
    transition: color 0.2s;
}
.toc-section:first-child .toc-chapter { margin-top: 0; }
.toc-chapter:hover { color: var(--text); }
.toc-chapter__pages {
    font-size: 11px;
    font-weight: 400;
    color: var(--text-dim);
    white-space: nowrap;
}
.toc-chapter__meta {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-shrink: 0;
}
/* TEST feature (see direction.md) — plain text, not an icon/picture, only
   on chapters that actually have scraped text data. */
.toc-chapter__info-link {
    font-size: 11px;
    font-weight: 400;
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
    white-space: nowrap;
    cursor: pointer;
}
.toc-chapter__info-link:hover { color: var(--text); }

.toc-frontmatter-links {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 8px 0 16px;
}
.toc-frontmatter-divider {
    color: var(--text-muted);
    font-size: 11px;
}

.toc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: 8px;
}

.toc-item {
    position: relative;
    aspect-ratio: 16 / 9;
    border-radius: 5px;
    overflow: hidden;
    cursor: pointer;
    background: var(--bg-thumb);
    transition: box-shadow 0.2s, filter 0.15s;
}
.toc-item img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
.toc-item:hover {
    filter: brightness(1.18);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28);
}
.toc-item.active {
    box-shadow:
        inset 0 0 0 2px var(--accent),
        inset 0 0 14px rgba(200, 169, 110, 0.28);
    filter: brightness(1.06);
}
.toc-item__num {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    background: rgba(0, 0, 0, 0.62);
    color: #fff;
    font-size: 10px;
    text-align: center;
    padding: 2px 0;
    pointer-events: none;
}
.toc-item.active .toc-item__num {
    background: rgba(200, 169, 110, 0.72);
    color: #111;
    font-weight: 600;
}
/* Gold corner dot on bookmarked pages */
.toc-item.bookmarked::after {
    content: '';
    position: absolute;
    top: 4px; right: 4px;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--bookmark-col);
    box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
}

.toc-empty {
    color: var(--text-dim);
    font-style: italic;
    text-align: center;
    margin-top: 40px;
    line-height: 1.6;
}

/* ── Bookmark list ──────────────────────────────────────────────── */
.bookmark-entry {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 8px;
    border-radius: var(--radius);
    cursor: pointer;
    border-bottom: 1px solid var(--border);
    transition: background 0.2s;
}
.bookmark-entry:hover { background: var(--bg-thumb); }

.bookmark-entry__thumb {
    width: 38px; height: 54px;
    object-fit: cover;
    border-radius: 3px;
    flex-shrink: 0;
    border: 1px solid var(--border-mid);
}
.bookmark-entry__info  { flex: 1; min-width: 0; }
.bookmark-entry__page  { font-size: 13px; font-weight: 500; }
.bookmark-entry__label { font-size: 11px; color: var(--text-dim); margin-top: 2px; }

.bookmark-entry__del {
    font-size: 15px;
    color: var(--text-muted);
    width: 28px; height: 28px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    transition: color 0.2s, background 0.2s;
}
.bookmark-entry__del:hover { color: var(--danger); background: rgba(217,95,95,0.12); }

.empty-msg {
    color: var(--text-dim);
    font-style: italic;
    text-align: center;
    margin-top: 48px;
    line-height: 1.6;
}

/* ── Settings ───────────────────────────────────────────────────── */
/* Groups related rows (e.g. "Downloads") under a small label, same subdued
   treatment as .title-menu__section — a settings list, not a report, so it
   stays quiet rather than competing with the rows themselves. */
.setting-section-heading {
    padding: 18px 0 4px;
    color: var(--text-dim);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.setting-section-heading:first-child { padding-top: 0; }
/* Same zero-top-padding treatment, but usable on more than one heading per
   panel — e.g. Contact & Support has 3 group headings that each start a
   fresh visual group (right after a divider), not just the literal first
   child of the popup body. */
.setting-section-heading.first { padding-top: 0; }

/* Group divider — separates unrelated clusters of content within a popup
   (e.g. Contact & Support's About / Feedback+Development / Donations /
   Credits) from each other, so the eye reads them as distinct groups
   instead of one flat stack of same-weight headings. */
.popup-group-divider {
    border: 0;
    border-top: 1px solid var(--border);
    margin: 20px 0;
}

/* Plain text-styled link inside a popup body (e.g. Contact & Support's link
   to the full About & Copyright popup) — a <button> reset to look and read
   like a link, not a boxed control. Replaces one-off inline style resets. */
.popup-text-link {
    display: inline-block;
    margin-top: 10px;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    color: var(--accent);
    font: inherit;
    text-decoration: underline;
}

/* A .hub-item row whose action leaves the app (mailto:, external URL)
   rather than opening another panel inside it — same row treatment, but the
   trailing glyph must NOT be .hub-item__go's "›" (that specifically means
   "go to another screen in this app", which mailto/external links don't
   do). ↗ matches the external-link convention already used elsewhere
   (e.g. "View original ↗" near photodharma.net links). */
.hub-item__external {
    flex: 0 0 auto;
    font-size: 18px;
    color: var(--text-dim);
}

/* Consistent spacing for an action row (mailto link, etc.) directly under
   its lead-in paragraph within a popup group — replaces one-off inline
   margin-top/margin-bottom that varied row to row for no reason. */
.popup-action-row {
    margin-top: 10px;
}

/* Small muted credit/disclosure text at the foot of a popup — "who made
   this possible" facts, not action items, so deliberately quieter than the
   body copy above it. */
.popup-fine-print {
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.6;
}

/* The outline-SVG icon sitting inline in a popup's <h2> title, matching the
   emoji it replaces (Contact & Support, Preferences, Library
   Catalogue, Book & Cache Manager) — one class instead of repeating the
   same size/alignment inline on every title. */
.popup-title-icon {
    width: 20px;
    height: 20px;
    vertical-align: -4px;
}

.setting-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 13px 0;
    border-bottom: 1px solid var(--border);
}
.setting-row--col {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}
.setting-label { font-size: 14px; }

/* Toggle switch */
.toggle-switch {
    position: relative;
    display: inline-block;
    width: 44px; height: 24px;
    flex-shrink: 0;
    cursor: pointer;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
    position: absolute;
    inset: 0;
    background: var(--border-mid);
    border-radius: 24px;
    transition: background 0.25s;
}
.toggle-slider::before {
    content: '';
    position: absolute;
    left: 3px; top: 3px;
    width: 18px; height: 18px;
    background: #fff;
    border-radius: 50%;
    transition: transform 0.25s var(--ease);
}
.toggle-switch input:checked + .toggle-slider               { background: var(--accent); }
.toggle-switch input:checked + .toggle-slider::before       { transform: translateX(20px); }

/* Two-option picker (currently only Group Quick Access Menu: Stitched / A–Z)
   — a real choice between two named states, not an on/off toggle, so it
   reads as two buttons rather than a switch. */
.segmented-pill {
    display: inline-flex;
    border: 1px solid var(--accent);
    border-radius: 999px;
    overflow: hidden;
    flex-shrink: 0;
}
.segmented-pill__btn {
    border: 0;
    background: transparent;
    color: var(--text-dim);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    padding: 6px 12px;
    cursor: pointer;
    white-space: nowrap;
}
.segmented-pill__btn + .segmented-pill__btn { border-left: 1px solid var(--accent); }
.segmented-pill__btn.is-active { background: var(--accent); color: var(--bg); }

.file-input {
    font-size: 12px;
    color: var(--text-dim);
    cursor: pointer;
}

.setting-section-title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-dim);
    margin-top: 20px;
    margin-bottom: 8px;
}

/* Keyboard shortcut grid */
.kbd-grid {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 8px 14px;
    align-items: center;
}
kbd {
    background: var(--bg-thumb);
    border: 1px solid var(--border-mid);
    border-radius: 4px;
    padding: 2px 7px;
    font-size: 12px;
    font-family: monospace;
    color: var(--accent);
    white-space: nowrap;
}
.kbd-grid span { font-size: 12px; color: var(--text-dim); }

/* ================================================================
   ZOOM MODE — clean full-screen examine state
   ================================================================ */

/* Gold ✕ exit button: hidden by default, fades in when zoom mode is active */
.zoom-exit-btn {
    position: fixed;
    bottom: 22px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1602;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: rgba(10, 10, 10, 0.60);
    border: 1.5px solid var(--accent);
    color: var(--accent);
    font-size: 17px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.28s ease, background 0.18s, color 0.18s, transform 0.18s;
}
body.zoom-mode .zoom-exit-btn {
    opacity: 1;
    pointer-events: auto;
}
.zoom-exit-btn:hover {
    background: var(--accent);
    color: var(--bg);
    transform: translateX(-50%) scale(1.12);
}

/* In zoom mode: image fills the full viewport — top bar steps aside
   too, so nothing distracts from examining the photo */
body.zoom-mode .viewer-main {
    top: 0 !important;
    bottom: 0 !important;
}
body.zoom-mode .top-bar {
    opacity: 0;
    pointer-events: none;
}
body.zoom-mode .ribbon-zone {
    display: none;   /* the hotspot must not steal zoom clicks */
}
body.zoom-mode .page-star {
    opacity: 0 !important;   /* whisper-star steps aside too while examining a photo */
}

/* Nav panel slides away; toggle tab hides */
body.zoom-mode .nav-panel {
    transform: translateY(100%);
}
body.zoom-mode .nav-toggle {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

/* Side arrow zones hidden — no navigation in zoom mode */
body.zoom-mode .side-nav {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

/* ================================================================
   NAV TOGGLE PULSE — draws attention on first hide
   ================================================================ */
@keyframes nav-pulse {
    0%   { box-shadow: 0 0 0 0 var(--accent-glow); color: var(--text-dim); }
    40%  { box-shadow: 0 0 0 8px rgba(200,169,110,0); color: var(--accent); }
    100% { box-shadow: 0 0 0 0 rgba(200,169,110,0); color: var(--text-dim); }
}
.nav-toggle.pulse {
    animation: nav-pulse 0.9s ease-out 4;
}

/* Help button */
.help-btn {
    font-size: 15px;
    font-weight: 700;
    color: var(--text-dim);
    border: 1px solid var(--border-mid);
    border-radius: 50%;
    transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.help-btn:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-dim);
}

/* ================================================================
   HELP OVERLAY
   ================================================================ */
.help-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.72);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    z-index: 1800;
    display: flex;
    align-items: center;
    justify-content: center;
    animation: bd-in 0.25s ease both;
}
.help-overlay.hidden { display: none !important; }

.help-card {
    background: var(--bg-panel);
    border: 1px solid var(--border-mid);
    border-radius: 16px;
    padding: 32px 36px 24px;
    width: min(520px, 92vw);
    max-height: 92vh;              /* short screens scroll rather than clip */
    overflow-y: auto;
    box-shadow: 0 24px 80px rgba(0,0,0,0.7);
    animation: help-card-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes help-card-in {
    from { transform: scale(0.88) translateY(20px); opacity: 0; }
    to   { transform: scale(1)    translateY(0);    opacity: 1; }
}

.help-header {
    text-align: center;
    margin-bottom: 24px;
    position: relative;   /* anchors .help-close */
}
/* Help was the one overlay with no X at all — only "Got it" at the very
   bottom, past a scroll on a short screen. Same 40px target the panels and
   popups use. */
.help-close {
    position: absolute;
    top: -10px;
    right: -14px;
    width: 40px;
    height: 40px;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--text-dim);
    font-size: 19px;
    line-height: 1;
    cursor: pointer;
    transition: background var(--t), color var(--t);
}
.help-close:hover { background: var(--bg-thumb); color: var(--text); }

/* Two pages, one card. Only one is in the DOM flow at a time, so the card
   keeps its own height per page instead of padding out to the taller one. */
.help-page.hidden { display: none; }

.help-keytable {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 14px;
    font-size: 13px;
}
.help-keytable caption {
    caption-side: top;
    text-align: left;
    font-size: 13px;
    font-weight: 600;
    color: var(--accent);
    padding-bottom: 10px;
}
.help-keytable td {
    padding: 7px 8px;
    border-bottom: 1px solid var(--border-soft, var(--border-mid));
    vertical-align: middle;
}
.help-keytable tr:last-child td { border-bottom: none; }
/* Fixed narrow first column so all ten keys line up in one straight edge —
   the whole point of a table here rather than ten more cards. */
.help-keytable td:first-child { width: 34px; text-align: center; }
.help-keytable td:nth-child(2) { color: var(--text); white-space: nowrap; }
.help-keytable td:last-child   { color: var(--text-dim); }
.help-keytable__note {
    font-size: 11px;
    color: var(--text-muted);
    line-height: 1.5;
    margin-bottom: 18px;
}

.help-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin-bottom: 16px;
}
.help-pager__arrow {
    width: 40px;
    height: 40px;
    border: 1px solid var(--border-mid);
    border-radius: 50%;
    background: none;
    color: var(--accent);
    font-size: 14px;
    cursor: pointer;
    transition: background var(--t), opacity var(--t);
}
.help-pager__arrow:hover:not(:disabled) { background: var(--bg-thumb); }
/* Dimmed rather than hidden: the pair staying put is what tells you there
   are exactly two pages and which end you're at. */
.help-pager__arrow:disabled { opacity: 0.3; cursor: default; }
.help-pager__label {
    font-size: 12px;
    color: var(--text-dim);
    min-width: 78px;
    text-align: center;
}
.help-logo {
    width: 36px;
    height: 36px;
    display: block;
    margin: 0 auto 8px;
}
.help-header h2 {
    font-size: 20px;
    font-weight: 600;
    color: var(--accent);
    margin-bottom: 4px;
}
.help-header p {
    font-size: 13px;
    color: var(--text-dim);
}

.help-grid {
    display: flex;
    flex-direction: column;
    gap: 0;
    margin-bottom: 24px;
}

.help-item {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 12px 0;
    border-bottom: 1px solid var(--border);
}
.help-item:last-child { border-bottom: none; }

/* Titles keep their text; the KEY CHIPS are pushed to the column's right
   edge, so every column's keys line up in one straight vertical row. */
.help-text strong {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
}
.help-text strong kbd:first-of-type { margin-left: auto; }
.help-item--keys .help-text strong { display: inline; }   /* the centred title */

/* The ◀ ▶ glyphs render huge and heavy at the icon size — small and light.
   Double class: the base .help-icon rule sits LATER in this file and would
   win a specificity tie (the same source-order trap as the pd-links bug). */
.help-icon.help-icon--sm {
    font-size: 11px;
    font-weight: 400;
    letter-spacing: 3px;
    opacity: 0.9;
}

/* The Other-keys box: ⌨ and title CENTRED, then the keys in tight centred
   rows beneath — a small neat keyboard, not a stretched table. */
.help-item--keys { display: block; }
.help-item--keys .help-text {
    display: block;
    text-align: center;
}
.help-keygrid {
    display: grid;
    grid-template-columns: repeat(4, auto);   /* 8 keys → a balanced 4 × 2 map */
    justify-content: center;
    gap: 6px 22px;
    margin-top: 7px;
    font-size: 12px;
    color: var(--text-dim);
}
.help-keygrid span { white-space: nowrap; text-align: left; }
@media (max-width: 480px) {
    .help-keygrid { grid-template-columns: repeat(2, auto); }
}

.help-icon {
    font-size: 20px;
    width: 44px;
    text-align: center;
    flex-shrink: 0;
    color: var(--accent);
    font-weight: 700;
    letter-spacing: -1px;
}

.help-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.help-text span {
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.5;
}

.help-got-it {
    display: block;
    width: 100%;
    padding: 11px;
    background: var(--accent);
    color: var(--bg);
    border-radius: 8px;
    font-size: 15px;
    font-weight: 600;
    transition: opacity 0.2s, transform 0.15s;
    margin-bottom: 12px;
}
.help-got-it:hover { opacity: 0.88; transform: translateY(-1px); }
.help-got-it:active { transform: translateY(0); }

.help-reopen {
    text-align: center;
    font-size: 11px;
    color: var(--text-muted);
}

.help-pdf-link {
    display: block;
    text-align: center;
    font-size: 12px;
    margin-top: 8px;
    color: var(--accent);
}

/* ================================================================
   RESPONSIVE
   ================================================================ */
@media (max-width: 640px) {
    .nav-btn   { padding: 3px 5px; font-size: 11px; }
    .action-btn { padding: 4px 9px; font-size: 11px; }
    .top-bar__title { font-size: 13px; }
    .overlay-panel { width: 100vw; border-left: none; }
    .thumb-item { height: 60px; }
    .thumbs-content-wrapper { --cover-tile-w: 60px; }
}

/* .top-bar__right (☰ Hide + divider + 4 icon buttons) measures ~250px on a
   320px phone — nearly the whole screen — leaving a long book title nowhere
   to shrink to but invisible. Reclaim space from the chrome that has a
   redundant label, rather than letting the title collapse to nothing. */
@media (max-width: 480px) {
    .nav-menu-label  { display: none; }   /* ☰ icon alone is enough — Settings still says "Hide/Show" in full */
    .top-bar__divider { display: none; }
    .top-bar__title  { min-width: 44px; }  /* a floor: always at least a few characters + an ellipsis, never zero */
}

@media (max-width: 420px) {
    .nav-btns--left  .nav-btn:first-child,
    .nav-btns--right .nav-btn:last-child { display: none; }
}

/* ================================================================
   TOAST — small transient feedback pill
   ================================================================ */
.toast {
    position: fixed;
    left: 50%;
    bottom: calc(var(--nav-h) + 14px);   /* clear of the nav panel */
    transform: translateX(-50%) translateY(10px);
    background: var(--bg-panel);
    border: 1px solid var(--border-mid);
    color: var(--text);
    font-size: 13px;
    padding: 9px 18px;
    border-radius: 20px;
    white-space: nowrap;
    z-index: 1900;
    opacity: 0;
    pointer-events: none;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
    transition: opacity 0.25s ease, transform 0.25s ease, bottom var(--t) var(--ease);
}
.toast.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}
/* Nav hidden → toast drops to the bottom edge */
body.immersive .toast,
body.zoom-mode .toast { bottom: 46px; }

/* ── Low-storage warning ─────────────────────────────────────────
   Deliberately NOT toast-styled — a toast auto-fades and this must not,
   since "running low on space" stays true until the reader actually does
   something about it. Sits below the top bar, above everything else. */
.storage-warning {
    position: fixed;
    top: var(--top-h);
    left: 0; right: 0;
    z-index: 1590;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    background: var(--danger);
    color: #fff;
    font-size: 13px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
}
.storage-warning.hidden { display: none; }
.storage-warning__icon { font-size: 16px; flex: 0 0 auto; }
.storage-warning__text { flex: 1 1 auto; }
.storage-warning__btn {
    flex: 0 0 auto;
    padding: 5px 14px;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.92);
    color: var(--danger);
    font-weight: 600;
    font-size: 12.5px;
    cursor: pointer;
}
.storage-warning__btn:hover { background: #fff; }
.storage-warning__close {
    flex: 0 0 auto;
    color: rgba(255, 255, 255, 0.8);
    font-size: 14px;
    cursor: pointer;
    padding: 4px;
}
.storage-warning__close:hover { color: #fff; }

/* ── Star shake — bookmark limit reached ───────────────────────── */
@keyframes star-shake {
    0%, 100% { transform: translateX(0); }
    20%      { transform: translateX(-4px); }
    40%      { transform: translateX(4px); }
    60%      { transform: translateX(-3px); }
    80%      { transform: translateX(3px); }
}
.star-btn.shake {
    animation: star-shake 0.4s ease;
    color: var(--danger) !important;
}

/* ── Keyboard focus — visible ring for keyboard users only ─────── */
:focus { outline: none; }
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}

/* ── Reduced motion ────────────────────────────────────────────── */
/* NOTE: an earlier build had a blanket prefers-reduced-motion rule here
   that collapsed ALL transitions/animations to 0.01ms. On Windows
   machines with “Animation effects” switched off (a common setting),
   that rule silently killed every piece of motion polish in the app —
   arrows, fades, page transitions — while JS-driven motion kept
   working. Removed on purpose: this is a reading app whose owner wants
   its gentle motion. If a reduce-motion option is ever needed, add it
   as an in-app Settings toggle, not a blanket kill. */

/* ================================================================
   RESPONSIVE — phone & tablet landscape
   Height is the scarce dimension in landscape: phones are 640–930px
   wide but only 320–430px tall. The width queries earlier in this
   file handle narrow desktop windows; everything below keys off
   height and pointer type.
   ================================================================ */

/* Dynamic viewport — mobile browser chrome collapse */
@supports (height: 100dvh) {
    html, body { height: 100dvh; }
}

/* ── Touch devices — comfortable targets ────────────────────────
   Apple asks 44px, Google 48dp; balanced pragmatically against a
   dense toolbar. Tap zones + swipe remain the primary navigation. */
@media (pointer: coarse) {
    .icon-btn         { width: 40px; height: 40px; }
    .nav-btn          { min-width: 38px; min-height: 36px; }
    .action-btn       { min-height: 34px; padding: 6px 14px; }
    .close-btn        { width: 40px; height: 40px; }
    .star-btn         { font-size: 24px; padding: 2px 4px; }
    .page-input       { padding: 6px; }
    .scrubber         { padding: 14px 0; }
    .scrubber-dot     { width: 18px; height: 18px; }
    .scrubber-bm-dot  { width: 10px; height: 10px; }
    .nav-collapse-btn { padding: 6px 18px; }
    .nav-toggle       { width: 76px; }
}

/* ── Phone landscape (≤560px tall) — slim chrome, image first ── */
@media (max-height: 560px) {
    body { --nav-h: 128px; --top-h: 42px; }

    .top-bar__logo  { font-size: 18px; }
    .top-bar__title { font-size: 13px; }
    .nav-menu-label { display: none; }

    .nav-row--thumbs   { padding-top: 5px; padding-bottom: 2px; }
    .thumb-item        { height: 46px; }
    .thumbs-content-wrapper { --cover-tile-w: 40px; }
    .thumb-num         { font-size: 8px; padding: 1px 0; }
    .nav-row--info     { padding-top: 0; padding-bottom: 2px; }
    .nav-row--scrubber { padding-bottom: 4px; }
    .scrubber          { padding: 10px 0; }

    /* Ribbon scales down so it doesn't dominate a short screen */
    .ribbon-zone { width: 56px; height: 86px; }
    .page-ribbon { width: 42px; height: 76px; right: 7px; }

    /* Help card must fit a ~360px-tall screen: two columns + scroll */
    .help-card {
        padding: 18px 22px 14px;
        width: min(640px, 94vw);
        max-height: 92vh;
        overflow-y: auto;
    }
    .help-header    { margin-bottom: 12px; }
    .help-logo      { width: 26px; height: 26px; margin: 0 auto 4px; }
    .help-header h2 { font-size: 17px; }
    .help-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0 18px;
        margin-bottom: 14px;
    }
    .help-item   { padding: 8px 0; }
    /* The Other-keys box is the table's last row, MERGED across both
       columns (a colspan) — one wide box, the keys mapped across it. */
    .help-item--keys { grid-column: 1 / -1; }
    .help-got-it { padding: 9px; font-size: 14px; margin-bottom: 8px; }

    .overlay-panel__header    { padding: 10px 16px 9px; }
    .overlay-panel__header h2 { font-size: 15px; }
    .overlay-panel__body      { padding: 12px 16px; }

    .zoom-exit-btn { width: 42px; height: 42px;
                     bottom: calc(14px + env(safe-area-inset-bottom, 0px)); }
    .toast { font-size: 12px; padding: 7px 14px; }
}

/* Phones: drop the ±15 jump buttons — 8 wide touch targets don't fit
   beside the strip; the scrubber and Contents grid cover long jumps */
@media (max-height: 560px) and (pointer: coarse) {
    #btnBack15, #btnFwd15 { display: none; }
}

/* ── Short phones (≤400px tall, e.g. 360px Androids) ──────────── */
@media (max-height: 400px) {
    body { --nav-h: 116px; --top-h: 38px; }
    .thumb-item { height: 40px; }
    .thumbs-content-wrapper { --cover-tile-w: 46px; }
    .nav-btn    { min-height: 32px; }
    .action-btn { min-height: 30px; font-size: 11px; padding: 4px 10px; }
    .help-text span { font-size: 11px; }
}

/* ── Notched phones — keep chrome clear of sensor housings and the
      home indicator (landscape insets sit left/right/bottom) ───── */
@supports (padding: max(0px)) {
    .top-bar {
        padding-left:  max(16px, env(safe-area-inset-left));
        padding-right: max(16px, env(safe-area-inset-right));
    }
    .nav-panel {
        height: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px));
        padding-left:   env(safe-area-inset-left);
        padding-right:  env(safe-area-inset-right);
        padding-bottom: env(safe-area-inset-bottom, 0);
    }
    .viewer-main {
        bottom: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px));
    }
    body.immersive .toast,
    body.zoom-mode .toast {
        bottom: calc(46px + env(safe-area-inset-bottom, 0px));
    }
}

/* ── Portrait on touch devices — landscape-only app ───────────── */
.rotate-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 2000;
    background: var(--bg) url('../images/noise.png');
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    text-align: center;
    padding: 24px;
}
.rotate-overlay__icon {
    font-size: 46px;
    animation: rotate-phone 1.8s ease-in-out infinite;
}
@keyframes rotate-phone {
    0%, 25%   { transform: rotate(0deg); }
    65%, 100% { transform: rotate(90deg); }
}
.rotate-overlay__text { font-size: 17px; color: var(--accent); font-weight: 500; }
.rotate-overlay__sub  { font-size: 12.5px; color: var(--text-dim); }

/* The `hidden` ATTRIBUTE must actually hide things.
   The UA sheet says [hidden] { display: none }, but that is the weakest rule
   there is — any `display: flex` on the same element beats it. Both book-row
   classes are flex, so `row.hidden = true` from the search filters did nothing
   at all until this rule existed. (The storage manager's own filter had been
   silently broken by exactly this.) */
[hidden] {
    display: none !important;
}

/* ══ POPUP BOXES ══════════════════════════════════════════════════
   One shell for the four destinations opened from the Settings hub:
   Preferences, Library Catalogue, Book & Cache Manager, Book Info.

   z-index sits ABOVE the overlay panels (1701) and their backdrop (1700) —
   a popup is opened FROM the Settings panel and must cover it — but below
   the toast (1800), so a "saved" message stays readable over an open popup.
   Each popup carries its own scrim, so it never touches #overlayBackdrop;
   that stays owned by the side panels. */
/* ⚠ THIS RULE DEFEATS loading="lazy" ON EVERY IMAGE INSIDE A POPUP.
   Measured 2026-08-09 against the live host (see
   `.systems/actions/REPORT-load-path-2026-08-09.md`).

   A closed popup is hidden with opacity:0 + pointer-events:none — NOT
   display:none — while still being position:fixed;inset:0. So its images
   have a real layout box that covers the whole viewport, the browser
   evaluates them as in-viewport, and lazy loading never defers anything.

   Cost as it stands: bhaja-meditating.jpg (201KB) + buddha-stone.jpg
   (103KB) + anandajoti-angkor-720.webp (83KB) = ~387KB of decorative
   photography fetched on EVERY cold load, from popups the reader has not
   opened, competing with the 27 boot scripts on the same HTTP/2 pipe. All
   three were observed starting at ~5.2s in a real cold load with every
   popup closed.

   Why it is left as-is for now: opacity is what makes the 0.2s fade work,
   and display:none cannot be transitioned. The real fixes are `content-
   visibility: hidden` on the closed state, or moving the <img> src to
   data-src and assigning it when the popup first opens (the launch screen
   already uses exactly that data-src trick — see app.htm's splash markup).
   Either is a behaviour change; neither is a CSS tweak. Do not simply add
   display:none — it will kill the fade and the transition on .popup__card. */
.popup {
    position: fixed;
    inset: 0;
    z-index: 1750;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, 0.7);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s var(--ease);
}
.popup.active {
    opacity: 1;
    pointer-events: auto;
}

.popup__card {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 560px;
    max-height: 88vh;
    border-radius: 14px;
    border: 1px solid var(--border, rgba(200, 169, 110, 0.18));
    background: var(--bg) url('../images/noise.png');
    color: var(--text);
    box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
    transform: translateY(8px) scale(0.99);
    transition: transform 0.2s var(--ease);
}
.popup.active .popup__card {
    transform: none;
}

/* The shelf and the manager carry lists of up to a hundred books. */
.popup__card--wide {
    max-width: 860px;
}

/* The header does NOT scroll — with 100 books you must always be able to
   reach the ✕ without scrolling back to the top. */
.popup__header {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 18px 12px 22px;
    border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.08));
}
.popup__title {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--accent);
    display: flex;
    align-items: center;
    gap: 8px;
}
.popup__title-icon {
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
}

/* ☸ U+2638 set as TEXT, not as an icon font's guess. Caudex has no glyph for
   it and neither does any system-ui face, so without naming the symbol fonts
   the mark is a different shape on every platform — which is what the traced
   SVG was built to avoid. FE0E after the character forces text presentation,
   so it takes currentColor instead of turning into a colour emoji on the
   platforms that default that way. Sized per use below: a glyph takes
   font-size, where the SVG it replaces took width/height. */
.glyph-wheel {
    font-family: "Apple Symbols", "Segoe UI Symbol", "Noto Sans Symbols 2",
                 "Noto Sans Symbols", "DejaVu Sans", sans-serif;
    font-weight: normal;
    line-height: 1;
    display: inline-block;
}
.popup__title-icon.glyph-wheel { width: auto; height: auto; font-size: 18px; }
.help-logo.glyph-wheel         { width: auto; height: auto; font-size: 36px; }
.app-notice__icon .glyph-wheel { font-size: 18px; }
.popup__close {
    flex: 0 0 auto;
    /* 40px: a real touch target, and big enough to find at a glance. Every
       one of these can now be opened from a bare keyboard shortcut (1-0),
       so "where do I click to get out" has to answer itself. Escape closes
       them too — see the Escape case in _bindKeyboard. */
    width: 40px;
    height: 40px;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--text-dim);
    font-size: 21px;
    line-height: 1;
    cursor: pointer;
    transition: background var(--t), color var(--t);
}
.popup__close:hover {
    background: var(--bg-thumb);
    color: var(--text);
}

.popup__body {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 16px 22px 22px;
}

/* A popup body holding a framed web page rather than our own markup.
   No padding: the page brings its own margins, and ours would read as a
   second, mismatched border around someone else's design. The frame does
   its own scrolling, so this must NOT scroll too — two nested scrollers on
   a phone is the thing that makes a frame feel broken. */
.popup__body--frame {
    padding: 0;
    overflow: hidden;
    display: flex;
    min-height: 0;
}

.webframe {
    flex: 1 1 auto;
    width: 100%;
    border: 0;
    /* Bhante's pages are light. Fixing the backdrop to white stops a flash of
       our dark panel through the frame before his CSS has painted. */
    background: #fff;
}

/* Loading and offline states. Centred in the same box the frame would fill,
   so the popup does not change size as it resolves. */
.webframe-state {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 32px 26px;
    text-align: center;
    color: var(--text-muted, #b9a88a);
}

.webframe-state p {
    margin: 0;
    max-width: 34em;
    line-height: 1.5;
}

.webframe-openout {
    margin-top: 6px;
    color: var(--accent, #c8a96e);
}

/* Phone: the popup becomes the screen. */
@media (max-width: 640px) {
    .popup {
        padding: 0;
    }
    .popup__card,
    .popup__card--wide {
        max-width: 100%;
        width: 100%;
        height: 100%;
        max-height: 100%;
        border: none;
        border-radius: 0;
    }
    .popup__header {
        padding-top: calc(16px + env(safe-area-inset-top, 0px));
    }
    .popup__body {
        padding-bottom: calc(22px + env(safe-area-inset-bottom, 0px));
    }
}

/* ══ TITLE QUICK-MENU — click the book title (top-left) to jump to another
   book without opening the full Library shelf. Same var()-themed CSS system
   as the popups above, instead of the raw inline style.cssText it used to
   be built from. */
/* One hit target for the title AND its caret — not two separately-clickable
   elements with a dead zone between them. Negative margin cancels the added
   padding so the title's own left edge doesn't visually shift. */
.title-trigger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 9px;
    margin: -6px -9px;
    border-radius: 8px;
    cursor: pointer;
    min-width: 0;    /* lets .top-bar__title inside it actually shrink/ellipsize */
    -webkit-tap-highlight-color: transparent;
    transition: background var(--t) var(--ease);
}
.title-trigger:hover,
.title-trigger:focus-visible {
    background: var(--bg-thumb);
}
.title-trigger:hover .top-bar__title,
.title-trigger:focus-visible .top-bar__title {
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: var(--accent);
}
.title-trigger:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; }
.title-menu-caret {
    flex: 0 0 auto;    /* never squeezed out — it's the "this opens something" cue */
    color: var(--accent);
    font-size: 0.8em;
}
.title-menu {
    position: fixed;
    top: 44px;
    left: 10px;
    z-index: 1751;
    max-height: 72vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    min-width: 240px;
    /* Reserves a fixed 70px on the right (left:10px position + the top-bar's
       right-side icon cluster) so the dropdown can never extend under/over
       those icons on a narrow screen — a raw vw percentage doesn't account
       for that fixed-width reserved zone, which is what let it overlap. */
    max-width: min(calc(100vw - 70px), 440px);
    padding: 6px;
    border-radius: var(--radius);
    background: var(--bg-panel);
    color: var(--text);
    border: 1px solid var(--border-mid);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
    font: 14px/1.35 -apple-system, system-ui, "Segoe UI", sans-serif;
}
.title-menu__head {
    padding: 6px 8px;
    color: var(--text-dim);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.title-menu__empty {
    padding: 8px;
    color: var(--text-dim);
}
/* .title-menu__row is a plain flex wrapper — .title-menu__main (the book
   itself, clickable) plus an optional .title-menu__caret-btn when the book
   has chapters, kept as a separate button so the caret's click never
   triggers the "jump to book" one. */
.title-menu__row {
    display: flex;
    align-items: center;
    gap: 2px;
}
/* The open book is pinned first (see _toggleTitleMenu) — a rule under it
   marks where "the current book" ends and the plain A-Z list begins, so the
   pin doesn't read as a random book out of alphabetical order. */
.title-menu__row.is-current {
    padding-bottom: 6px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--border);
}
.title-menu__main {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
    text-align: left;
    padding: 7px 9px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--text);
    cursor: pointer;
    font: inherit;
    transition: background var(--t);
}
.title-menu__main:hover { background: var(--bg-thumb); }
.title-menu__row.is-current .title-menu__main {
    background: var(--accent-dim);
    font-weight: 600;
}
.title-menu__row.is-current .title-menu__main:hover { background: var(--accent-dim); }

/* The pinned copy of the current book (grouped mode only) — a muted "you are
   here" marker, deliberately NOT the accent highlight used below in its own
   section, so the two readings ("pinned because it's open" vs "found here
   alphabetically") don't look like the same kind of thing. */
.title-menu__row.is-pinned .title-menu__main {
    background: var(--bg-thumb);
    color: var(--text-dim);
    font-weight: 500;
    cursor: default;
}
.title-menu__row.is-pinned .title-menu__main:hover { background: var(--bg-thumb); }

/* Small, neat section labels — this is a compact dropdown, not the full
   Catalogue, so headings stay subdued rather than repeating .lib-series'
   larger treatment. */
.title-menu__section {
    padding: 10px 9px 3px;
    color: var(--text-dim);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.title-menu__section:first-of-type { padding-top: 4px; }

/* ★ book favourite — same look and behaviour as .title-menu__chapter-star
   below, so starring reads identically whether it's a book or a chapter. */
.title-menu__star {
    flex: 0 0 auto;
    width: 18px;
    text-align: center;
    color: var(--text-dim);
    cursor: pointer;
}
.title-menu__star.is-on { color: var(--accent); }

/* Fixed-width leaf slot — same fix as .lib-recommend in library.css: always
   reserve the column so titles without a leaf don't sit further right than
   titles with one. */
.title-menu__leaf {
    flex: 0 0 auto;
    width: 20px;
    text-align: center;
    visibility: hidden;
}
.title-menu__leaf.is-on { visibility: visible; }
.title-menu__label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The expand caret for a multi-chapter book, and its chapter list — a
   bordered icon button (not a bare glyph), mirroring .lib-chapters-toggle /
   .lib-chapters in library.css so both menus read the same way. Collapsed
   points right; rotates to point down once the chapter list is open below. */
.title-menu__caret-btn {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    margin-right: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-mid);
    border-radius: 6px;
    background: transparent;
    color: var(--accent);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    transition: background .15s, border-color .15s, transform .15s;
}
.title-menu__caret-btn:hover { background: var(--bg-thumb); border-color: var(--accent); }
.title-menu__caret-btn[aria-expanded="true"] { transform: rotate(90deg); }
.title-menu__chapters {
    margin: 0 0 4px 22px;
    padding-left: 8px;
    border-left: 1px solid var(--border);
    font-size: 12.5px;
    color: var(--text-dim);
}
/* Same grouping heading as Contents (.toc-group), at this menu's scale. */
.title-menu__chapter-group {
    margin: 8px 0 2px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
}
.title-menu__chapter-group:first-child { margin-top: 0; }

.title-menu__chapter {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    text-align: left;
    padding: 5px 6px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text);
    cursor: pointer;
    font: inherit;
    font-size: 12.5px;
}
.title-menu__chapter:hover { background: var(--bg-thumb); }
.title-menu__chapter-star {
    flex: 0 0 auto;
    width: 16px;
    text-align: center;
    color: var(--text-dim);
}
.title-menu__chapter-star.is-on { color: var(--accent); }
.title-menu__chapter-label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.title-menu__foot {
    display: block;
    width: 100%;
    text-align: left;
    margin-top: 4px;
    padding: 8px 9px;
    border: 0;
    border-top: 1px solid var(--border);
    background: transparent;
    color: var(--accent);
    cursor: pointer;
    font: inherit;
}

/* ══ THE SETTINGS HUB — two columns ═══════════════════════════════
   Doors on the left, who-made-this on the right.

   The panel is wider than the others on purpose. Stacked in the standard
   380px strip, the credits sat below five menu rows — off the fold, read as
   an afterthought, and cramped. They are not an afterthought: they are the
   attribution for the whole collection. Side by side, both are simply there
   the moment Settings opens, which is the whole point of putting About here
   rather than behind another click. */
.overlay-panel--hub { width: min(760px, 96vw); }

.hub-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 30px;
    align-items: start;
}

.hub-about {
    min-width: 0;
    padding-left: 26px;
    border-left: 1px solid var(--border);
}

/* ══ THE HUB MENU — the doors in the Settings panel ═══════════════ */
.hub-menu {
    margin: -4px 0 22px;
}

.hub-item {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 12px 8px;
    border: none;
    border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.08));
    border-radius: 8px;
    background: none;
    color: var(--text);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: background var(--t);
}
.hub-item:hover {
    background: var(--accent-dim);
    border-bottom-color: var(--accent);   /* the gold underline is the hover cue now */
}
.hub-item.hidden {
    display: none;
}

.hub-item__icon {
    flex: 0 0 auto;
    font-size: 18px;
    line-height: 1;
}

/* Icon-set switch (ICON_STYLE, constants.js) — both the old emoji and the
   new SVG line-icon markup are always in the DOM for any icon that's been
   converted; this just shows one and hides the other, body-wide, so
   reverting the whole set back is a one-line change, not a re-edit. */
.icon-outline { display: none; }
body.icon-style-outline .icon-outline { display: inline-flex; }
body.icon-style-outline .icon-emoji   { display: none; }
body.icon-style-emoji   .icon-emoji   { display: inline; }
body.icon-style-emoji   .icon-outline { display: none; }
.hub-item__text {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.hub-item__label {
    font-size: 15px;
    font-weight: 500;
    color: var(--accent);
}
.hub-item__sub {
    font-size: 12px;
    color: var(--text-dim);
}
.hub-item__go {
    flex: 0 0 auto;
    font-size: 20px;
    color: var(--text-dim);
}

.hub-about__head {
    margin: 0 0 9px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
}

/* ── Small screens: the hub is DOORS-ONLY ─────────────────────────
   A menu fits its screen (the mobile rule the old stacked layout broke:
   About under the doors made a five-item menu a three-screen scroll).
   The About column hides and its sixth door appears; the content opens
   as its own screen (popupAbout).

   NOTE — these overrides must sit BELOW the base .hub-item rules: they
   are equal in specificity, so only source order lets them win. Placed
   above, the desktop door leaked visible and the phone trim never
   applied.

   Two triggers, not one: narrow (portrait tablets, slim windows) OR
   short-AND-TOUCH (phones in landscape — the only way a phone runs this
   app, so max-width alone would never fire for them: a landscape phone
   is 740–900px WIDE). The touch condition is load-bearing: without it a
   short desktop window or a zoomed browser lost the side About column,
   which must NEVER leave a big screen. */
.hub-item--about { display: none; }

@media (max-width: 700px), (pointer: coarse) and (max-height: 560px) {
    .hub-layout { grid-template-columns: 1fr; }
    .hub-layout > .hub-about { display: none; }
    .hub-item--about { display: flex; }
    .hub-menu { margin-bottom: 0; }

    /* Rows read oversized at phone density: 12px → 8px vertical trims
       them ~59px → ~51px — still above the 44pt/48dp touch floor.
       Desktop keeps its ease. */
    .hub-item { padding: 8px; }
}

/* The shortest phones (360px-tall landscape): six 52px doors are ~36px
   more than the panel body. 6px pads bring rows to ~48px — the 48dp
   floor exactly — and with the body's vertical padding at 8px the menu
   fits the 300px body to the pixel: -8 + 292 + 8 + 8 = 300. */
@media (pointer: coarse) and (max-height: 400px) {
    .hub-item { padding: 6px 8px; }
    .hub-menu { margin-top: -8px; }
    .overlay-panel--hub .overlay-panel__body {
        padding-top: 8px;
        padding-bottom: 8px;
    }
}

/* The borrowed column inside its popup: the popup header carries the
   title and the body carries the padding, so the column sheds its own. */
#popupAbout .hub-about { padding: 0; border: none; }
#popupAbout .hub-about__head { display: none; }

/* Touch: secondary lines at 12px are squint-size at arm's length.
   (.more-item__sub is defined much further down — same source-order rule
   applies, hence the id-free but LATE placement is done there too.) */
@media (pointer: coarse) {
    .hub-item__sub { font-size: 13px; }
}

/* (The five-click credit's pointer-free / unselectable look now lives on
   .about-credit — the whole App-copyright block is the trigger.) */
#zipEggRow.hidden {
    display: none;
}
/* While the ZIP download button is on show, the "write to Bhante" route is
   redundant — the reader already has the better door open. Pure CSS, so all
   three places that toggle the egg stay in sync automatically. */
#aboutZipRow:has(#zipEggRow:not(.hidden)) #zipMailText {
    display: none;
}

/* A setting that is announced but not yet built, OR one that has no meaning
   while its parent switch is off (the auto-play speed). Dimmed and inert —
   never a live-looking control that silently does nothing. */
.setting-row--disabled {
    opacity: 0.5;
}
.setting-row--disabled .setting-select {
    pointer-events: none;
}

/* A key named inside a setting's own description. The shortcut is taught HERE,
   at the control it operates, rather than in a help card the reader has to go
   and find. */
.kbd-inline {
    display: inline-block;
    min-width: 1.4em;
    padding: 1px 5px;
    border: 1px solid var(--border, rgba(200, 169, 110, 0.3));
    border-radius: 4px;
    background: var(--bg-thumb);
    color: var(--accent);
    font-family: Consolas, monospace;
    font-size: 11px;
    line-height: 1.5;
    text-align: center;
}

/* Auto-play speed. A native <select> so it takes the platform's own picker on a
   phone — a hand-rolled dropdown would be one more thing to get wrong on touch. */
.setting-select {
    flex: 0 0 auto;
    padding: 6px 10px;
    border: 1px solid var(--border, rgba(200, 169, 110, 0.22));
    border-radius: 7px;
    background: var(--bg-thumb);
    color: var(--text);
    font-family: inherit;
    font-size: 13.5px;
    cursor: pointer;
    transition: border-color var(--t);
}
.setting-select:focus {
    outline: none;
    border-color: var(--accent);
}
.setting-select:disabled {
    cursor: default;
}
.setting-sub {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--text-dim);
}
/* The consequence of a setting, as against a description of it. Same size and
   position as .setting-sub so the row's rhythm is unchanged — only the colour
   and a little top margin separate it, which is enough for it to read as a
   caution without shouting. */
.setting-sub--warn {
    margin-top: 6px;
    color: var(--bookmark-col, #e8a020);
}

.setting-soon {
    flex: 0 0 auto;
    font-size: 12px;
    font-style: italic;
    color: var(--text-dim);
    white-space: nowrap;
}

/* One PDF or twenty — the Dhammapada commentary ships a PDF per part.
   A wrapping row, so a long list of parts stays compact instead of becoming a
   column of twenty full-width buttons. */
.about-files {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 4px;
}

/* ══ ABOUT PHOTO DHARMA ═══════════════════════════════════════════
   The portrait and the name are SHORTCUTS into the popup, not the only way in
   — the "Read more" link below them is the signpost. Both get a pointer and a
   hover so they read as clickable; neither is load-bearing. */
.about-person__link {
    cursor: pointer;
    transition: opacity var(--t), color var(--t);
}
.about-person__link:hover {
    opacity: 0.85;
}
img.about-person__link:hover {
    opacity: 1;
    box-shadow: 0 0 0 2px var(--accent);
}
/* The name is a <button> so it is keyboard-reachable — strip the chrome and
   give it back the heading it looks like. */
button.about-person__name {
    display: block;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    text-align: left;
    color: inherit;
}
button.about-person__name:hover {
    color: var(--accent);
}

.about-blurb {
    margin: 2px 0 2px;   /* sit close under the social buttons — no big gap */
}
.about-more {
    display: inline;
    margin-left: 6px;             /* flows at the end of the blurb's last line */
    padding: 0;
    border: none;
    background: none;
    color: var(--accent);
    font-family: inherit;
    font-size: 13px;
    cursor: pointer;
    white-space: nowrap;
    transition: opacity var(--t);
}
.about-more:hover {
    opacity: 0.75;
    text-decoration: underline;
}

/* The photograph is the reason this is a popup rather than a link out. Let it
   run the full width of the card. */
/* The collection's own description, at the head of About Photo Dharma —
   what the app is, before whose work it is. Set as a pulled quote so it reads
   as a statement rather than another paragraph of body copy. */
.pd-slogan { margin: 0 0 22px; }
/* "About Photo Dharma" is now a section head inside the popup, not its title —
   the app leads, Bhante's collection follows. */
/* What the app does, under what the collection is. Body copy, not a quote —
   it is practical information, so it should not compete with Bhante's words. */
.pd-slogan__what {
    margin: 14px 0 0;
    font: 14px/1.6 -apple-system, system-ui, "Segoe UI", sans-serif;
    color: var(--text-dim, #b9b2a6);
}
body.light-mode .pd-slogan__what { color: #4a453d; }

.pd-subhead {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 28px 0 16px;
    padding-top: 20px;
    border-top: 1px solid rgba(200, 169, 110, .22);
    font: 600 16px/1.3 Georgia, "Times New Roman", serif;
    /* Was var(--gold, #c8a96e). --gold is declared nowhere, so this always
       rendered as the fallback — which is --accent's exact value, making the
       token a no-op that only looked like a decision. Named properly now so
       it follows a theme change instead of staying gold in a blue one. */
    color: var(--accent);
    letter-spacing: .02em;
}
/* The rule runs on past the words, so the head reads as the start of a
   section rather than a line of bold text floating above a photo. */
.pd-subhead::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(to right, rgba(200,169,110,.28), transparent);
}
.pd-subhead .pd-subhead__mark { font-size: 18px; line-height: 1; }
body.light-mode .pd-subhead { color: #a8460a; }
.pd-slogan__quote {
    margin: 0;
    padding: 0 0 0 14px;
    border-left: 2px solid rgba(200, 169, 110, .45);
    font: italic 15px/1.62 Georgia, "Times New Roman", serif;
    color: var(--text-dim, #b9b2a6);
}
body.light-mode .pd-slogan__quote {
    border-left-color: rgba(168, 70, 10, .35);
    color: #4a453d;
}

.pd-figure {
    margin: 0 0 16px;
}
.pd-photo {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 8px;
    background: var(--bg-thumb);
}
.pd-credit {
    margin-top: 7px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--text-dim);
}

/* ── About's foot image — the (portrait-shaped) photo behind the Bhaja Caves
   360°, centred and constrained, with a hover cue that tapping opens it
   full-resolution. The interactive 360° itself is the caption link below. */
.pd-figure--foot {
    margin-top: 22px;
    padding-top: 18px;
    border-top: 1px solid var(--border);
    text-align: center;
}
.pd-360 {
    position: relative;
    display: inline-block;
    max-width: 320px;
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 8px 26px rgba(0, 0, 0, .35);
}
.pd-figure--foot .pd-photo { border-radius: 10px; transition: transform .35s ease; }
.pd-360:hover .pd-photo { transform: scale(1.04); }
.pd-figure--foot .pd-credit { text-align: center; }
.pd-figure--foot .pd-credit strong { color: var(--text); }
.pd-figure--foot .pd-credit a { color: var(--accent); text-decoration: none; }
.pd-figure--foot .pd-credit a:hover { text-decoration: underline; }

.pd-links {
    flex-wrap: wrap;
}
/* The Photo Dharma popup's "Elsewhere" / "Search the collection" links are
   TEXT links — they share .about-links with the person-block's icon circles,
   so undo the 30px circle geometry or the text gets crushed into buttons.
   The DOUBLE class matters: a plain `.pd-links a` ties with `.about-links a`
   on specificity and loses on source order (that was the "still garbled" bug —
   the fix existed but was being overridden from later in this file). */
.about-links.pd-links a {
    display: inline-block;
    width: auto;
    height: auto;
    padding: 5px 11px;
    border: 1px solid var(--border-mid);
    border-radius: 999px;
    font-size: 12px;
    color: var(--text-dim);
    text-decoration: none;
    white-space: nowrap;
}
.about-links.pd-links a:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-dim, rgba(200, 169, 110, .1));
}

/* About popup link rows — plain text, no pills, no circles */
.full-about-links {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    margin-top: 4px;
    margin-bottom: 2px;
}
.full-about-links a {
    display: inline;
    padding: 0;
    border: none;
    border-radius: 0;
    background: none;
    font-size: 13px;
    line-height: 1.6;
    color: var(--accent);
    text-decoration: none;
}
.full-about-links a:hover {
    text-decoration: underline;
}

/* Breathing room between sections in the About popup only */
#aboutPanel .about-block + .about-block {
    margin-top: 14px;
}

#aboutPanel .about-text {
    text-align: justify;
    margin: 0 0 10px !important;  /* !important overrides the universal * { margin:0 } reset — revisit when that reset is scoped */
}
#aboutPanel .about-text:last-child {
    margin-bottom: 0 !important;
}

.about-para {
    margin-bottom: 10px;
}

/* ══ INSTALL STATE — Book & Cache Manager ═════════════════════════
   On iOS an uninstalled PWA has its books evicted after 7 days. That is the
   most destructive thing that can happen here, so the state is stated plainly
   at the top of the manager rather than left to a nudge that may never fire. */
.install-state {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 8px;
    margin-bottom: 14px;
    padding: 8px 12px;
    border-radius: 9px;
    border: 1px solid var(--border, rgba(200, 169, 110, 0.18));
    background: var(--bg-thumb);
    font-size: 12px;
    line-height: 1.45;
}
.install-state--ok {
    /* Was a hardcoded rgba(120,180,120,.35) green — same defect as the
       Catalogue pills: unthemed, identical in both palettes, and the only
       green in either. Reads off --ok now, so it follows the theme. */
    border-color: var(--ok);
}
.install-state--warn {
    border-color: rgba(200, 150, 80, 0.45);
}
.install-state__head {
    width: 100%;                   /* title owns its own line, body under it */
    font-weight: 600;
    color: var(--accent);
}
.install-state__body {
    color: var(--text-dim);
}
.install-state__btn {
    margin-left: auto;             /* sits at the right edge, clear of the text */
    padding: 4px 12px;
    font-size: 12px;
}
.install-state__btn--danger {
    color: #c8705e;
    border-color: rgba(200, 112, 94, .45);
}
.install-steps {
    display: flex;
    flex-direction: column;
    gap: 5px;
    flex-basis: 100%;
    margin-top: 2px;
}
.install-steps__row {
    display: flex;
    align-items: center;
    gap: 8px;
}
.install-steps__icon {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    color: var(--accent);
}
.install-steps__text {
    color: var(--text-dim);
}
.install-state__btn--danger:hover {
    color: #fff;
    background: #b3543f;
    border-color: #b3543f;
}
.install-state__link {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    cursor: pointer;
    color: var(--accent);
    font-size: 12px;
    text-decoration: none;
    white-space: nowrap;
}
.install-state__link:hover { text-decoration: underline; }

/* ══ BOOK INFORMATION ═════════════════════════════════════════════
   WHAT the book is, above what you can DO with it, above its copyright.
   The page used to open straight into a licence notice with no book in
   sight — which is why the book's own details read as missing. */
.book-facts {
    display: flex;
    gap: 14px;
    align-items: flex-start;
    padding-bottom: 16px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.08));
}
.book-facts__cover {
    flex: 0 0 auto;
    width: 96px;
    height: 60px;               /* the posters are 16:9 */
    object-fit: cover;
    border-radius: 4px;
    background: var(--bg-thumb);
}
/* bhikkhu-sumedha's cover is a portrait photo, not a 16:9 poster — see the
   same rule in library.css for the full explanation. */
img[src*="bhikkhu-sumedha/cover.jpg"] { object-position: center top; }
.book-facts__body {
    flex: 1 1 auto;
    min-width: 0;
}
.book-facts__subtitle {
    font-size: 13px;
    line-height: 1.4;
    color: var(--text);
    margin-bottom: 4px;
}
.book-facts__author {
    font-size: 13px;
    font-weight: 500;
    color: var(--accent);
    margin-bottom: 4px;
}
.book-facts__meta {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-dim);
}
/* Not another fact about the book — an explanation of why the picture on
   screen isn't the one Settings says it should be. The rule marks it as an
   aside so it doesn't read as a third line of metadata. */
.book-facts__quality-note {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-dim);
    margin-top: 6px;
    padding-left: 8px;
    border-left: 2px solid var(--border, rgba(255, 255, 255, 0.14));
}

/* The caveat belongs UNDER the button, not wrapped around it — inline, it
   snaked around the PDF link and read as part of the link's own label. */
#aboutPdfRow .about-note {
    display: block;
    margin-top: 6px;
}

/* ── Open · Offline · PDF ─────────────────────────────────────── */
.book-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 18px;
}
.book-actions__note {
    flex: 1 0 100%;
    font-size: 12.5px;
    color: var(--text-dim);
}
/* An anchor styled as an action button (e.g. "View original ↗" → photodharma.net). */
a.action-btn {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

/* The ℹ️ on a book row, in both the Catalogue and the Manager. It carries the
   row's OWN slug, which is what makes Book Information un-confusable. */
.lib-btn-info,
.sm-btn-info {
    background: none;
    border: 1px solid var(--border, rgba(200, 169, 110, 0.22));
    color: var(--text-dim);
}
.lib-btn-info:hover,
.sm-btn-info:hover {
    color: var(--text);
    border-color: var(--accent);
}
.lib-btn-info {
    align-self: flex-start;
    padding: 4px 8px;
    border-radius: 6px;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}

/* Shown when a search matches nothing. */
.lib-no-match {
    padding: 24px 8px;
    text-align: center;
    color: var(--text-dim);
    font-size: 14px;
}

/* ══ SEARCH BAR — Library and Storage overlays ════════════════════
   Sticky at the top of the overlay body so it survives a 100-book scroll.
   One component, both screens: the two lists differ, the act of finding a
   book by name does not. */
.search-bar {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: -4px 0 12px;
    padding: 10px 0;
    background: var(--bg) url('../images/noise.png');
}
.search-bar__input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 9px 12px;
    border: 1px solid var(--border, rgba(200, 169, 110, 0.22));
    border-radius: 7px;
    background: var(--bg-thumb);
    color: var(--text);
    font-family: inherit;
    font-size: 14.5px;
    transition: border-color var(--t);
}
.search-bar__input:focus {
    outline: none;
    border-color: var(--accent);
}
.search-bar__input::placeholder {
    color: var(--text-dim);
}
.search-bar__clear {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--text-dim);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    transition: background var(--t), color var(--t);
}
.search-bar__clear:hover {
    background: var(--bg-thumb);
    color: var(--text);
}
.search-bar__clear.hidden {
    display: none;
}

@media (orientation: portrait) and (pointer: coarse) {
    .rotate-overlay { display: flex; }
}

/* The .settings-group <details> accordions lived here. Settings is now a side
   panel of doors (.hub-item) plus popups (.popup), so they are gone. */

.settings-wide-btn {
    width: 100%;
    margin: 12px 0;
}
/* The way into the Library, sitting above the list of what is already here */
#openLibrary {
    margin-top: 4px;
    /* Gold text/underline moved to the shared :hover — Library no longer stays
       highlighted at rest; it matches the other doors until pointed at. */
}

/* ── About ─────────────────────────────────────────────────────
   Credits for the book, then for the app. The icons are inline SVG using
   currentColor, so they take the theme rather than fighting it — no brand
   blues and greens shouting out of a sepia panel. */

.about-text {
    font-size: 13px;
    line-height: 1.55;    /* the single cheapest "finely typeset" win */
    color: var(--text-dim);
    margin: 10px;
}
.about-text a { color: var(--accent); }
.about-text em { font-style: italic; }

.about-block {
    padding: 10px 0;
    /* hairline divider — same colour family, just quieter (almost invisible
       until needed); falls back to the plain border where color-mix is absent */
    border-top: 1px solid var(--border);
    border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
}

/* A <button> version of .about-block — strips the native button chrome so it
   reads as the same divider-topped block, but clickable. */
.about-block--link {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    border-top: 1px solid var(--border);
    border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
    font: inherit;
    cursor: pointer;
}
.about-block--link:hover .about-label,
.about-block--link:focus-visible .about-label {
    color: var(--accent);
    opacity: 1;
}

.about-label {
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-dim);
    opacity: .8;
    margin-bottom: 3px;
}

.about-link {
    font-size: 13px;
    color: var(--accent);
}
.about-note {
    font-size: 12px;
    color: var(--text-dim);
    font-style: italic;
}
.about-note:not(:empty)::before { content: ' — '; }

/* Licence badge + text as one link, findable as a single block — see
   The badge is the user's own supplied asset,
   not a generated icon. */
.licence-block__link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.licence-block__badge {
    width: 20px;
    height: auto;
    flex: 0 0 auto;
}

/* Offline-cache links: live buttons when the book is open (variant A),
   inert plain text when Book Information is showing a catalogue entry that
   is not the open book (variant B) — sending the reader back to the
   Catalogue they are already in is a loop, not a shortcut. */
.about-link--inert {
    color: var(--text);
    pointer-events: none;
    cursor: default;
    text-decoration: none;
}

/* Multi-part PDF disclosure — native <details>, no new component. */
.about-pdf-toggle > summary {
    cursor: pointer;
    list-style: none;
}
.about-pdf-toggle > summary::-webkit-details-marker { display: none; }
.about-pdf-toggle > summary::after { content: ' ▾'; }
.about-pdf-toggle[open] > summary::after { content: ' ▴'; }

/* ── Kappiya (steward) notice — a quiet accent callout under the donation links.
   A monk does not handle money, so this explains the different name a donor meets
   on the way through. Uses the theme vars, so it inks correctly in light + dark. */
.kappiya-note {
    display: flex;
    gap: 10px;
    margin-top: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border-mid);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    background: var(--accent-dim, rgba(200, 169, 110, .10));
}
.kappiya-note__icon { flex: 0 0 auto; font-size: 18px; line-height: 1.5; }
.kappiya-note__text {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--text-dim);
}
.kappiya-note__text strong { color: var(--text); font-weight: 600; }
.kappiya-note__text em { color: var(--accent); font-style: italic; }

/* ── App copyright — the developer credit. Quiet and refined, NOT a card:
   it sits under Bhante's About and must not compete with it. The whole block
   is the five-click egg, so text is unselectable (a 5-tap must never highlight)
   and there is no button chrome. */
.about-credit {
    -webkit-user-select: none;
    user-select: none;
    cursor: default;
}
/* Simple grey, smaller than Bhante's text — his About is the focus. No gold,
   no bold, no shouting. A slight gap under the title, then the three lines
   sit tight together. */
.about-credit .about-label { margin-bottom: 6px; }
.about-credit__by {
    margin: 0 0 3px;
    font-size: 11.5px;
    color: var(--text-dim);
}
.about-credit__name {
    font-weight: normal;         /* no lift — the credit stays quiet */
}
/* The Dhamma quote — the same grey family, just SLIGHTLY brighter than the
   credit text, italic. Subtle, professional. */
.about-credit__quote {
    margin: 3px 0;
    padding-left: 10px;
    font-style: italic;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-dim);                                        /* fallback */
    color: color-mix(in srgb, var(--text) 50%, var(--text-dim));   /* a touch brighter */
}
.about-credit__note {
    margin: 0;
    font-size: 11.5px;
    color: var(--text-dim);
}

/* ── Code Libraries — a slightly smaller label and line; the build stamp
   flows directly beneath so the whole tail reads line after line. */
.about-label--small { font-size: 10px; }
.lib-line { margin: 0; font-size: 11px; color: var(--text-dim); line-height: 1.6; }
.lib-line a { color: var(--text-dim); text-decoration: none; }
.lib-line a:hover { color: var(--accent); text-decoration: underline; }
/* Footer matter, and it was three lines of it. Smaller, one line, with the
   version and licence folded away until you point at one of them — hovering a
   single entry can't push the row onto a second line, hovering the whole row
   would. nowrap so a narrow column truncates rather than wrapping, which is
   the failure this was meant to fix. */
.lib-line--compact {
    font-size: 10px;
    line-height: 1.5;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* The complete third-party list, on the App Info page. Every entry names the
   thing, its version, its licence, and links the licence FILE we ship — a link
   to someone else's website is not a copy travelling with the code. */
.licence-list {
    margin: 6px 0 0;
    font-size: 12.5px;
    line-height: 1.5;
}
.licence-list dt {
    color: var(--text);
    font-weight: 600;
    margin-top: 10px;
}
.licence-list dd {
    color: var(--text-dim);
    margin: 2px 0 0;
}
.licence-list a { color: var(--accent); }

.lib-line__more { display: none; }
.lib-line--compact a:hover .lib-line__more,
.lib-line--compact a:focus-visible .lib-line__more { display: inline; }
#jszipCredit.hidden { display: none; }

/* ── Bhante ────────────────────────────────────────────────── */

.about-person {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding-bottom: 10px;
}

/* The portrait is already toned to the palette at build time (a duotone from
   the app's deep brown to its warm gold), so it needs no CSS filter and looks
   the same on every browser. */
.about-avatar {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--border-mid);
}

.about-person__body { min-width: 0; }
.about-person__name {
    font-size: 15px;
    color: var(--text);
}
.about-person__role {
    font-size: 12px;
    color: var(--text-dim);
    margin-top: 2px;
    margin-bottom: 6px;
}

.about-links {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.about-links a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    color: var(--text-dim);
    border: 1px solid var(--border-mid);
    border-radius: 50%;
    transition: color .15s, border-color .15s, background .15s;
}
.about-links a:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-dim, rgba(200, 169, 110, .1));
}

.ico {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
}

/* ── The book's PDF ────────────────────────────────────────── */

.about-file {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
    font-size: 13px;
    color: var(--accent);
    border: 1px solid var(--border-mid);
    border-radius: 6px;
    transition: border-color .15s, background .15s;
}
.about-file:hover {
    border-color: var(--accent);
    background: var(--accent-dim, rgba(200, 169, 110, .1));
}
.about-file .ico { width: 16px; height: 16px; }

/* Explains what a control does, under the control */
.setting-note {
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-dim);
    padding: 8px 0 0;
}
.setting-note strong {
    color: var(--text);
    font-weight: 500;
    white-space: nowrap;
}


/* ── ⋯ More — the small-screen menu ────────────────────────────
   The nav row carries the page counter, the star, and the action buttons.
   On a phone there is no room for ⬇ + Contents + Bookmarks + Settings beside
   the counter, so below 560px they all collapse into one button and the Menu
   panel routes to each — written out properly, with a line saying what each
   one is.

     > 560px   ⬇ · 📖 Contents · 🔖 Bookmarks · ⚙ Settings
     ≤ 560px   ⋯ More
*/
.more-btn { display: none; }

@media (max-width: 560px) {
    .panel-btn { display: none; }
    .more-btn  { display: inline-flex; }
}

.more-item {
    display: grid;
    grid-template-columns: 32px 1fr;
    grid-template-rows: auto auto;
    align-items: center;
    gap: 0 12px;
    width: 100%;
    padding: 8px 4px;      /* this menu only exists ≤560px — phone density (~51px rows, above the 48dp floor) */
    text-align: left;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--border);
    color: var(--text);
    font-family: inherit;
    cursor: pointer;
}
.more-item:last-child  { border-bottom: none; }
.more-item:hover       { background: var(--accent-dim, rgba(128, 128, 128, .06)); }
.more-item:disabled    { opacity: .4; cursor: default; }
.action-btn:disabled   { opacity: .4; cursor: default; }

.more-item__icon {
    grid-row: 1 / 3;
    font-size: 20px;
    text-align: center;
}
.more-item__label { font-size: 15px; }
.more-item__sub {
    font-size: 12px;
    color: var(--text-dim);
    margin-top: 2px;
}
/* Touch: readable at arm's length (below the base rule so it wins). */
@media (pointer: coarse) {
    .more-item__sub { font-size: 13px; }
}

/* Bhante's CC statement in the Save-this-page panel: a quiet colophon under
   the two buttons (the hairline above it is the last row's own border). */
/* The licence colophon: LEFT-aligned so it flows with the menu rows above
   it — badge first, then three whole lines, none wrapping mid-phrase. */
.save-licence {
    margin: 16px 0 4px;
    text-align: left;
}
.save-licence__badge {
    display: inline-block;
}
/* The brown badge is inked FOR this theme — it needs no dimming, just a
   gentle lift under the pointer. */
.save-licence__badge img {
    display: block;
    border-radius: 5px;
    opacity: 0.9;
    transition: opacity var(--t);
}
.save-licence__badge:hover img { opacity: 1; }

.save-licence__text {
    margin: 8px 0 0;
    font-size: 11px;
    line-height: 1.5;
    color: var(--text-dim);
}
/* Each piece is its own line, whole — it never breaks mid-phrase. */
.save-licence__text span,
.save-licence__text a {
    display: block;
    white-space: nowrap;
    margin-top: 3px;
}
/* The licence name links to the deed — quiet, not a gold call-to-action. */
.save-licence__text a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.save-licence__text a:hover { color: var(--accent); }

/* ================================================================
   DEV BENCH — the 5-click knock-code popup (see app.htm)
   ================================================================
   Deliberately unlike the rest of the app: a compact golden instrument
   box, monospace, semi-transparent over whatever is behind it. The form
   is carried over from the original Slide Bench (dev.html in the
   EARLY_MASTER backup) — segmented pills rather than checkboxes, so the
   current value AND the alternatives read at a glance, plus a live stat
   strip. Scoped entirely under .popup__card--dev so not one rule here can
   reach a reader-facing panel.
   ================================================================ */
.popup__card--dev {
    width: min(420px, 94vw);
    background: rgba(28, 20, 12, 0.9);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1px solid rgba(200, 169, 110, 0.28);
    border-radius: 20px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.7), inset 0 0 0 1px rgba(200, 169, 110, 0.08);
    color: #d8c8a0;
    font: 12px/1.5 'SF Mono', Menlo, Consolas, monospace;
}
.dev-title {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 14px 10px 22px;
    font-size: 11px;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: #b89f6e;
    border-bottom: 1px solid rgba(200, 169, 110, 0.2);
}
.dev-title__build {
    flex: 1;
    text-align: right;
    font-size: 10px;
    letter-spacing: 1px;
    opacity: 0.5;
    text-transform: none;
}
.dev-title .popup__close { color: #b89f6e; }

.dev-body { padding: 14px 22px 18px; }

.dev-group {
    font-size: 10px;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    color: #8a7550;
    margin: 14px 0 8px;
}
.dev-group:first-child { margin-top: 0; }
.dev-group__note { letter-spacing: 0.5px; opacity: 0.7; text-transform: none; }

.dev-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 5px 0;
    border-bottom: 1px dashed rgba(200, 169, 110, 0.1);
}
.dev-row:last-of-type { border-bottom: 0; }
.dev-row[hidden] { display: none; }   /* the splash rows swap by trigger */
.dev-row__label { color: #b89f6e; letter-spacing: 0.3px; }
/* When a change actually lands — the single thing that used to need a
   sentence of explanation under every switch. */
.dev-row__when {
    flex: 1;
    font-size: 10px;
    color: #6a5a3e;
    letter-spacing: 0.5px;
}

.dev-pills { display: flex; gap: 5px; flex-wrap: wrap; margin-left: auto; }
.dev-pill {
    background: rgba(0, 0, 0, 0.5);
    padding: 3px 11px;
    border-radius: 40px;
    border: 1px solid transparent;
    font: inherit;
    font-size: 11px;
    color: #a08860;
    cursor: pointer;
    transition: background 0.1s, border-color 0.1s, color 0.1s;
}
.dev-pill:hover { color: #d8c8a0; }
.dev-pill.active {
    background: rgba(200, 169, 110, 0.2);
    border-color: rgba(200, 169, 110, 0.5);
    color: #f0e0c0;
    box-shadow: 0 0 12px rgba(200, 169, 110, 0.08);
}

.dev-stats {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    font-size: 11px;
    color: #8a7550;
    border-top: 1px solid rgba(200, 169, 110, 0.1);
    margin-top: 14px;
    padding-top: 9px;
}
.dev-stats--wide { border-top: 0; margin-top: 0; padding-top: 2px; }
.dev-stats b { color: #d8c8a0; font-weight: 400; }

.dev-actions { display: flex; gap: 8px; margin-top: 14px; }
.dev-linkbtn {
    flex: 1;
    text-align: center;
    padding: 7px 10px;
    border-radius: 40px;
    border: 1px solid rgba(200, 169, 110, 0.3);
    background: rgba(0, 0, 0, 0.35);
    color: #c8a96e;
    font: inherit;
    font-size: 11px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}
.dev-linkbtn:hover { background: rgba(200, 169, 110, 0.14); color: #f0e0c0; }

.dev-foot {
    font-size: 10px;
    color: #6a5a3e;
    letter-spacing: 0.3px;
    text-align: center;
    border-top: 1px solid rgba(200, 169, 110, 0.08);
    margin-top: 14px;
    padding-top: 9px;
}
.dev-foot kbd {
    background: rgba(0, 0, 0, 0.4);
    padding: 1px 7px;
    border-radius: 12px;
    color: #b89f6e;
}

/* ── App Information popup ──────────────────────────────────────
   What the APP is, as against what a book is (that's Book Information).
   Short by design — the image, the name, the one line that describes the
   collection, and the build. Anything longer belongs in About & Copyright. */
.appinfo-figure {
    margin: 0 0 18px;
    border-radius: 10px;
    overflow: hidden;
    line-height: 0;              /* kills the inline-image descender gap */
}
.appinfo-photo {
    width: 100%;
    height: auto;
    display: block;
    cursor: zoom-in;             /* the click really does open it larger */
    transition: transform 0.25s ease, filter 0.25s ease;
}
.appinfo-photo:hover { transform: scale(1.02); filter: brightness(1.05); }
.appinfo-name {
    font-size: 20px;
    font-weight: 600;
    color: var(--accent);
    text-align: center;
    margin-bottom: 6px;
}
.appinfo-tagline {
    font-size: 13px;
    line-height: 1.6;
    color: var(--text);
    text-align: center;
    margin-bottom: 14px;
}
.appinfo-note {
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--text-dim);
    text-align: center;
    margin-bottom: 18px;
}
/* Licences. Left-aligned on purpose against the centred name/tagline above —
   this is reference matter to be read, not a heading. Reuses .about-label and
   .about-text so it matches the copyright rows in About & Copyright. */
.appinfo-licence { margin-bottom: 18px; }
.appinfo-licence__row + .appinfo-licence__row { margin-top: 14px; }
.appinfo-licence .about-text { margin: 0; font-size: 12.5px; line-height: 1.65; }
.appinfo-licence .lib-line { margin-top: 14px; }

/* Reader-facing version and the real build sit together: the version is what
   someone would quote, the build is what a bug report actually needs. */
.appinfo-build {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    font-size: 11px;
    color: var(--text-muted);
    border-top: 1px solid var(--border-mid);
    padding-top: 10px;
}
.appinfo-build b { color: var(--text-dim); font-weight: 600; }

/* ── App notice — offline / maintenance ───────────────────────────
   Two DIFFERENT reasons to show this (see maintenance.js's own comment for
   why they're kept separate rather than one generic banner), one shared
   card so a reader learns its shape once. Same visual family as
   .popup__card (radius, noise texture, shadow) rather than a plain toast —
   this can sit on screen for a while, so it earns the same weight as
   something the reader would deliberately open. Fixed at the bottom,
   inset from the edges rather than edge-to-edge, so it never fights the
   top bar or the filmstrip for the reader's eye. */
.app-notice {
    position: fixed;
    left: 50%;
    bottom: 18px;
    transform: translateX(-50%);
    z-index: 99997;   /* below the update-nudge (99998) — an update always
                         wins the reader's attention over a status notice */
    display: flex;
    align-items: flex-start;
    gap: 12px;
    width: calc(100% - 32px);
    max-width: 420px;
    padding: 14px 16px;
    border-radius: 14px;
    border: 1px solid var(--border, rgba(200, 169, 110, 0.18));
    background: var(--bg-panel) url('../images/noise.png');
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.4);
    animation: appNoticeIn 0.22s ease-out;
}
@keyframes appNoticeIn {
    from { opacity: 0; transform: translateX(-50%) translateY(10px); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
/* Icon badge — a small filled circle rather than a bare glyph, so it reads
   at a glance even before the text is read. Colour is the only thing that
   differs between the two kinds (see the two modifiers below); everything
   else about the card is identical, which is what makes them feel like one
   family instead of two unrelated alerts. */
.app-notice__icon {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}
.app-notice__icon svg { width: 18px; height: 18px; fill: currentColor; }
.app-notice__body { flex: 1 1 auto; min-width: 0; }
.app-notice__title {
    font-size: 13px;
    font-weight: 700;
    margin: 0 0 3px;
    color: var(--text);
}
.app-notice__message {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-dim);
    margin: 0;
}
.app-notice__close {
    flex: 0 0 auto;
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--text-muted);
    font-size: 15px;
    line-height: 1;
    padding: 2px 4px;
    margin: -2px -4px 0 0;
}
.app-notice__close:hover,
.app-notice__close:focus-visible { color: var(--text); }

/* Offline: calm, reassuring — this is expected and cached books are fine,
   so the accent is a neutral blue-grey rather than a warning colour. */
.app-notice--offline .app-notice__icon {
    background: rgba(120, 150, 180, 0.18);
    color: #8fb4d9;
}
/* Maintenance: the app's own accent gold — a deliberate signal FROM us,
   not a network hiccup, so it earns the same colour the rest of the app
   already uses for "this matters, look here" (chapter marks on hover,
   the update-nudge border). */
.app-notice--maintenance .app-notice__icon {
    background: var(--accent-dim, rgba(200, 169, 110, 0.18));
    color: var(--accent);
}

/* ── Chapter Information popup ──────────────────────────────────
   Was built entirely from inline cssText in viewer.js _showChapterInfo,
   which no media query could reach (inline styles outrank stylesheet rules),
   so a phone got the desktop card. Moved here as real classes so the small
   screen below is possible at all. */
.chapter-info {
    position: fixed;
    inset: 0;
    z-index: 99998;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.6);
    padding: 4vh 20px;      /* room for the card to shrink on phone landscape */
}
.chapter-info__card {
    position: relative;
    display: flex;
    flex-direction: column;
    max-width: min(92vw, 460px);
    max-height: 82vh;
    padding: clamp(16px, 4vh, 24px) clamp(18px, 5vw, 26px) clamp(14px, 3vh, 20px);
    border-radius: 16px;
    text-align: left;
    background: var(--bg-panel, #1e1e1e);
    color: var(--text, #e8e0d0);
    border: 1px solid var(--accent, #c8a96e);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
    font: clamp(13px, 3.6vw, 15px)/1.6 -apple-system, system-ui, "Segoe UI", sans-serif;
}
.chapter-info__x {
    position: absolute;
    top: 10px;
    right: 12px;
    width: 40px;            /* a real touch target, not a decorative glyph */
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: none;
    cursor: pointer;
    font: 600 20px/1 inherit;
    color: var(--text-dim, #888);
}
.chapter-info__x:hover,
.chapter-info__x:focus-visible { background: rgba(255, 255, 255, 0.08); }

.chapter-info__scroll {
    overflow-y: auto;
    min-height: 0;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;   /* scrolling the summary must not scroll
                                       the book underneath it */
}
.chapter-info__title {
    font-weight: 600;
    color: var(--accent, #c8a96e);
    font-size: clamp(16px, 4.6vw, 19px);
    margin-bottom: 14px;
    padding-right: 34px;    /* clears the ✕ so a long title wraps before it */
}
.chapter-info__body {
    color: var(--text, #e8e0d0);
    margin-bottom: 6px;
    white-space: pre-line;  /* keeps the blank lines between summary/Cast/Keywords */
}
.chapter-info__attrib {
    color: var(--text-dim, #888);
    font-size: 11px;
    margin-top: 12px;
    opacity: 0.8;
}
.chapter-info__footer { flex: 0 0 auto; padding-top: 16px; }
.chapter-info__close {
    width: 100%;
    cursor: pointer;
    border: 0;
    border-radius: 8px;
    padding: 9px 22px;
    font: inherit;
    font-weight: 600;
    background: var(--accent, #c8a96e);
    color: var(--bg, #0f0a05);
}

/* Small screens: a bottom sheet, not a shrunken desktop card. A centred box
   with margins on all four sides wastes the little width a phone has, and
   these summaries run to a couple of hundred words. Full width, anchored to
   the bottom where the thumb already is, and taller — the text is the point,
   so it gets the room. */
@media (max-width: 600px) {
    .chapter-info {
        align-items: flex-end;
        padding: 0;
    }
    .chapter-info__card {
        max-width: 100%;
        width: 100%;
        max-height: 88vh;
        border-radius: 18px 18px 0 0;
        border-width: 1px 0 0 0;    /* only the top edge reads as an edge now */
        padding: 20px 20px calc(16px + env(safe-area-inset-bottom, 0px));
        font-size: 15px;            /* not shrunk to fit — a phone is where
                                       this is most likely to be read */
    }
    .chapter-info__title { font-size: 18px; }
    .chapter-info__close { padding: 13px 22px; }   /* full-width thumb target */
}

/* Landscape phone: very little height, so drop back to a centred card and
   let the scroll area do the work rather than a sheet that covers the screen. */
@media (max-width: 900px) and (max-height: 460px) {
    .chapter-info { align-items: center; padding: 2vh 16px; }
    .chapter-info__card {
        max-width: min(92vw, 560px);
        max-height: 96vh;
        border-radius: 14px;
        border-width: 1px;
        padding: 14px 18px 12px;
    }
}

/* ── Cover soft box — Contents' "cover" link ────────────────────
   Sits above the Contents panel (which is z-index 99997-ish) and below
   nothing else; same backdrop weight as the chapter card so the two read as
   one family. The image is the whole content — no title, no caption, no
   chrome except the ✕, because the reader asked to see the cover. */
.cover-box {
    position: fixed;
    inset: 0;
    z-index: 99998;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.72);
    padding: 4vh 20px;
}
.cover-box__card {
    position: relative;
    max-width: min(92vw, 820px);
    max-height: 88vh;
    display: flex;
}
.cover-box__img {
    display: block;
    max-width: 100%;
    max-height: 88vh;
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 10px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6),
                0 0 0 1px rgba(255, 255, 255, 0.06);
}
.cover-box__x {
    position: absolute;
    top: -6px;
    right: -6px;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    cursor: pointer;
    font: 600 18px/1 -apple-system, system-ui, "Segoe UI", sans-serif;
    background: var(--bg-panel, #1e1e1e);
    color: var(--text, #e8e0d0);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
.cover-box__x:hover,
.cover-box__x:focus-visible { background: var(--bg-input, #2a2a2a); }

/* Phone: the ✕ hanging outside the image gets clipped by the viewport edge,
   so bring it inside and give the box the full width it can use. */
@media (max-width: 600px) {
    .cover-box { padding: 16px; }
    .cover-box__card { max-width: 100%; }
    .cover-box__x { top: 8px; right: 8px; }
}

/* Touch targets in Contents. "chapter info" and the front-matter links render
   at 59x13 px — fine for a mouse, well under the ~44px a thumb needs, and a
   real tap genuinely misses them (found by an emulated-touch tap landing on
   nothing while a direct DOM click worked). Padding only: the text keeps its
   size and position, the hit area grows around it. */
@media (max-width: 600px), (pointer: coarse) {
    .toc-chapter__info-link,
    .toc-frontmatter-links .toc-chapter__info-link {
        padding: 12px 8px;
        margin: -12px -8px;      /* pulled back so layout is unchanged */
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }
    .toc-frontmatter-links { display: flex; align-items: center; gap: 4px; }
}

/* ══ EARLY-RELEASE NUDGE ═══════════════════════════════════════════
   One quiet invitation to report problems, on the reader's third session.
   Deliberately NOT a modal: it must never interrupt reading, so it sits in a
   corner, takes no focus, and fades itself out after a minute. On a phone it
   spans the width instead, where a floating card would crowd the page.
   See _maybeShowFeedbackNudge in viewer.js for when it fires. */
.nudge {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 9000;                 /* under popups (9999), over the reader */
    width: min(360px, calc(100vw - 40px));
    padding: 16px 18px 14px;
    background: rgba(30, 30, 30, 0.97);
    border: 1px solid var(--accent-dim);
    border-radius: 14px;
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55),
                inset 0 0 0 1px rgba(200, 169, 110, 0.06);
    color: #e8e0d0;
    opacity: 0;
    transform: translateY(14px);
    transition: opacity .5s ease, transform .5s ease;
    pointer-events: auto;
}
.nudge--in { opacity: 1; transform: translateY(0); }

.nudge__lead {
    margin: 0 0 6px;
    font: 600 15px/1.3 Georgia, "Times New Roman", serif;
    color: var(--accent);
}
.nudge__sub {
    margin: 0;
    font-size: 13px;
    line-height: 1.5;
    color: #b8b0a0;
}
.nudge__actions { margin-top: 12px; }
.nudge__btn {
    font: 600 13px/1 inherit;
    padding: 8px 14px;
    color: var(--accent);
    background: rgba(200, 169, 110, 0.10);
    border: 1px solid var(--accent-dim);
    border-radius: 8px;
    cursor: pointer;
    transition: background .2s ease, color .2s ease;
}
.nudge__btn:hover { background: rgba(200, 169, 110, 0.20); color: #f0e0c0; }
.nudge__close {
    position: absolute;
    top: 6px;
    right: 8px;
    width: 28px;
    height: 28px;
    font-size: 20px;
    line-height: 1;
    color: #8a8378;
    background: none;
    border: 0;
    cursor: pointer;
}
.nudge__close:hover { color: var(--accent); }

body.light-mode .nudge {
    background: rgba(252, 246, 232, 0.98);
    color: #3a2a15;
    border-color: rgba(168, 70, 10, 0.28);
}
body.light-mode .nudge__lead { color: #a8460a; }
body.light-mode .nudge__sub  { color: #6a5540; }
body.light-mode .nudge__btn  { color: #a8460a; background: rgba(168, 70, 10, 0.08);
                               border-color: rgba(168, 70, 10, 0.28); }

@media (max-width: 560px) {
    .nudge { right: 12px; left: 12px; bottom: 12px; width: auto; }
}
@media (prefers-reduced-motion: reduce) {
    .nudge { transition: none; transform: none; }
}

/* Recent Collections — parked 2026-08-12, build 310.
   The iframe panel frames Bhante's own /Recent/index.htm live. That was never the
   feature: what I asked for is a detector that reads his Recent page, compares it
   against what the app already holds, and shows only what is genuinely new. That
   script does not exist. Both doors and the panel are hidden until it does.
   Unhide: delete this rule. Markup is untouched at app.htm:1364, :2285, :2427. */
#moreRecent,
#aboutRecentBtn,
#popupRecent { display: none !important; }
