/* === Fonts === */

@font-face {
  font-family: 'Bernie Sans Beta';
  src: url('fonts/BernieSansBetaVF.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}

@font-face {
  font-family: 'Inter-Alia';
  src: url('fonts/InterAlia-VF.otf') format('opentype');
  font-weight: 100 900;
  font-display: swap;
}

/* === Reset & Base === */

/* The reset is layered so the book's own style sheets, adopted into the
   editor's panes in the later `shave-book` layer (gui/bookPaneStyles.js),
   outrank it there; every unlayered rule outranks both. */
@layer reset, shave-book;

@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}

/* Light is the default. Every variable defined here is themed below
   via :root[data-theme="dark"] AND the prefers-color-scheme media query.
   Paper surfaces (reader pages, editor panes, session cards) can opt out
   of the dark cascade via `body.paper-keeps-light` — see the paper
   override block lower down. */
:root {
  --bg: #fafafa;
  --bg-pane: #ffffff;
  --bg-toolbar: #1a1a2e;
  --text: #1a1a2e;
  --text-muted: #6b7280;
  --text-toolbar: #e8e8f0;
  --border: #e2e4e8;
  --accent: #4f46e5;
  --accent-hover: #4338ca;
  --link: #3730a3;
  --link-visited: #6d28d9;
  --bg-pane-alt: #f5f3ff;
  --text-error: #b91c1c;
  --shadow-modal: 0 24px 64px rgba(0,0,0,0.35);
  --shadow-panel: 0 8px 24px rgba(0,0,0,0.06);
  --radius-sm: 4px;
  /* The page face, for surfaces that must NAME it because they show Shavian
     from inside a Latin-stacked shell (the reader's running head). */
  --font-shavian: 'shave-active', 'Bernie Sans Beta', 'Inter-Alia', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
}

:root[data-theme="dark"] {
  --bg: #1c1c1e;
  --bg-pane: #2c2c2e;
  --bg-toolbar: #1a1a1f;
  --text: #f2f2f7;
  --text-muted: #8e8e93;
  --text-toolbar: #f2f2f7;
  --border: #3a3a3c;
  --accent: #4f46e5;
  --accent-hover: #6366f1;
  --link: #a5b4fc;
  --link-visited: #d8b4fe;
  --bg-pane-alt: #2a273f;
  --text-error: #fca5a5;
  --shadow-modal: 0 24px 64px rgba(0,0,0,0.7);
  --shadow-panel: 0 8px 24px rgba(0,0,0,0.4);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1c1c1e;
    --bg-pane: #2c2c2e;
    --bg-toolbar: #1a1a1f;
    --text: #f2f2f7;
    --text-muted: #8e8e93;
    --text-toolbar: #f2f2f7;
    --border: #3a3a3c;
    --accent: #4f46e5;
    --accent-hover: #6366f1;
    --link: #a5b4fc;
    --link-visited: #d8b4fe;
    --bg-pane-alt: #2a273f;
    --text-error: #fca5a5;
    --shadow-modal: 0 24px 64px rgba(0,0,0,0.7);
    --shadow-panel: 0 8px 24px rgba(0,0,0,0.4);
  }
}

/* Paper-keeps-light override: when set, designated paper surfaces
   re-assert light variable values regardless of the surrounding theme.
   v1 scope: editor source/target panes, dashboard session/library tiles.
   Reader pages are protected via the separate --reader-page-bg/ink
   mechanism in reader.css (which keys off body:not(.paper-keeps-light)
   the other way round). Tooltip and popover hosts stay themed — only
   the canvas surfaces flip back to paper-white. */
/* Scope is .context-content (the body of each editor pane), NOT the
   parent .context-pane — keeping the scope on the parent would cascade
   the redefined vars into the sibling .pane-header (which paints with
   --bg-pane-alt) and invert the header along with the content. The
   header must stay themed; only the content area flips to paper. */
body.paper-keeps-light .pane-content,
body.paper-keeps-light .context-content,
body.paper-keeps-light #comment-popup,
body.paper-keeps-light .cw-input {
  --bg: #fafafa;
  --bg-pane: #ffffff;
  --text: #1a1a2e;
  --text-muted: #6b7280;
  --border: #e2e4e8;
  --link: #3730a3;
  --link-visited: #6d28d9;
  --bg-pane-alt: #f5f3ff;
}
/* Canvas surfaces paint themselves with the paper-light values. The
   chrome host #comment-popup only redefines the variables — its
   painting is left to its own/child rules so we don't introduce a
   stray panel-paint over a transparent host. The edit-widget chrome
   (#word-popup) deliberately stays themed in dark mode; only its
   .cw-input flips to paper. .context-content needs the `color`
   repaint specifically: var redefinition alone is not enough because
   `color` is inherited from `body` (which computed it in dark scope),
   so descendants only re-evaluate `--text` if SOMEONE in the paper
   subtree paints it. */
body.paper-keeps-light .pane-content,
body.paper-keeps-light .context-content,
body.paper-keeps-light .cw-input {
  background: var(--bg-pane);
  color: var(--text);
}

/* The .context-content mask-image fade reveals its parent .context-pane.
   For the fade to read as a unified paper surface (not paper-on-chrome),
   the parent must paint the same colour. .context-pane already paints
   var(--bg-pane) by default (ebook.css), which gives matching paper in
   light, matching dark in dark. The paper-keeps-light + dark case is the
   one this rule fixes: --bg-pane on .context-pane resolves to the dark
   theme value (we can't redefine the var here without cascading into
   .pane-header — see AN5b above), so paint white directly. Mirrors the
   #ffffff that .context-content's paper-keeps-light scope resolves to. */
body.paper-keeps-light .context-pane {
  background: #ffffff;
}

/* One font for everything. `shave-active` is the user-selected face,
   injected at runtime by fontModal.js. The remainder of the stack is
   the built-in default plus generic system fallbacks. Most fonts used
   in the Shavian community handle both Latin and Shavian glyphs, so
   the same family covers source, target, toolbar and chrome. */
html, body {
  height: 100%;
  font-family: var(--font-shavian);
  font-size: 15px;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

body {
  display: flex;
  flex-direction: column;
}

/* Form-control defaults. Browsers default text-input `color` to black
   regardless of surrounding CSS, which is unreadable in dark mode. Pin
   the colour/background/border to themed vars so every input picks up
   the active theme — and the paper-keeps-light override — automatically.
   `:where()` zeroes specificity so any component-level rule (.cw-input,
   .auth-field input, .dict-row input, …) wins without needing !important.
   Excludes button-like inputs (radio/checkbox/range/colour/file) —
   those have their own visual model and `accent-color` lives elsewhere. */
:where(
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="url"],
  input[type="number"],
  input[type="tel"],
  textarea,
  select
) {
  color: var(--text);
  background: var(--bg-pane);
  border: 1px solid var(--border);
  font-family: inherit;
}
:where(
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="url"],
  input[type="number"],
  input[type="tel"],
  textarea
)::placeholder {
  color: var(--text-muted);
  opacity: 1;
}
:where(input, textarea, select):disabled {
  color: var(--text-muted);
  background: var(--bg);
  cursor: not-allowed;
}

/* Same problem as the form controls above: the browser's built-in
   #0000EE/#551A8B ignore the theme and fall to 1.5:1 on dark paper.
   Going through a variable is what tracks the paper-keeps-light
   override — a link on white paper stays dark-on-light even while
   the surrounding chrome is dark. */
:where(a) {
  color: var(--link);
}
:where(a:visited) {
  color: var(--link-visited);
}

/* === Toolbar === */

.toolbar {
  display: flex;
  align-items: center;
  padding: 0 20px;
  height: 48px;
  gap: 16px;
  background: var(--bg-toolbar);
  color: var(--text-toolbar);
  flex-shrink: 0;
}

/* Middle group: by default `display: contents` so children act as
   direct flex children of `.toolbar` and the layout is unchanged.
   Narrow-screen rules below break it out as its own row. */
.toolbar-middle {
  display: contents;
}

@media (max-width: 768px) {
  .toolbar {
    flex-wrap: wrap;
    height: auto;
    min-height: 48px;
    padding-top: 6px;
    padding-bottom: 6px;
    row-gap: 6px;
  }
  /* Gated: an empty middle would still claim a full row via flex-basis:100%. */
  body.has-book .toolbar-middle,
  body.reader-mode .toolbar-middle {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-basis: 100%;
    order: 99;
    flex-wrap: wrap;
  }
}

.logo {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* Wordmark on the left of the toolbar — Shavian "𐑖𐑱𐑝", styled to
   match the favicon: red glyph, bold (700), wide (font-stretch 150%),
   italic, on a snug white rounded pill. */
.toolbar-logo {
  font-size: 18px;
  font-weight: 700;
  font-style: italic;
  font-stretch: 150%;
  color: #b81d22;
  background: #fff;
  padding: 0 6px;
  margin-right: 4px;
  border-radius: 999px;
  line-height: 1;
  user-select: none;
}

.toolbar-nav {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-toolbar);
  text-decoration: none;
  opacity: 0.6;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  transition: opacity 0.15s, background 0.15s;
}

.toolbar-nav:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.08);
}

.toolbar-nav.active {
  opacity: 1;
}

/* Offline: tabs whose surface needs the live server/engine are inert.
   Kept visible (not hidden) so the chrome is stable across connectivity
   flaps. NOT pointer-events:none — the tab must keep receiving taps so it
   can explain itself on touch, where the title tooltip does not exist;
   offlineChrome.js is what stops the navigation. The strike-through is the
   at-a-glance signal, since dimming alone reads as ordinary styling. */
.toolbar-nav-disabled {
  opacity: 0.35;
  text-decoration: line-through;
  cursor: not-allowed;
}

.toolbar-nav-disabled:hover {
  opacity: 0.35;
  background: none;
}

.select-dialect {
  padding: 5px 10px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-toolbar);
  font-size: 13px;
  cursor: pointer;
  outline: none;
}

.select-dialect option {
  background: var(--bg-toolbar);
  color: var(--text-toolbar);
}

.toolbar-radio-group {
  display: flex;
  align-items: center;
  gap: 8px;
}

.toolbar-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--text-toolbar);
  font-size: 0.85em;
  cursor: pointer;
  opacity: 0.8;
  user-select: none;
}

.toolbar-toggle:hover {
  opacity: 1;
}

.toolbar-toggle input[type="radio"] {
  accent-color: var(--accent);
}

.toolbar-slider {
  gap: 6px;
}

.toolbar-slider input[type="range"] {
  width: 80px;
  accent-color: var(--accent);
}

.toolbar-spacer {
  flex: 1;
}

/* The app shell pins itself to the viewport (html,body height:100%), which
   would squash anything placed after <main>. On the one page that carries
   the explainer, the body grows past the viewport instead and the document
   scrolls. The toolbar wraps to a variable height on narrow screens, so the
   tool's one-screen height comes from flex against a 100dvh minimum rather
   than from subtracting assumed chrome heights. */

.app-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  height: 24px;
  flex-shrink: 0;
  background: var(--bg-pane);
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-muted);
}
.app-footer a,
.app-footer a:link,
.app-footer a:visited,
.app-footer a:hover,
.app-footer a:active {
  color: inherit;
  text-decoration: none;
}
.app-footer a:hover { text-decoration: underline; }
.app-footer-version { margin-left: 8px; }
.app-footer-version::before { content: "·"; margin-right: 8px; }

.toolbar-btn {
  padding: 5px 12px;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-toolbar);
  font-size: 13px;
  cursor: pointer;
  outline: none;
  font-family: inherit;
  line-height: 1.4;
}

.toolbar-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.15);
}
.toolbar-btn:disabled {
  opacity: 0.35;
  cursor: default;
}
.toolbar-btn-icon {
  font-size: 16px;
  padding: 3px 10px;
  line-height: 1;
}

/* Connection-status icon. Ambient, not a button: no border/background,
   just the stroke glyph in the toolbar text colour. Fixed 18px box so it
   never overflows the mobile toolbar row. */
.toolbar-conn-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  color: var(--text-toolbar);
  opacity: 0.7;
  cursor: pointer;
}
.toolbar-conn-status .shave-icon {
  width: 18px;
  height: 18px;
}
.toolbar-conn-status[data-conn-state="offline"] {
  opacity: 1;
}
/* Online is the silent default: the icon renders no glyph, and the host
   box collapses so a healthy link reserves no toolbar space. */
.toolbar-conn-status[data-conn-state="online"] {
  display: none;
}

/* === Burger menu + modals === */

.toolbar-burger-wrap {
  position: relative;
}

.burger-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: var(--bg-toolbar);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: var(--radius-sm);
  min-width: 160px;
  /* Sits above the notification root (10000) so transient toasts can't
     occlude the menu the user just opened. Modal backdrops still cover
     the burger — see .modal-backdrop. */
  z-index: 10500;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  padding: 4px;
}

.burger-menu-item {
  display: block;
  width: 100%;
  padding: 8px 12px;
  background: transparent;
  border: 0;
  color: var(--text-toolbar);
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.burger-menu-item:hover {
  background: rgba(255,255,255,0.08);
}

.burger-menu-sep {
  margin: 4px 0;
  border: 0;
  border-top: 1px solid rgba(255,255,255,0.15);
}

/* The shaw-keys library stacks its own overlays (base picker, glyph palette,
   the promoted keyboard) from --sk-overlay-z. Its default floor is 2000, which
   sits under our modals — and the keyboard picker is reached FROM Settings, so
   its overlays opened behind the modal that launched them. Raise the floor past
   .modal-backdrop; the library keeps its overlays' relative order above it. */
:root {
  --sk-overlay-z: 12500;
}

/* Modal */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Above the burger menu (10500) and notification root (10000) so a
     modal always wins the stack. modalShell.computeStackedZIndex bumps
     subsequent nested modals above this base. */
  z-index: 11000;
  /* Every backdrop blurs, so a hand-off between two of them (details
     dialogue to launch overlay to reader) never drops back to sharp.
     1px is the floor that still reads: browsers quantise 0.5px to no
     blur at all. */
  -webkit-backdrop-filter: blur(1px);
  backdrop-filter: blur(1px);
}

/* Busy overlay (ShaveBusyOverlay): a transient "working..." chip shown
   during slow view-swaps. Unlike a dialog, the view behind it is mid-swap,
   so the heavy .modal-backdrop scrim reads wrong here — a barely-there tint
   instead of darkness, over the shared blur. */
.busy-backdrop {
  background: rgba(0,0,0,0.12);
}

.busy-panel {
  display: flex;
  align-items: center;
  max-width: min(90vw, 320px);
  padding: 14px 20px;
  background: var(--bg, #fff);
  color: var(--text, #111);
  border: 1px solid var(--border, #e2e4e8);
  border-radius: var(--radius-md, 8px);
  box-shadow: var(--shadow-modal, 0 24px 64px rgba(0,0,0,0.35));
}

.busy-label {
  font-size: 15px;
}

.modal {
  background: var(--bg, #fff);
  color: var(--text, #111);
  border-radius: var(--radius-md, 8px);
  min-width: 360px;
  max-width: 560px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 24px 64px rgba(0,0,0,0.35);
  overflow: hidden;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border, #e5e5e5);
}
.modal-header h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}
.modal-close {
  background: transparent;
  border: 0;
  font-size: 22px;
  line-height: 1;
  color: var(--text-muted, #777);
  cursor: pointer;
  padding: 4px 8px;
}
.modal-close:hover { color: var(--text, #111); }

.modal-body {
  padding: 20px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Unified Settings dialog — tab strip sits between header and body.
   Tabs use a thin accent underline for the active state so the visual
   doesn't require a hover affordance (mobile-first). Horizontal scroll
   on narrow viewports keeps every tab reachable without wrapping. */
.settings-dialog { min-width: 480px; max-width: 720px; width: 90vw; }
.settings-tab-strip {
  display: flex;
  gap: 4px;
  padding: 0 12px;
  border-bottom: 1px solid var(--border, #e5e5e5);
  overflow-x: auto;
  flex-wrap: nowrap;
}
.settings-tab-button {
  flex-shrink: 0;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 13px 14px;
  font-size: 14px;
  color: var(--text-muted, #6b7280);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  /* Pull the bottom border up onto the strip's own underline so the
     active tab visually replaces it. */
  margin-bottom: -1px;
}
.settings-tab-button:hover { color: var(--text, #111); }
.settings-tab-button[aria-selected="true"],
.settings-tab-button.is-active {
  color: var(--text, #111);
  border-bottom-color: var(--accent, #4f46e5);
  font-weight: 600;
}
.settings-tab-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Keyboard tab blurb — short two-sentence intro above the shaw-type
   panel. Slightly smaller than body text, muted colour. */
.vk-settings-blurb {
  font-size: 13px;
  color: var(--text-muted, #6b7280);
  line-height: 1.5;
}
.vk-settings-blurb a { color: var(--link); }
.vk-settings-panel-host {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Font panel — when mounted as a tab body, the upload-action row sits
   below the list and aligns its bits the same way the standalone modal
   footer used to. */
.font-panel-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: wrap;
}
.font-panel-actions .font-panel-upload-hint {
  margin-right: auto;
  font-size: 12px;
  color: var(--text-muted, #6b7280);
}

/* Viewer-side font-override tab (ebook Settings dialog). The checkbox sits
   above a regular font-picker; when the checkbox is checked (= use owner's
   font), the picker is greyed but readable — visual "this is the inactive
   stance" without removing the rows from the layout. */
.font-override-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.font-override-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  cursor: pointer;
  user-select: none;
}
.font-override-toggle .font-override-checkbox {
  margin: 0;
}
.font-override-picker.is-disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* Embedded shaw-type keyboard panel — light theme is the default; map
   its surface tokens to our --shave-* palette so dark mode doesn't
   leave the panel as a bright island. Scoped to .vk-settings-panel-host
   so the standalone keyboard widget (when docked elsewhere on the page)
   stays untouched. */
:root[data-theme="dark"] .vk-settings-panel-host {
  color: var(--text);
}
:root[data-theme="dark"] .vk-settings-panel-host input[type="text"],
:root[data-theme="dark"] .vk-settings-panel-host select {
  background: var(--bg-pane);
  color: var(--text);
  border: 1px solid var(--border, #555);
}

/* Regenerate dialog (#settings-modal) — same tab shell as Settings but
   the host owns the footer + dismissal. Slightly wider than the default
   .modal so the Font tab's list rows don't crowd. Mobile rule below
   collapses width and tightens the panel font list. */
.modal.regenerate-modal { min-width: 480px; max-width: 640px; width: 90vw; }
.regenerate-modal .font-panel-list { max-height: 44vh; }

/* Mobile: settings dialog fills more of the viewport and the tab strip
   keeps its horizontal scroll. */
@media (max-width: 600px) {
  .settings-dialog { min-width: 0; width: 96vw; }
  .modal.regenerate-modal { min-width: 0; width: 96vw; }
  .settings-tab-button { padding: 13px 12px; font-size: 13px; }
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 20px;
  border-top: 1px solid var(--border, #e5e5e5);
  background: var(--bg-pane, #fafafa);
}

/* Share-link row. Mirrors the book share modal's row in ebook.css, which the
   text page does not load. */
.share-url-row {
  display: flex;
  gap: 8px;
  margin: 12px 0;
}

.share-url-input {
  flex: 1;
  min-width: 0;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
}

.modal.help-modal {
  max-width: 720px;
  width: 90vw;
}
.help-modal-body {
  max-height: 70vh;
  overflow-y: auto;
  line-height: 1.5;
}
.help-modal-body h2 {
  font-size: 18px;
  margin: 0 0 8px;
}
.help-modal-body h3 {
  font-size: 15px;
  margin: 16px 0 4px;
}
.help-modal-body h2:first-child,
.help-modal-body h3:first-child {
  margin-top: 0;
}
.help-modal-body p,
.help-modal-body ul,
.help-modal-body ol {
  margin: 6px 0;
}
.help-modal-body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
}

/* Restore traditional list bullet indent in content-rendering contexts.
   The global reset zeroes padding on every element, which (combined with
   the default list-style-position: outside) renders bullets in the empty
   gutter to the LEFT of the content box. Re-instate a modest left pad
   for the contexts that legitimately render prose lists. */
.pane-content ul,
.pane-content ol,
.messages-item-html ul,
.messages-item-html ol,
.modal-body ul,
.modal-body ol {
  padding-left: 1.5em;
}

/* Settings inside the modal use a lighter colour scheme than the
   original toolbar-dropdown incarnation. Override the dark-on-dark
   bits of .settings-label / .settings-radio-group. */
.modal-body .settings-label {
  color: var(--text-muted, #555);
  opacity: 1;
}
.modal-body .settings-radio-group label {
  color: var(--text, #111);
}

.settings-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Read-only book-settings hint shown to collaborators / viewers (#21
   seam 2): they see the owner's transliterator settings but can't edit
   them. Muted, full-width, wraps within the modal's column. */
.settings-copy-hint {
  font-size: 13px;
  line-height: 1.4;
  opacity: 0.7;
  color: var(--text-toolbar);
  max-width: 100%;
}

/* Explicit Regenerate button (#133): owner-only, tx-tab-only. Separated
   from the settings controls above and left-aligned at the button's
   natural width rather than stretched by .settings-section's column. */
.settings-regenerate-section {
  align-items: flex-start;
  border-top: 1px solid var(--border, rgba(0, 0, 0, 0.12));
  padding-top: 12px;
  margin-top: 4px;
}

.settings-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.5;
  color: var(--text-toolbar);
}

/* Override .select-dialect's dark-toolbar palette for the light modal. */
.settings-select {
  width: 100%;
  background: var(--bg-pane);
  color: var(--text);
  border: 1px solid var(--border);
}
.settings-select option {
  background: var(--bg-pane);
  color: var(--text);
}

.settings-radio-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.settings-radio-group label {
  font-size: 13px;
  color: var(--text-toolbar);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
}

.settings-radio-group input[type="radio"] {
  accent-color: var(--accent);
}

/* ── Metadata tab (#21 seam 3): editable title/author + cover. All
   controls stay within the modal column (mobile-first). ── */
.book-meta-tab {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.book-meta-field-label {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* "Edited" badge — a field whose value is a manual override. */
.book-meta-edited-badge {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  opacity: 0.85;
}

.book-meta-field-controls {
  display: flex;
  gap: 8px;
  align-items: center;
}

.book-meta-input {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  background: var(--bg-pane);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 8px;
  font-size: 14px;
}

.book-meta-reset:disabled {
  opacity: 0.4;
  cursor: default;
}

.book-meta-cover-section {
  align-items: flex-start;
}

.book-meta-cover-preview {
  max-width: 120px;
  max-height: 180px;
  border-radius: 4px;
  border: 1px solid var(--border);
}

.book-meta-cover-controls {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.book-meta-cover-replace {
  cursor: pointer;
}

.settings-hint {
  color: var(--text-muted);
  font-size: 12px;
  margin-top: 4px;
}

.settings-checkbox {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  cursor: pointer;
}
.settings-checkbox input[type="checkbox"] {
  accent-color: var(--accent);
}
.settings-slider {
  accent-color: var(--accent);
}

/* Custom dictionary modal */
.dict-modal { min-width: 0; width: 96vw; max-width: 640px; }
.dict-hint {
  font-size: 13px;
  color: var(--text-muted, #6b7280);
  margin: 0;
}
.dict-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 50vh;
  overflow-y: auto;
  padding: 4px 0;
}
.dict-row {
  display: flex;
  gap: 8px;
  align-items: center;
}
/* Flex items floor at their content's intrinsic width without min-width:0,
   which overflows the row on a phone. */
.dict-row input,
.dict-row select {
  min-width: 0;
  padding: 6px 10px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: var(--radius-sm, 4px);
  background: var(--bg, #fff);
  color: var(--text, #111);
  font-size: 14px;
  font-family: inherit;
}
.dict-row input.dict-word,
.dict-row input.dict-shavian {
  flex: 1 1 0;
}
/* dictModal.js keeps the option text to a bare Penn tag, so the widest option
   only needs ~72px (33px of tag + 37px of native arrow furniture + padding).
   That is the floor a phone gets, where every pixel here is taken from the
   two fields the user actually types into. The basis grows with the row, not
   the viewport, because the modal caps at 640px — past that the row stops
   growing and a viewport breakpoint would be measuring the wrong box. The
   ceiling stays well under the 140px this used to be: at that width more than
   half the control is empty gap between the tag and the arrow. */
.dict-row select.dict-pos {
  flex: 0 1 clamp(72px, 18%, 104px);
  padding-left: 6px;
  padding-right: 2px;
}
.dict-row-delete {
  flex: none;
  background: transparent;
  border: 0;
  color: var(--text-muted, #9ca3af);
  font-size: 18px;
  cursor: pointer;
  padding: 2px 8px;
  line-height: 1;
}
.dict-row-delete:hover { color: #dc2626; }
.dict-add-row {
  align-self: flex-start;
}
.dict-empty {
  font-size: 13px;
  color: var(--text-muted, #6b7280);
  font-style: italic;
  padding: 12px 0;
}
/* "Just added" via the correction widget — sustained outline so the
   user can see what changed even after the modal-open animation. */
.dict-row.dict-row-highlight {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 2px;
  border-radius: var(--radius-sm, 4px);
}
/* "Already present" highlight — softer, doesn't claim recency. */
.dict-row.dict-row-existing {
  outline: 2px dashed var(--text-muted, #9ca3af);
  outline-offset: 2px;
  border-radius: var(--radius-sm, 4px);
}
.dict-row-cancel {
  padding: 4px 10px;
  font-size: 13px;
}
/* CorrectionWidget 📖 button when the current word+POS is already in
   the user's dict. Tints the icon green so the state is visible. */
.cw-btn-add-to-dict.cw-in-dict {
  color: #16a34a;
}

/* Font picker — see fontModal.js. Each row renders the font name AND a
   Shavian sample line in the row's own font face (set inline by JS on
   .font-row-text, inherited by both .font-row-label and -sample). */
.font-upload-hint {
  font-size: 13px;
  color: var(--text-muted, #6b7280);
  font-style: italic;
  margin-right: auto;
}
.font-upload-error {
  font-size: 13px;
  color: var(--text-error);
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.35);
  border-radius: var(--radius-sm, 4px);
  padding: 4px 8px;
  margin-right: auto;
}
.font-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Cap absolute height on tall viewports so the panel doesn't crowd the
     modal footer; min preserves the responsive shrink on phones. */
  max-height: min(60vh, 480px);
  overflow-y: auto;
}
.font-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  min-height: 44px;
  border: 1px solid var(--border, #e2e4e8);
  border-radius: var(--radius-sm, 4px);
  background: var(--bg-pane, #fff);
  cursor: pointer;
  flex-shrink: 0;
}
.font-row:hover {
  background: var(--bg-pane-alt, #f5f3ff);
}
.font-row:focus-visible {
  outline: 2px solid var(--accent, #4f46e5);
  outline-offset: 2px;
}
.font-row[aria-disabled="true"] {
  cursor: default;
}
.font-row-selected {
  border-color: var(--accent, #4f46e5);
  background: var(--bg-pane-alt, #f5f3ff);
}
.font-row-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  /* font-family is set inline by JS to the row's own face; children inherit. */
}
.font-row-label {
  font-size: 22px;
  line-height: 1.2;
}
.font-row-sample {
  font-size: 22px;
  line-height: 1.2;
  color: var(--text-muted, #6b7280);
  word-break: break-word;
}
.font-row-actions {
  display: flex;
  gap: 6px;
  align-items: center;
}
.font-row-check {
  width: 18px;
  height: 18px;
  color: var(--accent, #4f46e5);
  visibility: hidden;
}
.font-row-selected .font-row-check {
  visibility: visible;
}
.font-row-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-muted, #9ca3af);
  cursor: pointer;
  padding: 2px 4px;
  line-height: 1;
}
.font-row-delete .shave-icon { width: 16px; height: 16px; }
.font-row-delete:hover { color: #dc2626; }
.font-row-delete-placeholder { visibility: hidden; cursor: default; }

/* Auth modal — see authModal.js. */
.auth-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border, #d1d5db);
  margin: 0 0 12px;
}
.auth-tab {
  background: transparent;
  border: 0;
  padding: 8px 14px;
  font: inherit;
  color: var(--text-muted, #6b7280);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.auth-tab-active {
  color: var(--text, #111);
  border-bottom-color: var(--accent, #4f46e5);
}
.auth-hint {
  font-size: 13px;
  color: var(--text-muted, #6b7280);
  margin: 0 0 12px;
}
.auth-form { display: flex; flex-direction: column; gap: 10px; }
.auth-field { display: flex; flex-direction: column; gap: 4px; }
.auth-field-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted, #6b7280);
}
.auth-field input {
  padding: 8px 10px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: var(--radius-sm, 4px);
  background: var(--bg, #fff);
  color: var(--text, #111);
  font-size: 14px;
  font-family: inherit;
}
.auth-field input.auth-field-invalid {
  border-color: #ef4444;
  box-shadow: 0 0 0 1px #ef4444 inset;
}
.auth-eye-wrap {
  position: relative;
  display: flex;
  align-items: stretch;
}
.auth-eye-wrap > input {
  flex: 1 1 auto;
  padding-right: 36px;
}
.auth-eye-toggle {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: 0;
  padding: 4px 6px;
  cursor: pointer;
  color: var(--text-muted, #6b7280);
  font-size: 14px;
  line-height: 1;
  opacity: 0.7;
}
.auth-eye-toggle:hover { opacity: 1; }
.auth-eye-toggle.is-showing { color: var(--accent, #4f46e5); opacity: 1; }

/* === Keyboard chord hint === */
.shave-chord-hint {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(6px);
  background: rgba(20, 20, 30, 0.92);
  color: #f4f4f7;
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  letter-spacing: 0.02em;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s, transform 0.15s;
  z-index: 12000;
}
.shave-chord-hint.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* === Shortcuts modal === */
.shortcuts-section {
  margin-bottom: 16px;
}
.shortcuts-section:last-child {
  margin-bottom: 0;
}
.shortcuts-section h3 {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted, #6b7280);
  margin: 0 0 6px;
}
.shortcuts-table {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 16px;
  align-items: center;
}
.shortcuts-row {
  display: contents;
}
.shortcuts-keys {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.shortcuts-keys kbd {
  display: inline-block;
  padding: 2px 6px;
  background: var(--bg, #fff);
  border: 1px solid var(--border, #d1d5db);
  border-bottom-width: 2px;
  border-radius: 4px;
  font-family: -apple-system, BlinkMacSystemFont, 'SF Mono', Menlo, monospace;
  font-size: 12px;
  min-width: 18px;
  text-align: center;
  line-height: 1;
}
.shortcuts-then {
  font-size: 11px;
  color: var(--text-muted, #6b7280);
}
.shortcuts-label {
  font-size: 13px;
}
.shortcuts-hint {
  color: var(--text-muted, #6b7280);
  font-size: 12px;
}

.auth-error {
  font-size: 13px;
  color: #b91c1c;
  background: #fef2f2;
  border: 1px solid #fecaca;
  border-radius: var(--radius-sm, 4px);
  padding: 8px 10px;
}
.auth-recovery-links {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--text-muted, #6b7280);
}
.auth-recovery-links a {
  color: var(--link);
  text-decoration: none;
}
.auth-recovery-links a:hover { text-decoration: underline; }
.auth-recovery-back {
  margin: 4px 0 0;
  font-size: 12px;
}
.auth-recovery-back a {
  color: var(--text-muted, #6b7280);
  text-decoration: none;
}
.auth-recovery-back a:hover {
  color: var(--link);
  text-decoration: underline;
}

/* Feedback modal — see feedbackModal.js. Reuses .auth-field styling
   for inputs; this block covers the textarea and the optional
   context-disclosure section. */
.auth-field textarea {
  padding: 8px 10px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: var(--radius-sm, 4px);
  background: var(--bg, #fff);
  color: var(--text, #111);
  font-size: 14px;
  font-family: inherit;
  resize: vertical;
  min-height: 100px;
}
.feedback-context-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}
.feedback-context-summary {
  background: var(--bg-muted, #f9fafb);
  border: 1px solid var(--border, #d1d5db);
  border-radius: var(--radius-sm, 4px);
  padding: 10px 12px;
  font-size: 13px;
}
.feedback-context-summary .fb-summary-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 12px;
  row-gap: 4px;
  margin: 0;
}
.feedback-context-summary dt {
  color: var(--text-muted, #6b7280);
  font-weight: 500;
}
.feedback-context-summary dd {
  margin: 0;
  word-break: break-word;
}
.feedback-context-summary .fb-candidates {
  margin: 0;
  padding-left: 16px;
}
.feedback-context-summary .fb-pct {
  color: var(--text-muted, #6b7280);
  font-variant-numeric: tabular-nums;
}
.feedback-context-summary .fb-tag {
  color: var(--text-muted, #6b7280);
  font-style: italic;
}
.feedback-success {
  font-size: 13px;
  color: #15803d;
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  border-radius: var(--radius-sm, 4px);
  padding: 8px 10px;
}

/* Devices modal — see devicesModal.js. */
.devices-modal { min-width: 460px; max-width: 620px; }
.devices-hint {
  font-size: 13px;
  color: var(--text-muted, #6b7280);
  margin: 0 0 8px;
}
.devices-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 60vh;
  overflow-y: auto;
}
.devices-row {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.5fr) auto;
  gap: 10px;
  align-items: center;
  padding: 10px 12px;
  border: 1px solid var(--border, #e2e4e8);
  border-radius: var(--radius-sm, 4px);
  background: var(--bg-pane, #fff);
}
.devices-row-current {
  border-color: var(--accent, #4f46e5);
  background: var(--bg-pane-alt, #f5f3ff);
}
.devices-row-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--text, #111);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.devices-row-meta {
  font-size: 12px;
  color: var(--text-muted, #6b7280);
}
.devices-row-actions {
  display: flex;
  gap: 6px;
}
.devices-forget:hover { color: #b91c1c; border-color: #fecaca; }
.devices-empty {
  font-size: 13px;
  color: var(--text-muted, #6b7280);
  font-style: italic;
  padding: 12px 0;
}

/* === URL bar === */

.url-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 20px;
  background: var(--bg-pane);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.url-input {
  flex: 1;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
  outline: none;
}

.url-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.15);
}

.btn-fetch {
  padding: 6px 16px;
  background: var(--accent);
  color: white;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}

.btn-fetch:hover {
  background: #4338ca;
}

.btn-fetch:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-url-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 20px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.btn-url-close:hover {
  color: var(--text);
}

/* === Drop overlay === */

.drop-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(79, 70, 229, 0.08);
  border: 2px dashed var(--accent);
  border-radius: 4px;
  font-size: 18px;
  font-weight: 600;
  color: var(--accent);
  pointer-events: none;
}

.drop-overlay.active {
  display: flex;
}

/* === Editor Panes === */

.editor {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

@media (max-width: 768px) {
  /* Stack panes vertically on narrow screens; the splitter between them
     is interpreted by JS as a y-axis drag (see splitter.js currentAxis). */
  .editor { flex-direction: column; }
  .editor > .splitter-h {
    width: auto;
    height: 1px;
    cursor: ns-resize;
  }
  .editor > .splitter-h .splitter-handle {
    /* Horizontal divider on mobile: handle on the left so it stays
       clear of the Clear/Copy pane-header buttons on the right. */
    left: 18px;
    right: auto;
    top: -11px;
    bottom: auto;
    cursor: ns-resize;
  }
  .editor > .splitter-h .splitter-handle::before { content: '↕'; }
}

.pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  container-type: inline-size;
}

.pane-divider {
  width: 1px;
  background: var(--border);
  flex-shrink: 0;
}

/* Draggable pane splitters. Shared between text and ebook modes — wired
   up by gui/splitter.js. The splitter itself is a thin 1px sliver; the
   visible affordance is a circular handle pinned to one end. */
.splitter {
  position: relative;
  z-index: 49;
  flex-shrink: 0;
  background: var(--border, #d1d5db);
}
.splitter-h { width: 1px; cursor: ew-resize; }
.splitter-v { height: 1px; cursor: ns-resize; }

.splitter-handle {
  position: absolute;
  z-index: 50;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bg-pane, #fff);
  border: 1px solid var(--border, #d1d5db);
  color: var(--text-muted, #6b7280);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  line-height: 1;
  user-select: none;
  opacity: 0.7;
  transition: opacity 120ms ease, background 120ms ease, color 120ms ease;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  touch-action: none;
}
.splitter-handle:hover,
.splitter.dragging .splitter-handle {
  opacity: 1;
  background: var(--text-muted, #6b7280);
  color: var(--bg-pane, #fff);
}
.splitter-h .splitter-handle {
  left: -11px;
  bottom: 18px;
  cursor: ew-resize;
}
.splitter-h .splitter-handle::before { content: '↔'; }
.splitter-v .splitter-handle {
  bottom: -11px;
  right: 18px;
  cursor: ns-resize;
}
.splitter-v .splitter-handle::before { content: '↕'; }
.splitter.dragging { background: var(--text-muted, #6b7280); }

@media (pointer: coarse) {
  .splitter-handle { width: 28px; height: 28px; opacity: 0.9; font-size: 16px; }
  .splitter-h .splitter-handle { left: -14px; }
  .splitter-v .splitter-handle { bottom: -14px; }
}

.pane-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 12px 4px 16px;
  height: 40px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  background: var(--bg-pane);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.pane-header-label {
  flex: 1;
  min-width: 0;
}

.pane-header-btn {
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 3px 10px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.pane-header-btn:hover {
  background: var(--bg);
  color: var(--text);
  border-color: var(--text-muted);
}
.pane-header-btn:active {
  background: var(--border);
}
.pane-header-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Source-pane wrapper that hosts both #source-text and the drop overlay.
   Must participate in flex sizing so the inner #source-text gets a
   bounded height (and thus scrolls instead of pushing the pane open). */
.pane-content-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  position: relative;
}

.pane-note {
  margin: 0;
  padding: 8px 16px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--text-muted);
  background: var(--bg-pane);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  overflow-wrap: anywhere;
}

.pane-content {
  flex: 1;
  padding: 24px 48px;
  overflow-y: auto;
  min-height: 0;
  background: var(--bg-pane);
  /* Unitless line-height so headings can override compactly. Body
     paragraphs land at 16 × 1.6 = 25.6px; same multiplier on the
     Shavian side keeps the two panes visually balanced. */
  line-height: 1.6;
  font-size: 16px;
  outline: none;
  resize: none;
  border: none;
  font-family: inherit;
  color: var(--text);
  word-wrap: break-word;
}

/* Document typography for the two text-mode panes. Both source and
   target render HTML directly (welcome copy + transliterated mirror),
   so they get the same vertical rhythm. */
#source-text.pane-content,
#shavian-output.pane-content {
  white-space: normal;
}
#source-text.pane-content h1,
#shavian-output.pane-content h1 {
  margin: 0.4em 0 0.2em;
  line-height: 1.05;
}
#source-text.pane-content h2,
#shavian-output.pane-content h2 {
  margin: 1.4em 0 0.5em;
  line-height: 1.25;
  font-size: 22px;
  font-weight: 600;
}
#source-text.pane-content h3,
#shavian-output.pane-content h3 {
  margin: 1.2em 0 0.4em;
  line-height: 1.3;
  font-size: 18px;
  font-weight: 600;
}
#source-text.pane-content p,
#shavian-output.pane-content p {
  margin: 0 0 0.9em;
}
#source-text.pane-content code,
#shavian-output.pane-content code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.9em;
  background: var(--bg);
  padding: 1px 5px;
  border-radius: 3px;
}
#source-text.pane-content a:hover,
#shavian-output.pane-content a:hover {
  text-decoration: underline;
}

/* Welcome page heading. Lives in both panes — the engine transliterates
   it across so the title appears in Roman on the left and Shavian on
   the right. */
.welcome-title {
  font-size: clamp(80px, 24cqi, 144px);
  text-align: center;
  margin: 0.2em 0 0;
  line-height: 1;
  letter-spacing: -0.02em;
}

.pane-content:focus {
  box-shadow: inset 0 0 0 2px rgba(79, 70, 229, 0.15);
}

.pane-content:empty::before {
  content: attr(data-placeholder);
  color: var(--text-muted);
  font-style: italic;
  pointer-events: none;
}

.shavian-text {
  font-size: 18px;
  line-height: 1.6;
  white-space: pre-wrap;
}

.shavian-text.placeholder {
  color: var(--text-muted);
  font-family: inherit;
  font-size: 16px;
  font-style: italic;
}

.shavian-text.error {
  color: #991b1b;
}

/* Wrap so the drop overlay can position itself over the editor pane. */
.pane-content-wrap {
  position: relative;
  flex: 1;
  display: flex;
  min-height: 0;
}

/* === Debug Highlights === */

.hl-green {
  background: rgba(34, 197, 94, 0.15);
  border-bottom: 2px solid #22c55e;
  cursor: help;
}

.hl-yellow {
  background: rgba(234, 179, 8, 0.15);
  border-bottom: 2px solid #eab308;
  cursor: help;
}

.hl-red {
  background: rgba(239, 68, 68, 0.15);
  border-bottom: 2px solid #ef4444;
  cursor: help;
}

.hl-orange {
  background: rgba(249, 115, 22, 0.15);
  border-bottom: 2px solid #f97316;
  cursor: help;
}

/* Punctuation / symbol ambiguity — distinct from green/yellow/red (confidence),
   orange (unknown), and purple (user-edit). */
.hl-teal {
  background: rgba(20, 184, 166, 0.15);
  border-bottom: 2px dotted #14b8a6;
  cursor: help;
}

/* === Edited highlight === */

.hl-edited {
  background: rgba(139, 92, 246, 0.15);
  border-bottom: 2px dashed #8b5cf6;
}

/* Accepted words. Quieter than the per-confidence highlights — once
   the user has signed off on a word, the marking is just a faint
   reminder that it's been touched, not a call for attention. */
.hl-done {
  background: transparent;
  border-bottom: 1px dotted rgba(34, 197, 94, 0.6);
  cursor: pointer;
}

/* === Source pane highlights === */

.source-span {
  /* Default: no decoration */
}

/* Source-side event spans render visually invisible — they exist as
   click/hover anchors only. The .bi-highlight class supplies the
   transient indication on mouseover. */
.source-hl-green,
.source-hl-yellow,
.source-hl-red,
.source-hl-orange,
.source-hl-teal {
  /* No decoration. */
}

/* === Bidirectional hover highlight === */

.bi-highlight {
  background: rgba(79, 70, 229, 0.12);
  border-radius: 2px;
}

/* === CorrectionWidget (shared between text-mode popup and ebook review) === */

/* Shared button colours used by .cw-actions buttons. */
.btn-primary,
.btn-secondary {
  padding: 6px 14px;
  border: 0;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
}
.btn-primary:not(:disabled),
.btn-secondary:not(:disabled) {
  cursor: pointer;
}
.btn-primary {
  background: var(--accent);
  color: #fff;
}
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); }
.btn-secondary {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
}
.btn-secondary:hover:not(:disabled) { background: var(--bg-pane); }
.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled {
  opacity: 0.5;
}
.btn-danger {
  padding: 6px 14px;
  border: 0;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  background: #dc2626;
  color: #fff;
}
.btn-danger:not(:disabled) { cursor: pointer; }
.btn-danger:hover:not(:disabled) { background: #b91c1c; }

/* Subdued destructive link-style button. Used for footer-level
 * "Delete account…" affordances — present, but not eye-catching. */
.btn-danger-link {
  color: #b91c1c;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: 13px;
  padding: 6px 10px;
  font-family: inherit;
  cursor: pointer;
  margin-right: auto;
}
.btn-danger-link:hover:not(:disabled) { background: rgba(220, 38, 38, 0.08); }
.btn-danger-link:disabled { opacity: 0.5; cursor: default; }

.profile-email-readonly {
  color: var(--text-muted, #6b7280);
  font-size: 14px;
  padding: 6px 0;
}

.profile-storage-text {
  color: var(--text-muted, #6b7280);
  font-size: 13px;
  padding: 2px 0;
}
.profile-storage-bar {
  width: 100%;
  height: 8px;
  background: var(--bg-pane, #f0f0f0);
  border-radius: 4px;
  overflow: hidden;
  margin-top: 4px;
}
.profile-storage-bar-fill {
  height: 100%;
  width: 0;
  background: var(--accent, #2563eb);
  transition: width 200ms ease;
}
.profile-storage-bar-fill-warn {
  background: var(--warning, #d97706);
}
.profile-storage-warning {
  font-size: 12px;
  color: var(--warning, #d97706);
  margin-top: 4px;
}

/* Muted secondary line in confirm modals (e.g. "3 users have this book"). */
.confirm-detail {
  color: var(--text-muted, #6b7280);
  font-size: 13px;
}

/* The sync Review's list of a book's waiting edits. */
.sync-review-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  max-height: 40vh;
  overflow-y: auto;
}
.sync-review-list li {
  display: flex;
  flex-direction: column;
  padding: 6px 0;
  border-bottom: 1px solid var(--border, #e5e7eb);
}
.sync-review-why,
.sync-review-more {
  color: var(--text-muted, #6b7280);
  font-size: 13px;
}

.cw-root {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: var(--bg-pane);
}

.cw-header {
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.cw-word {
  font-size: 20px;
  font-weight: 600;
}
.source-line-break::after {
  content: '\23CE';
}
.cw-type {
  font-size: 13px;
  color: var(--text-muted);
  background: var(--bg);
  padding: 2px 8px;
  border-radius: 3px;
}
.cw-contributor {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  background: rgba(79, 70, 229, 0.10);
  padding: 2px 6px;
  border-radius: 3px;
}
.cw-location {
  font-size: 13px;
  color: var(--text-muted);
  margin-left: auto;
}

.cw-input {
  width: 100%;
  padding: 10px 14px;
  border: 2px solid var(--border);
  border-radius: 6px;
  font-size: 20px;
  outline: none;
  background: var(--bg-pane);
  box-sizing: border-box;
}
.cw-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.12);
}

/* Read-only widget: damp the chrome so the input doesn't look
   inviting. Dotted border, muted background, no focus halo, default
   text cursor instead of the I-beam. */
.cw-readonly .cw-input {
  border-style: dashed;
  border-color: var(--border);
  background: var(--bg, #f3f4f6);
  color: var(--text-muted, #6b7280);
  cursor: default;
}
.cw-readonly .cw-input:focus {
  border-color: var(--border);
  box-shadow: none;
}
.cw-readonly .cw-alt { cursor: default; opacity: 0.65; }
.cw-readonly .cw-alt:hover { background: transparent; }

.cw-error {
  margin-top: -4px;
  padding: 8px 12px;
  border-radius: 4px;
  background: #fee2e2;
  color: #991b1b;
  font-size: 13px;
  border: 1px solid #fca5a5;
}

.cw-alts {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  min-height: 0;
}
.cw-alt {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  cursor: pointer;
  border-radius: 4px;
}
.cw-alt:hover { background: var(--bg); }
.cw-alt.selected { background: rgba(79, 70, 229, 0.08); }
.cw-alt-num {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  width: 18px;
  text-align: right;
  flex-shrink: 0;
}
.cw-alt-text {
  font-size: 18px;
  flex-shrink: 0;
}
.cw-alt-conf {
  font-size: 12px;
  color: var(--text-muted);
  flex-shrink: 0;
  width: 36px;
}
.cw-alt-bar {
  flex: 1;
  height: 8px;
  background: var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.cw-alt-bar-fill {
  height: 100%;
  border-radius: 4px;
  transition: width 0.2s;
}
.cw-alt-bar-fill.green  { background: #22c55e; }
.cw-alt-bar-fill.yellow { background: #eab308; }
.cw-alt-bar-fill.red    { background: #ef4444; }
.cw-alt-bar-fill.orange { background: #f97316; }

.cw-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

/* Mac-style line-art icons inside editor-widget buttons. */
.cw-icon-btn .shave-icon,
.cw-pick-btn .shave-icon {
  width: 14px;
  height: 14px;
  display: block;
}
.cw-pick-btn .shave-icon {
  width: 18px;
  height: 18px;
}
/* Undo/redo are global (book-level), not per-issue — push them to the
   far right with a separator gap so they don't read as part of the
   per-issue action group. */
.cw-actions .cw-btn-undo { margin-left: auto; }

/* Compact (text-mode) popup variant */
.cw-root.cw-compact {
  position: fixed;
  z-index: 100;
  min-width: 280px;
  max-width: 380px;
  padding: 10px 12px;
  gap: 8px;
  background: var(--bg-pane);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.cw-compact .cw-word { font-size: 14px; }
.cw-compact .cw-type { font-size: 11px; padding: 1px 6px; }
.cw-compact .cw-location { display: none; }
.cw-compact .cw-input { padding: 6px 10px; font-size: 16px; }
.cw-compact .cw-alt { padding: 4px 8px; gap: 8px; }
.cw-compact .cw-alt-text { font-size: 16px; }
.cw-compact .cw-alt-bar { display: none; }
.cw-compact .cw-actions { gap: 4px; }
.cw-compact .cw-actions .btn-primary,
.cw-compact .cw-actions .btn-secondary {
  padding: 3px 8px;
  font-size: 12px;
}

.cw-picker {
  display: flex;
  gap: 4px;
}
/* The picker hosts the annotation toolbar (swatches + actions) as a flex strip
   of buttons. */
.cw-picker-annotation {
  display: flex;
  gap: 4px;
}
.cw-root.cw-mode-picker {
  padding: 4px;
  min-width: 0;
  width: max-content;
}
.cw-pick-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--border);
  background: var(--bg);
  /* SVG icons inside use stroke="currentColor" — without an explicit
     color rule, the browser's button default (≈ black `ButtonText`)
     leaks through and stays black under dark mode. */
  color: var(--text);
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
}
.cw-pick-btn:hover {
  background: var(--accent);
  color: white;
}


/* === Web mode === */

/* Blank state: centered URL input. Reuses the .pane look-and-feel via
   inline classes; we don't need a full layout. */
.web-blank {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px;
  gap: 16px;
  min-height: 0;
}

.web-url-form {
  display: flex;
  gap: 8px;
  width: min(640px, 100%);
}

.web-url-form .url-input-wrap {
  flex: 1;
  position: relative;
  display: flex;
}

.web-url-form input {
  flex: 1;
  padding: 10px 36px 10px 14px;
  font-size: 16px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-pane);
  color: var(--text);
}

.web-blank-hint {
  color: var(--text-muted);
  font-size: 14px;
  max-width: 520px;
  text-align: center;
  line-height: 1.5;
}

/* Loaded state: two iframes side-by-side. The URL bar lives in the
   toolbar (.toolbar-url-form), so the loaded pane is just a flex
   container holding the panes + splitter. */
.web-loaded {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
}

/* Toolbar URL bar — only shown in the loaded state (web.js toggles
   display). Stretches to fill the middle of the toolbar so the
   address is the primary affordance, with dialect/confidence sitting
   to the right of it. */
.toolbar-url-form {
  flex: 1;
  min-width: 0;
  display: flex;
}

.toolbar-url-form .url-input-wrap {
  flex: 1;
  min-width: 0;
  position: relative;
  display: flex;
}

.toolbar-url-form input {
  flex: 1;
  min-width: 0;
  padding: 5px 28px 5px 10px;
  font-size: 13px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg-pane);
  color: var(--text);
}

/* Dropdown arrow rendered inside the URL input (Chrome/Safari address-bar
   style). Opens the recents popup; pointer-only — keyboard cycles directly
   on the input via Down/Up. */
.url-input-arrow {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  border: 0;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  padding: 4px 6px;
}
.url-input-arrow:hover { color: var(--text); }

/* Recents popup — absolutely positioned under its input, matching its
   width. Black-on-white rows with a hover/active state; one click on a
   row submits the URL. */
.recents-popup {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  margin: 2px 0 0 0;
  padding: 4px 0;
  list-style: none;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg-pane);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  z-index: 100;
}

.recents-popup li {
  padding: 6px 12px;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recents-popup li:hover,
.recents-popup li.active {
  background: var(--bg-pane-alt);
}

.web-panes {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

@media (max-width: 768px) {
  .web-panes { flex-direction: column; }
  .web-panes > .splitter-h {
    width: auto;
    height: 1px;
    cursor: ns-resize;
  }
  /* When the web-panes splitter flips to a horizontal bar, its handle
     needs the vertical-splitter look — bottom-right anchor + ↕ glyph. */
  .web-panes > .splitter-h .splitter-handle {
    left: auto;
    bottom: -11px;
    right: 18px;
    cursor: ns-resize;
  }
  .web-panes > .splitter-h .splitter-handle::before { content: '↕'; }
}

.web-iframe {
  flex: 1;
  width: 100%;
  border: 0;
  background: white;
}

.web-panes:has(.splitter.dragging) .web-iframe {
  pointer-events: none;
}

.sidebar-collapse-btn,
.sidebar-expand-btn {
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted, #6b7280);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.sidebar-collapse-btn:hover,
.sidebar-expand-btn:hover {
  background: var(--border, #d1d5db);
  color: var(--text, #111);
}
@media (pointer: coarse) {
  .sidebar-collapse-btn, .sidebar-expand-btn { padding: 6px 10px; font-size: 22px; }
}

.web-source-rail {
  display: none;
  flex: 0 0 28px;
  background: var(--bg-pane);
  border-right: 1px solid var(--border);
  align-items: flex-start;
  justify-content: center;
  padding-top: 12px;
}
body.web-source-collapsed .pane-source,
body.web-source-collapsed #splitter-web-panes { display: none; }
body.web-source-collapsed .web-source-rail { display: flex; }

/* Admin actions modal */
.admin-modal { max-width: 800px; }
/* The admin *actions* modal is feature-rich (Users + Books tables, several
   columns each), so it gets a roomy near-full-screen surface. Scoped to
   .admin-actions-modal so the profile modal (which also uses .admin-modal)
   stays compact. Clamps to 95vw on narrow screens. The .modal-body scrolls
   internally via the shared .modal flex-column + body overflow. */
.admin-actions-modal { max-width: min(1200px, 95vw); max-height: 90vh; }
.admin-users-section { gap: 8px; }
.admin-books-section { gap: 8px; }
.admin-users-table-wrap,
.admin-books-table-wrap {
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--border, #e5e5e5);
  border-radius: 6px;
  max-height: 50vh;
  overflow-y: auto;
}
.admin-users-loading {
  padding: 16px;
  text-align: center;
  color: var(--text-muted, #555);
  font-size: 13px;
}
.admin-users-empty {
  padding: 16px;
  text-align: center;
  color: var(--text-muted, #555);
  font-size: 13px;
  font-style: italic;
}
/* width:100% fills the roomy desktop wrap; min-width:max-content stops the
   table shrinking below its natural width on narrow screens, so the wrap's
   overflow-x:auto scrolls instead of crushing columns (the Users table has
   an extra email column + a non-collapsible actions column that otherwise
   starve email to nothing on mobile). */
.admin-users-table {
  width: 100%;
  min-width: max-content;
  border-collapse: collapse;
  font-size: 13px;
}
.admin-users-table th,
.admin-users-table td {
  padding: 6px 10px;
  text-align: left;
  border-bottom: 1px solid var(--border, #e5e5e5);
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Column caps are generous now that the modal is wide; ellipsis +
   overflow-x:auto on the wrap remain the safety net for pathological values. */
.admin-users-table .admin-users-handle { max-width: 28ch; }
.admin-users-table .admin-users-email { max-width: 40ch; }
.admin-users-table .admin-books-title { max-width: 56ch; }
.admin-users-table .admin-books-owner { max-width: 28ch; }
.admin-users-table thead th {
  position: sticky;
  top: 0;
  background: var(--bg-pane, #fafafa);
  font-weight: 600;
  font-size: 12px;
  color: var(--text-muted, #555);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  user-select: none;
}
.admin-users-sortable { cursor: pointer; }
.admin-users-sortable:hover { color: var(--text, #111); }
.admin-users-sort-asc::after { content: ' ▲'; font-size: 9px; }
.admin-users-sort-desc::after { content: ' ▼'; font-size: 9px; }
.admin-users-num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-users-date { font-variant-numeric: tabular-nums; color: var(--text-muted, #555); }
.admin-books-schema { font-variant-numeric: tabular-nums; color: var(--text-muted, #555); }
/* Amber flag for books behind the current schema version. */
.admin-books-schema.admin-schema-old { color: var(--warning, #d97706); font-weight: 600; }
.admin-users-row-anon .admin-users-handle { color: var(--text-muted, #555); font-style: italic; }
.admin-users-actions {
  display: flex;
  gap: 4px;
  justify-content: flex-end;
  flex-wrap: wrap;
}
.admin-users-action {
  padding: 3px 8px !important;
  font-size: 11px !important;
  white-space: nowrap;
}
.admin-books-title-link {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--accent, #4f46e5);
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.admin-books-title-link:hover { color: var(--accent-hover, #4338ca); }
.admin-books-title-link:focus-visible {
  outline: 2px solid var(--accent, #2563eb);
  outline-offset: 2px;
}

/* Aggregate row above the admin Books table: total disk used across all
   sessions + host free. Muted typography because it's a header, not a
   row in the data set. */
.admin-books-totals {
  font-size: 12px;
  color: var(--text-muted, #555);
  padding: 4px 0 8px;
}

/* ─── Usage stats section ─── */
.admin-stats-online {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text, #111);
}
.admin-stats-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-muted, #999);
  flex: none;
}
.admin-stats-dot-live {
  background: #16a34a;
  box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.2);
}
.admin-stats-buckets {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}
.admin-stats-bucket-btn {
  padding: 4px 12px;
  font-size: 12px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: 6px;
  background: var(--bg, #fff);
  color: var(--text-muted, #555);
  cursor: pointer;
}
.admin-stats-bucket-btn:hover { color: var(--text, #111); }
.admin-stats-bucket-active {
  background: var(--accent, #4f46e5);
  border-color: var(--accent, #4f46e5);
  color: #fff;
}
.admin-stats-bucket-active:hover { color: #fff; }
/* Column-group headers straddle the total/anon/reg sub-columns, so they
   centre rather than left-align like the data-column headers. */
.admin-stats-table thead th[colspan] { text-align: center; }
.admin-stats-total-row td {
  font-weight: 600;
  border-top: 2px solid var(--border, #e5e5e5);
}
.admin-stats-asof {
  font-size: 11px;
  color: var(--text-muted, #777);
  font-style: italic;
}

.admin-events-toggle {
  align-self: flex-start;
  padding: 2px 0;
  border: 0;
  background: none;
  color: var(--accent, #4f46e5);
  font-size: 12px;
  cursor: pointer;
}
.admin-events-toggle:hover { text-decoration: underline; }
.admin-events-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.admin-events-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
}
.admin-events-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  color: var(--text-muted, #555);
}
.admin-events-field input,
.admin-events-field select {
  padding: 5px 8px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: 6px;
  font-size: 13px;
  font-family: inherit;
  background: var(--bg, #fff);
  max-width: 100%;
}
.admin-events-field input[type=text] { min-width: 14ch; }
.admin-events-field input[type=number] { width: 10ch; }
.admin-events-apply { align-self: flex-end; white-space: nowrap; }
#admin-events-table-wrap .admin-events-visitor-cell,
#admin-events-table-wrap .admin-events-subject-cell { max-width: 22ch; }
.admin-events-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-muted, #555);
}
.admin-events-pager-summary { margin-right: auto; }
.admin-events-page-btn { padding: 4px 12px; font-size: 12px; }

@media (max-width: 640px) {
  .admin-modal { max-width: 100%; }
  .admin-actions-modal { max-width: 95vw; }
  .admin-users-table { font-size: 12px; }
  .admin-users-table th,
  .admin-users-table td { padding: 4px 6px; }
  .admin-events-field { flex: 1 1 100%; }
  .admin-events-field input,
  .admin-events-field select { width: 100%; }
}

.admin-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border, #e5e5e5);
}
.admin-section:last-of-type { border-bottom: 0; padding-bottom: 0; }
.admin-section h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted, #555);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.admin-row {
  display: flex;
  gap: 8px;
  align-items: center;
}
.admin-row input[type=text],
.admin-row select {
  flex: 1;
  padding: 6px 10px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
}
.admin-row select { background: var(--bg, #fff); }
.admin-row .btn-secondary { white-space: nowrap; }
.admin-danger {
  background: #dc2626 !important;
  color: #fff !important;
  border-color: #dc2626 !important;
}
.admin-danger:hover:not(:disabled) { background: #b91c1c !important; }
.admin-result {
  font-size: 12px;
  min-height: 1.2em;
}
.admin-result-ok { color: #16a34a; }
.admin-result-err { color: #dc2626; }

/* admin-picker is always nested inside the admin modal (z-index 11000),
   so its baseline sits above the parent backdrop. ShaveModalShell.open
   overrides this with computeStackedZIndex when the picker is opened
   through it; the CSS rule is the static fallback for any mount that
   doesn't route through the shell. */
.admin-picker-backdrop {
  z-index: 11500;
  align-items: center;
  justify-content: center;
}
.admin-picker-modal {
  background: var(--bg, #fff);
  border-radius: 8px;
  padding: 16px 18px;
  min-width: 280px;
  max-width: 90vw;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.admin-picker-modal h3 {
  margin: 0;
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted, #555);
}
.admin-picker-select { width: 100%; padding: 6px 8px; }
/* Anon owner entries in the transfer-owner dropdown — render visually
 * subtle so admins instinctively read the list as "named accounts first,
 * anons grouped below." Browsers vary in how much <option> styling they
 * honour, but italic + muted colour is broadly respected. */
.admin-picker-option-anon {
  font-style: italic;
  color: var(--text-muted);
}
.admin-picker-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* A reader's bookshelf and a book's readers, over the admin modal: the
   dashboard's tiles, view only, and a table of readers. */
.admin-shelf-modal,
.admin-readers-modal {
  width: min(720px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
}
.admin-popup-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  min-height: 0;
}
.admin-shelf-search { width: 100%; padding: 6px 8px; }
.admin-shelf-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 10px;
}
.admin-shelf-tile { cursor: default; }

.admin-msg-composer {
  flex: 1;
  min-height: 60px;
  padding: 8px 10px;
  border: 1px solid var(--border, #d1d5db);
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
  background: var(--bg, #fff);
}

.admin-msg-composer:empty:before {
  content: attr(data-placeholder);
  color: var(--text-muted, #888);
  pointer-events: none;
}

.admin-msg-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 320px;
  overflow-y: auto;
  margin-top: 8px;
}

.admin-msg-empty {
  color: var(--text-muted, #555);
  font-size: 13px;
  font-style: italic;
}

.admin-msg-item {
  border: 1px solid var(--border, #e5e5e5);
  border-radius: 6px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: var(--bg-pane, #fafafa);
}

.admin-msg-item-head {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--text-muted, #555);
}

.admin-msg-item-body {
  font-size: 13px;
  color: var(--text, #111);
  line-height: 1.4;
}

.admin-msg-item-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

.admin-msg-item-actions .btn-secondary {
  padding: 4px 8px;
  font-size: 12px;
}

.profile-delete-hint {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted, #555);
}

/* Anonymous-data recovery banner — pinned to the top of <body>. */
.anonymous-data-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  background: #2563eb;
  color: #fff;
  padding: 8px 16px;
  font-size: 14px;
  z-index: 100;
}
.anonymous-data-banner-label { font-weight: 500; }
.anonymous-data-banner-btn {
  background: rgba(0, 0, 0, 0.2);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 13px;
  cursor: pointer;
}
.anonymous-data-banner-btn:hover:not(:disabled) { background: rgba(0, 0, 0, 0.35); }
.anonymous-data-banner-btn:disabled { opacity: 0.5; cursor: default; }
.anonymous-data-banner-dismiss {
  color: #fff;
  text-decoration: underline;
  font-size: 13px;
  opacity: 0.9;
}
.anonymous-data-banner-dismiss:hover { opacity: 1; }

/* === Dark-mode tone-shifts === Translucent green/red/amber tints on
   dark surfaces read as muddy mid-greys with dark text below contrast.
   Lift the alphas and brighten the foreground for the small set of
   recurring inline-status patterns. Paper-keeps-light surfaces inherit
   the light cascade so anything rendered inside a paper surface needs
   no change. */
:root[data-theme="dark"] .feedback-success {
  color: #86efac;
  background: rgba(34, 197, 94, 0.16);
  border-color: rgba(34, 197, 94, 0.35);
}
:root[data-theme="dark"] .auth-error,
:root[data-theme="dark"] .preview-error {
  color: #fca5a5;
  background: rgba(220, 38, 38, 0.16);
  border-color: rgba(220, 38, 38, 0.35);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .feedback-success {
    color: #86efac;
    background: rgba(34, 197, 94, 0.16);
    border-color: rgba(34, 197, 94, 0.35);
  }
  :root:not([data-theme="light"]) .auth-error,
  :root:not([data-theme="light"]) .preview-error {
    color: #fca5a5;
    background: rgba(220, 38, 38, 0.16);
    border-color: rgba(220, 38, 38, 0.35);
  }
}

/* === Landscape-phone chrome reclaim ===
   A phone in landscape is short vertically, and the persistent toolbar +
   footer eat the scarce height. Reclaim it: at (orientation: landscape)
   AND a phone-sized height, collapse the toolbar and footer the same way
   fullscreen does. The reader then re-fits to the taller area on its own
   — measureChromeHeights() reads the now-zero-height chrome and
   getGeometry() re-derives the page against the reclaimed height (the
   orientationchange listener forces the re-derive). Tablets in landscape
   have heights >= 600px, so the max-height gate leaves them untouched.
   Pure CSS — no JS, no Screen Orientation API (which requires
   fullscreen/PWA and would silently fail). */
@media (orientation: landscape) and (max-height: 480px) {
  .toolbar,
  .app-footer {
    display: none !important;
  }
  body.reader-mode .reader-root {
    top: 0;
    bottom: 0;
  }
}
