/* Bundled reading font — Literata (designed for on-screen reading; SIL OFL).
   Self-hosted so every platform gets the same type, not the device default serif. */
@font-face { font-family: "Literata"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/literata-400-normal.woff2") format("woff2"); }
@font-face { font-family: "Literata"; font-style: normal; font-weight: 600; font-display: swap; src: url("../fonts/literata-600-normal.woff2") format("woff2"); }
@font-face { font-family: "Literata"; font-style: normal; font-weight: 700; font-display: swap; src: url("../fonts/literata-700-normal.woff2") format("woff2"); }
@font-face { font-family: "Literata"; font-style: italic; font-weight: 400; font-display: swap; src: url("../fonts/literata-400-italic.woff2") format("woff2"); }
/* Brand wordmark face — Berkshire Swash (SIL OFL), self-hosted like Literata so
   the "enrapt" logotype renders identically everywhere and works offline. */
@font-face { font-family: "Berkshire Swash"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/berkshire-swash-400.woff2") format("woff2"); }

/* The enrapt logotype: lowercase Berkshire Swash in the theme's primary ink
   (near-white on dark themes, espresso on light) — resets any inherited
   gradient/clip treatment so it's a clean solid wordmark wherever used. */
.logotype {
  /* Fallback is a web-safe SERIF, never the generic `cursive` — on Windows the
     browser maps `cursive` to Comic Sans MS, which flashed before the (now
     preloaded + precached) Berkshire Swash webfont swapped in on slow networks. */
  font-family: "Berkshire Swash", Georgia, "Times New Roman", serif;
  font-weight: 400;
  text-transform: lowercase;
  letter-spacing: 0;
  color: var(--text);
  -webkit-text-fill-color: var(--text);
  background: none;
}

:root {
  /* BRAND — dark blue · gold · silver · black · white. A premium, high-contrast
     "midnight library": deep blue-black surfaces, crisp white ink, a metallic
     silver secondary tier, and gold as the single confident accent. Cool and
     modern (think Letterboxd), not warm/brown. Borders are hairline; spacing
     and elevation do the grouping. */
  --bg: #0e1320;        /* dark blue-black */
  --panel: #161d2c;     /* dark blue surface */
  --panel-2: #1e2738;   /* raised blue surface */
  --elevate: #2a3650;   /* lifted blue — active segments / hover */
  /* Ink — near-white primary (high contrast) with two SILVER quieter tiers. */
  --text: #f6f8fc;      /* near-white — primary ink */
  --muted: #b6c0d0;     /* silver — readable secondary */
  --faint: #7c8698;     /* dim silver — faint meta */
  /* Gold — the one confident interactive accent + soft tints. Reads premium on
     the blue base; reserved for primary actions, active nav, focus. */
  --accent-2: #e9b949;
  /* Gold as ON-SURFACE TEXT — same as the accent on dark themes, a darker gold on
     light themes so accent text clears WCAG AA. Fills/icons/borders keep --accent-2. */
  --accent-2-text: #e9b949;
  --accent-2-strong: #ffd277;
  --accent-2-soft: rgba(233, 185, 73, 0.16);
  --accent: #ef5f5f;              /* clean red — reserved for genuine errors */
  --danger-soft: rgba(239, 95, 95, 0.14); /* destructive-hover wash (delete/remove) */
  --on-accent: #16110a;           /* near-black ink/icon on the gold accent fill */
  /* Reading-stage hues. The ORP (optimal-recognition-point) is gold — the
     reading-focus letter, never red; --ok carries on-track/positive (the palette
     has no green, so gold); --preview-ink is the muted look-ahead/preview text. */
  --orp: #ffd277;
  --orp-soft: rgba(255, 210, 119, 0.18);
  --preview-ink: #7c8698;
  --ok: #e9b949;
  /* Reading ink for the paused book page — near-white on the blue-black page. */
  --read-ink: #e8edf6;
  --border: #2a3447;    /* blue-grey hairline */
  --border-soft: #1a2130;
  /* Silver progress fill — the two ends of the achievement-bar gradient. Tokenised
     (was a hardcoded literal in 6 files) so a theme can recolour progress like any
     other surface; carried as theme tokens (progressLight / progressDim). */
  --progress-dim: #aeb8c8;    /* gradient dark end */
  --progress-light: #e6ecf5;  /* gradient light end */
  /* Book-rankings score badge — a PURPLE decoration, deliberately OUTSIDE the
     gold brand accent so a score pill never reads as a primary action. Local to
     web (not a cross-platform semantic token); tokenised so it recolours per
     theme instead of staying a hardcoded purple that breaks on a light surface.
     Used by js/library-scan-badges.js + js/screens/library-shelf-view.js. */
  --score-badge-bg: rgba(168, 85, 247, 0.18);
  --score-badge-ink: #c39bff;
  --score-badge-border: #a855f7;
  /* Shape, depth, motion. */
  --radius: 14px;
  --radius-sm: 9px;
  --radius-pill: 999px;
  /* ---- Elevation (DESIGN-SYSTEM.md §1) -----------------------------------
     ONE named shadow ramp both platforms share (Android maps to matching tonal
     elevation). elev-0 = flat, elev-1 = resting card, elev-2 = lifted panel /
     hovered card. There is no elev-3: nothing in the app needs a higher tier
     today, so it is intentionally absent (do not invent a value Android must
     mirror). The legacy --shadow-sm / --shadow-panel names are kept as ALIASES
     of the elevation tokens so the values are byte-for-byte unchanged. */
  --elev-0: none;
  --elev-1: 0 2px 10px rgba(0, 0, 0, 0.30);
  --elev-2: 0 18px 50px rgba(0, 0, 0, 0.52);
  --shadow-sm: var(--elev-1);
  --shadow-panel: var(--elev-2);
  /* ---- Motion (DESIGN-SYSTEM.md §1) --------------------------------------
     ONE duration + easing ramp both platforms share. fast = taps/colour swaps,
     base = the medium default, slow = the springy enter/transform feel. The
     durations are the numeric ms; the eases are the curves. The legacy --tap /
     --spring names are kept as ALIASES (tap = fast + standard ease, spring =
     slow + spring ease) so every existing animation is unchanged. */
  --dur-fast: 150ms;
  --dur-base: 240ms;
  --dur-slow: 380ms;
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.22, 1, 0.36, 1);
  --tap: var(--dur-fast) var(--ease-standard);
  --spring: var(--dur-slow) var(--ease-spring);

  /* ---- Design tokens: spacing + control sizing (one scale, no magic numbers) ----
     Every control's size/gap/radius derives from these, so a new control can't
     invent its own rhythm or break a bar's spacing. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --control-size: 48px;          /* standard touch target (square icon button) */
  --control-size-primary: 56px;  /* the primary transport button */
  --control-radius: 12px;
  --control-icon: 22px;          /* icon glyph size inside a control */
  --control-icon-primary: 26px;
  --control-icon-sm: 16px;       /* glyph inside a compact DsIconButton (remove/close) */
  --hit-target: 44px;            /* minimum touch target (WCAG 2.5.5) */
}

/* OATMEAL — a warm, LIGHT theme. Cream/oat surfaces, dark espresso ink, and a
   caramel-gold accent. Redefines only the THEME tokens (TOKEN_VARS); spacing,
   motion, elevation, radius and the rest stay theme-agnostic in `:root`. The
   token KEYS + VALUES are pinned across web/Android by
   parity/vectors/theme-tokens.json (PARITY.md "Theme"). */
[data-theme="oatmeal"] {
  --bg: #EAE1CF;        /* warm oat base */
  --panel: #F4EEE0;     /* cream surface */
  --panel-2: #FBF7EE;   /* raised cream surface */
  --elevate: #E4D9C0;   /* lifted oat — active segments / hover */
  --text: #2D2820;      /* espresso — primary ink */
  --muted: #5D5546;     /* readable secondary */
  --faint: #6D6451;     /* faint meta */
  --accent-2: #A37225;
  --accent-2-text: #855B17;       /* dark readable gold for accent text (AA on cream) */
  --accent-2-strong: #855B17;
  --accent-2-soft: rgba(163, 114, 37, 0.15);
  --accent: #AF3F31;              /* clean red — reserved for genuine errors */
  --danger-soft: rgba(175, 63, 49, 0.12);
  --on-accent: #2A1E08;           /* near-black ink/icon on the caramel accent fill */
  --orp: #B5651D;
  --orp-soft: rgba(181, 101, 29, 0.18);
  --preview-ink: #6D6451;
  --ok: #A37225;
  --read-ink: #2D2820;
  --border: #D7CBB2;
  --border-soft: #E7DDC9;
  --progress-dim: #C99A3A;    /* gradient dark end */
  --progress-light: #EAD08A;  /* gradient light end */
  /* Score badge — a deep, readable purple on the light cream surfaces (the
     midnight values would be near-invisible light purple on cream). */
  --score-badge-bg: rgba(124, 58, 173, 0.14);
  --score-badge-ink: #6B2FA0;
  --score-badge-border: #9B59C7;
}

/* AMOLED — a true-black DARK theme for OLED panels. The midnight accent/ink/
   progress hues are unchanged (gold accent, near-white ink); only the surface
   plane drops to pure black with very dark blue-grey panels and tighter
   borders. Redefines only the THEME tokens (TOKEN_VARS); the rest stays in
   `:root`. Token KEYS + VALUES pinned by parity/vectors/theme-tokens.json. */
[data-theme="amoled"] {
  --bg: #000000;        /* true black — OLED pixels off */
  --panel: #0B0F17;     /* near-black surface */
  --panel-2: #141A26;   /* raised near-black surface */
  --elevate: #20283A;   /* lifted — active segments / hover */
  --text: #F6F8FC;      /* near-white — primary ink */
  --muted: #B6C0D0;     /* silver — readable secondary */
  --faint: #7C8698;     /* dim silver — faint meta */
  --accent-2: #E9B949;
  --accent-2-text: #E9B949;
  --accent-2-strong: #FFD277;
  --accent-2-soft: rgba(233, 185, 73, 0.16);
  --accent: #EF5F5F;              /* clean red — reserved for genuine errors */
  --danger-soft: rgba(239, 95, 95, 0.14);
  --on-accent: #16110A;           /* near-black ink/icon on the gold accent fill */
  --orp: #FFD277;
  --orp-soft: rgba(255, 210, 119, 0.18);
  --preview-ink: #7C8698;
  --ok: #E9B949;
  --read-ink: #E8EDF6;
  --border: #1C2535;
  --border-soft: #11161F;
  --progress-dim: #AEB8C8;    /* gradient dark end */
  --progress-light: #E6ECF5;  /* gradient light end */
  /* Score badge — reuse the midnight purple (dark surfaces). */
  --score-badge-bg: rgba(168, 85, 247, 0.18);
  --score-badge-ink: #c39bff;
  --score-badge-border: #a855f7;
}

/* SLATE — a cool, neutral DARK theme. Blue-grey "slate" surfaces (warmer and
   lighter than midnight's blue-black), the same gold accent, and softly cooled
   inks. Redefines only the THEME tokens (TOKEN_VARS); the rest stays in `:root`.
   Token KEYS + VALUES pinned by parity/vectors/theme-tokens.json. */
[data-theme="slate"] {
  --bg: #15181C;        /* slate base */
  --panel: #1E2228;     /* slate surface */
  --panel-2: #272C34;   /* raised slate surface */
  --elevate: #353B45;   /* lifted slate — active segments / hover */
  --text: #EEF1F4;      /* near-white — primary ink */
  --muted: #AAB2BC;     /* cool grey — readable secondary */
  --faint: #8B939C;     /* dim grey — faint meta */
  --accent-2: #E9B949;
  --accent-2-text: #E9B949;
  --accent-2-strong: #FFD277;
  --accent-2-soft: rgba(233, 185, 73, 0.16);
  --accent: #F06565;              /* clean red — reserved for genuine errors */
  --danger-soft: rgba(240, 101, 101, 0.14);
  --on-accent: #16110A;           /* near-black ink/icon on the gold accent fill */
  --orp: #FFD277;
  --orp-soft: rgba(255, 210, 119, 0.18);
  --preview-ink: #8B939C;
  --ok: #E9B949;
  --read-ink: #E6EAEF;
  --border: #2E353F;
  --border-soft: #1A1F26;
  --progress-dim: #AEB8C8;    /* gradient dark end */
  --progress-light: #E6ECF5;  /* gradient light end */
  /* Score badge — reuse the midnight purple (dark surfaces). */
  --score-badge-bg: rgba(168, 85, 247, 0.18);
  --score-badge-ink: #c39bff;
  --score-badge-border: #a855f7;
}

/* SEPIA — a warm parchment LIGHT theme. Aged-paper surfaces, deep brown ink,
   and a russet gold accent. Redefines only the THEME tokens (TOKEN_VARS); the
   rest stays in `:root`. Token KEYS + VALUES pinned by
   parity/vectors/theme-tokens.json. */
[data-theme="sepia"] {
  --bg: #F2E6C8;        /* parchment base */
  --panel: #F8F0DA;     /* warm paper surface */
  --panel-2: #FCF7E9;   /* raised paper surface */
  --elevate: #E6D6B0;   /* lifted parchment — active segments / hover */
  --text: #3A2E16;      /* deep brown — primary ink */
  --muted: #6B5832;     /* readable secondary */
  --faint: #766642;     /* faint meta */
  --accent-2: #9A6B1F;
  --accent-2-text: #7A551A;
  --accent-2-strong: #6E4D17;
  --accent-2-soft: rgba(154, 107, 31, 0.15);
  --accent: #B23A2C;              /* clean red — reserved for genuine errors */
  --danger-soft: rgba(178, 58, 44, 0.12);
  --on-accent: #2A1E08;           /* near-black ink/icon on the gold accent fill */
  --orp: #B5651D;
  --orp-soft: rgba(181, 101, 29, 0.18);
  --preview-ink: #766642;
  --ok: #9A6B1F;
  --read-ink: #3A2E16;
  --border: #D8C7A0;
  --border-soft: #E8DCC0;
  --progress-dim: #C39A3A;    /* gradient dark end */
  --progress-light: #E7C982;  /* gradient light end */
  /* Score badge — a deep, readable purple on the light parchment surfaces. */
  --score-badge-bg: rgba(124, 58, 173, 0.14);
  --score-badge-ink: #6B2FA0;
  --score-badge-border: #9B59C7;
}

/* GREEN — a soft sage LIGHT theme. Muted green surfaces, dark olive ink, and a
   warm gold accent. Redefines only the THEME tokens (TOKEN_VARS); the rest
   stays in `:root`. Token KEYS + VALUES pinned by
   parity/vectors/theme-tokens.json. */
[data-theme="green"] {
  --bg: #D9E6CE;        /* sage base */
  --panel: #E6F0DD;     /* soft green surface */
  --panel-2: #F1F7EA;   /* raised green surface */
  --elevate: #C8DABA;   /* lifted sage — active segments / hover */
  --text: #233018;      /* dark olive — primary ink */
  --muted: #47572F;     /* readable secondary */
  --faint: #5B6A43;     /* faint meta */
  --accent-2: #8A6A1E;
  --accent-2-text: #6E551A;
  --accent-2-strong: #614A16;
  --accent-2-soft: rgba(138, 106, 30, 0.15);
  --accent: #A83A2A;              /* clean red — reserved for genuine errors */
  --danger-soft: rgba(168, 58, 42, 0.12);
  --on-accent: #241B07;           /* near-black ink/icon on the gold accent fill */
  --orp: #B5651D;
  --orp-soft: rgba(181, 101, 29, 0.18);
  --preview-ink: #5B6A43;
  --ok: #8A6A1E;
  --read-ink: #233018;
  --border: #C2D3B0;
  --border-soft: #DCE8CF;
  --progress-dim: #C39A3A;    /* gradient dark end */
  --progress-light: #E7C982;  /* gradient light end */
  /* Score badge — a deep, readable purple on the light sage surfaces. */
  --score-badge-bg: rgba(124, 58, 173, 0.14);
  --score-badge-ink: #6B2FA0;
  --score-badge-border: #9B59C7;
}

/* HIGH CONTRAST — a maximum-contrast DARK theme for accessibility. Pure-black
   surfaces, pure-white ink, a bright gold accent, and strong borders. Redefines
   only the THEME tokens (TOKEN_VARS); the rest stays in `:root`. Token KEYS +
   VALUES pinned by parity/vectors/theme-tokens.json. */
[data-theme="contrast"] {
  --bg: #000000;        /* pure black base */
  --panel: #0A0A0A;     /* near-black surface */
  --panel-2: #161616;   /* raised near-black surface */
  --elevate: #242424;   /* lifted — active segments / hover */
  --text: #FFFFFF;      /* pure white — primary ink */
  --muted: #E6E6E6;     /* bright grey — readable secondary */
  --faint: #C4C4C4;     /* grey — faint meta */
  --accent-2: #FFCE3A;
  --accent-2-text: #FFCE3A;
  --accent-2-strong: #FFE08A;
  --accent-2-soft: rgba(255, 206, 58, 0.16);
  --accent: #FF7A6B;              /* clean red — reserved for genuine errors */
  --danger-soft: rgba(255, 122, 107, 0.14);
  --on-accent: #1A1300;           /* near-black ink/icon on the gold accent fill */
  --orp: #FFE08A;
  --orp-soft: rgba(255, 224, 138, 0.18);
  --preview-ink: #C4C4C4;
  --ok: #FFCE3A;
  --read-ink: #FFFFFF;
  --border: #8A8A8A;
  --border-soft: #3A3A3A;
  --progress-dim: #BFBFBF;    /* gradient dark end */
  --progress-light: #FFFFFF;  /* gradient light end */
  /* Score badge — reuse the midnight purple (dark surfaces). */
  --score-badge-bg: rgba(168, 85, 247, 0.18);
  --score-badge-ink: #c39bff;
  --score-badge-border: #a855f7;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }

/* Premium polish: quiet text selection, and thin restrained scrollbars instead
   of the OS default chrome (a small thing that reads as "finished"). */
::selection { background: var(--accent-2-soft); }
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--elevate); background-clip: content-box; }
body {
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  letter-spacing: 0.005em;
  display: flex;
  flex-direction: column;
}

/* Setup view */
.setup {
  max-width: 700px;
  margin: 6vh auto;
  padding: 28px 28px 64px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  width: 100%;
}
/* The library-scan section needs to be wider than the setup form to fit
   long book titles + series tags + dates. Break out of the 720px column. */
.setup .lib-scan {
  width: min(1000px, calc(100vw - 48px));
  margin-left: 50%;
  transform: translateX(-50%);
}
.setup h1 {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.025em;
  margin: 0 0 8px;
  background: linear-gradient(180deg, var(--text), var(--muted));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
/* Hero shows the enrapt wordmark, larger, so it reads as the brand. */
.setup h1.logotype { font-size: 44px; line-height: 1.05; margin: 0 0 10px; }
.setup p.hint { color: var(--muted); margin: 0; font-size: 13.5px; line-height: 1.6; max-width: 52ch; }
.row { display: flex; gap: 10px; }
.row > * { flex: 1; }
.row-end { justify-content: flex-end; }
.row-end > * { flex: 0 0 auto; }
/* DsTextField — THE one canonical text field (DESIGN-SYSTEM.md §3 / §5 P2).
   The global text-input rhythm (~40px control height, --radius-sm, --panel
   surface, 1px --border, calm --faint placeholder, clean amber focus ring) is
   now the canonical FIELD CONTROL. `.ds-field-control` is the explicit class an
   input/textarea opts into; the bare `input[type=text], textarea` selector is
   kept as an ALIAS of the same rule so existing unclassed inputs are byte-for-
   byte unchanged. The `.ds-field` wrapper adds the documented structure (a label
   + the control + an optional help / error slot) that Android's 8+ ad-hoc
   OutlinedTextField sites mirror. Every value is a token. ------------------- */
input[type=text], textarea,
.ds-field-control {
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  min-height: 40px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font: inherit;
  width: 100%;
  outline: none;
  transition: border-color var(--tap), box-shadow var(--tap);
}
input[type=text]::placeholder, textarea::placeholder,
.ds-field-control::placeholder { color: var(--faint); }
input[type=text]:focus, textarea:focus,
.ds-field-control:focus { border-color: var(--accent-2); box-shadow: var(--focus-ring); }
textarea, textarea.ds-field-control { min-height: 140px; resize: vertical; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; }

/* The field WRAPPER: stacked label + control + optional help / error slot. One
   gap rhythm from the spacing ramp; no per-field invented spacing. */
.ds-field { display: flex; flex-direction: column; gap: var(--s2); }
.ds-field-label { font-size: var(--fs-cap); font-weight: 600; color: var(--muted); }
.ds-field-help { font-size: var(--fs-cap); color: var(--faint); line-height: 1.5; }
/* The error slot reuses the existing .err token-driven copy colour; shown only
   when the field carries data-invalid (set by the call site that already owns
   validation). No new validation behaviour is introduced here. */
.ds-field-error { font-size: var(--fs-cap); color: var(--accent); line-height: 1.5; }
.ds-field[data-invalid] .ds-field-control { border-color: var(--accent); }
/* PRIMARY — the one action you want taken: amber fill, espresso ink, a fixed
   ~40px control height + --radius-sm so every button row shares one rhythm. */
/* A bare <button> is a NEUTRAL RESET — it carries NO visual styling. So nothing
   leaks onto the controls that happen to be <button>s (tabs, segmented controls,
   the account avatar, icon buttons, .small-btn). A button's LOOK comes from its
   class, never from the element. Opt in: .primary (gold) / .ghost (neutral) — or
   a component class (.small-btn, .tab, .ds-seg, .ds-btn-*). This is THE abstraction:
   change a control's class, not patch each control to escape a global default. */
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
button:disabled { cursor: not-allowed; opacity: .55; }

/* PRIMARY (.primary) and the full-width CTA (.ds-btn-primary) now live as the
   ONE canonical DsButton in components.css (.ds-btn / .ds-btn--*), with .primary
   and .ds-btn-primary kept there as aliases. The .ghost focus ring stays here
   beside the .ghost rule it pairs with. */
.ghost:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* SECONDARY — a quiet filled pill (subtle surface + hairline; lifts on hover). */
.ghost {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  min-height: 40px; padding: 10px 16px; border-radius: var(--radius-sm);
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border-soft);
  font-weight: 600;
  transition: background var(--tap), border-color var(--tap);
}
.ghost:hover { background: var(--elevate); border-color: var(--border); }

/* enrapt-pulse + .primary:disabled (progress pulse) moved with the DsButton
   component to components.css. */

/* Range sliders: replace the OS-default control (the single biggest "unfinished"
   tell in the old UI) with a thin track + accent thumb. The element keeps a
   generous height for the hit area; the visible track is a 4px pseudo, so the
   mobile touch-target override doesn't fatten the track. */
input[type=range] {
  -webkit-appearance: none; appearance: none;
  background: transparent; cursor: pointer;
  height: 18px; width: 180px;
}
input[type=range]::-webkit-slider-runnable-track { height: 4px; border-radius: 999px; background: var(--border); }
input[type=range]::-moz-range-track { height: 4px; border-radius: 999px; background: var(--border); }
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  margin-top: -5.5px; width: 15px; height: 15px; border-radius: 50%;
  background: var(--accent-2); border: 2px solid var(--panel);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); transition: transform var(--tap);
}
input[type=range]::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent-2); border: 2px solid var(--panel);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
input[type=range]:hover::-webkit-slider-thumb { transform: scale(1.12); }
input[type=range]:focus-visible { outline: none; }
input[type=range]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--accent-2-soft); }

/* Hide the native file input but keep it operable (triggered by a styled
   button) so it doesn't break the dark theme. */
.visually-hidden-file {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
  flex: 0 0 auto !important;
}
#epubPickBtn { flex: 0 0 auto; }

.divider {
  display: flex;
  align-items: center;
  gap: 14px;
  text-align: center;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: 2px 0;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.err { color: var(--accent); font-size: 13px; min-height: 18px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

