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

  :root {
    --bg: #0a0a0b; --surface: #141416; --surface2: #1c1c20;
    --border: #27272b; --text: #e8e8ed; --text2: #8a8a94;
    /* Border for interactive controls whose outline is their only affordance
       (form fields, toggle tracks). On dark it equals --border; the light block
       deepens it to clear 3:1 on paper. */
    --border-strong: var(--border);
    --accent: #f5f5f7; --amber: #f59e0b; --amber2: #d97706;
    /* Ink for text/glyphs sitting ON an amber fill (buttons, active pills,
       badges, toggle knobs). Dark theme's amber is bright, so black reads best;
       the light block flips it to white because light's amber is deep. One
       token keeps ~14 call sites from hardcoding a color that's only legible
       in one theme. */
    --on-amber: #000;
    --amber-glow: rgba(245, 158, 11, 0.08); --amber-border: rgba(245, 158, 11, 0.25);
    /* text3 is reserved for large-text (≥18px or ≥14px bold) only —
       it's WCAG-AA compliant against --bg at large sizes (3:1) but
       not at normal body text. Use --text2 for everything else. */
    --text3: #6e6e7a; --text-dim: #6e6e7a; --hover: rgba(245, 158, 11, 0.08);
    --error: #ef4444;
    /* Translucent bar/menu surface (topbar frosted glass). Tokenised so the
       light theme can flip it without every backdrop rule carrying a literal. */
    --topbar-bg: rgba(10, 10, 11, 0.85);
    /* Raised popover/menu fill (context menus, drawers). One token so the
       three call sites can't drift, and the light theme flips it in one place. */
    --card-bg: #1a1a1a;
    /* Positive/"sharing active" semantics (seed + peer badges). --success is the
       text/icon green; the faint translucent fills stay inline (they read on both
       themes). Light theme darkens the ink for contrast on paper. */
    --success: #6ec56e; --success-strong: #84d684;
    --radius: 10px; --shadow-popup: 0 8px 32px rgba(0,0,0,0.5);
    /* Height of the connection banner (0 when connected). Measured by
       _syncConnBannerHeight() because the banner wraps on narrow screens.
       Every surface anchored under the 56px topbar offsets by it, so the
       banner PUSHES content instead of covering it — an offline notice must
       never sit on top of the article someone is reading. */
    --conn-h: 0px;
    --under-topbar: calc(56px + var(--conn-h));
    /* Where the header goes when it steps aside: its own height and a pixel
       more, so a part pixel of rounding never leaves its bottom border
       showing as a line along the top of the page. */
    --topbar-away: calc(-100% - 1px);
    color-scheme: dark;
    font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", sans-serif;
  }

  /* ── Light theme ──
     Warm-paper, soft-contrast counterpart to the default dark palette — NOT
     stark white. The amber accent deepens to a burnt amber (#b45309) so it keeps
     AA contrast as text/link on paper AND still reads as a warm fill with black
     label text; every `var(--amber*)` reference adapts at once (no per-selector
     churn). Applied by [data-theme] on <html>, stamped synchronously in the head
     bootstrap so there's no dark flash. Auto mode resolves to dark or light and
     sets the same attribute, so the CSS has exactly one code path.
     Deliberate seam: the almanac overlay + the home "Today"/space cards keep
     their dark cosmic identity in both themes (a night sky is a night sky). */
  :root[data-theme="light"] {
    --bg: #f2efe8; --surface: #fbfaf6; --surface2: #ffffff;
    --border: #e4dfd4; --text: #26241f; --text2: #6a675e;
    /* Amber deepened one more step (#b45309 → #9d4a08) so it clears WCAG-AA as
       text/link on paper (5.35:1 on --bg) AND as small amber ink on the faint
       amber-glow badge fills (updated / installed / multilingual: 4.70:1). The
       fills flip their ink to white via --on-amber, so a deeper amber only
       helps them too. */
    --accent: #1a1917; --amber: #9d4a08; --amber2: #92400e;
    --amber-glow: rgba(180, 83, 9, 0.10); --amber-border: rgba(180, 83, 9, 0.28);
    /* On paper the amber fill is deep, so ink on it flips to white (6.15:1). */
    --on-amber: #fff;
    /* text3 deepened (#7c786e → #6f6b60) so the muted 10–12px labels that use it
       (download sizes, field notes, dropdown sublabels) clear AA at 4.63:1 —
       the dark-theme "large text only" caveat doesn't survive on paper. */
    --text3: #6f6b60; --text-dim: #6f6b60; --hover: rgba(180, 83, 9, 0.10);
    --error: #dc2626;
    /* Interactive-control borders that carry meaning (form fields, toggle
       tracks). The decorative --border (#e4dfd4) is only ~1.2:1 on paper — fine
       for card separators that also have a fill change, too weak for a control
       whose outline is its only affordance. This deeper grey hits 3:1 on --bg
       (3.30) and on --surface (3.63). */
    --border-strong: #8c826f;
    /* Card definition on paper: a firmer edge than the decorative --border plus
       a soft warm shadow, so tiles/detail cards read as cards, not loose blocks. */
    --card-border: #ddd6c8;
    --card-shadow: 0 1px 2px rgba(66, 54, 30, 0.06), 0 3px 8px rgba(66, 54, 30, 0.05);
    --topbar-bg: rgba(250, 249, 246, 0.85);
    --card-bg: #ffffff;
    --success: #2f7d32; --success-strong: #256428;
    --shadow-popup: 0 8px 32px rgba(60, 50, 30, 0.16);
    color-scheme: light;
  }
  /* Two single-use status inks that are legible on dark but wash out on paper:
     the "reused download" teal and the "stale" ochre. Darkened here rather than
     tokenised — one call site each, so a token would be ceremony. */
  :root[data-theme="light"] .dl-source-reuse { color: #0f766e; }
  :root[data-theme="light"] .ci-hier-freshness { color: #a16207; }
  /* Almanac seam (intentional): the almanac is a canvas-driven night-sky app —
     its orrery/sky/star-field paint hardcoded cosmic colors that only read on
     dark. Rather than ship a half-lit almanac, we keep its whole subtree dark
     under the light app. Custom properties inherit, so redefining the token set
     on the overlay container re-establishes the dark palette for every child
     (cards, text, pills) without touching almanac.css. The result is a light app
     chrome over a deliberately dark cosmic panel — a seam by design, not a bug. */
  /* The whole chrome joins the seam while the almanac is open (body.almanac-mode):
     a light header floating over the night-sky panel was the one jarring strip
     left in the light theme. The tokens scope to the BODY, not just .topbar, for
     two reasons found the hard way: (1) .topbar declares no `color`, so its
     glyphs (logo, breadcrumb calendar icon) inherit body's computed light-theme
     ink — retinting tokens on .topbar alone left dark ink on a dark bar; and
     (2) --topbar-bg is translucent, so the bar composites over the body
     background — over paper it rendered a lighter, warmer strip than the true
     dark topbar. On body, `background: var(--bg)` and `color: var(--text)`
     re-resolve with the dark set, making the chrome pixel-identical to the dark
     theme. html.almanac-boot covers the reload-into-almanac window before
     almanac.js has added body.almanac-mode, so even the first paint is dark.
     The classes come off with the almanac, so everything reverts on leave. */
  :root[data-theme="light"] .almanac-view,
  :root[data-theme="light"] body.almanac-mode,
  :root[data-theme="light"].almanac-boot body {
    --bg: #0a0a0b; --surface: #141416; --surface2: #1c1c20;
    --border: #27272b; --text: #e8e8ed; --text2: #8a8a94;
    --accent: #f5f5f7; --amber: #f59e0b; --amber2: #d97706;
    --amber-glow: rgba(245, 158, 11, 0.08); --amber-border: rgba(245, 158, 11, 0.25);
    --text3: #6e6e7a; --text-dim: #6e6e7a; --hover: rgba(245, 158, 11, 0.08);
    --error: #ef4444; --card-bg: #1a1a1a;
    --success: #6ec56e; --success-strong: #84d684;
    /* Dark-only tokens the light root overrides above: the topbar (and its
       menu) read these, so they must flip back with the rest of the set. */
    --on-amber: #000; --border-strong: var(--border);
    --topbar-bg: rgba(10, 10, 11, 0.85);
    --shadow-popup: 0 8px 32px rgba(0, 0, 0, 0.5);
    /* Light-only card tokens return to their dark-theme fallbacks (usage is
       var(--card-border, var(--border)) / var(--card-shadow, none)). */
    --card-border: var(--border); --card-shadow: none;
    color-scheme: dark;
  }
  /* Reload-into-almanac boot gate. The head bootstrap stamps html.almanac-boot
     synchronously when the URL hash is #almanac (removed by _openAlmanacInner,
     or by the load-failure paths), so the home/library view never paints first
     and the almanac's dark shell shows immediately — before almanac.js loads. */
  html.almanac-boot #main-view { display: none !important; }
  html.almanac-boot #almanac-view { display: flex; }
  /* Reload-into-Create boot gate, same contract as the almanac one above:
     stamped by the head bootstrap for #create, dropped by _openCreateInner (or
     by the load-failure paths), so a shared Create URL opens ON Create instead
     of flashing the library first. */
  html.create-boot #main-view { display: none !important; }
  html.create-boot #create-view { display: flex; }
  /* Reload-into-Manage boot gate. Manage renders INTO #main-view (no view of
     its own), so unlike the two above the gate can't swap containers — it hides
     main-view's content (visibility, not display, so renderManage can still
     measure layout) until enterManage has drawn manage in place and dropped the
     class. If the client may not manage, enterManage drops the class and shows
     the library instead. */
  html.manage-boot #main-view { visibility: hidden; }

  /* Pulled past an edge, the page shows its own background, not the
     browser's white, and does not spring: no bounce either way, no sideways
     scroll. */
  html { height: 100%; overflow-y: auto; overflow-x: hidden; overscroll-behavior: none; background: var(--bg); }
  /* A pinch zooms; nothing zooms on its own. A double tap is a tap, not a
     zoom; the text keeps its size when the
     phone turns; and a field or menu gets 16px type on a touch screen,
     because iOS zooms the page into any it focuses that is smaller. The
     !important outranks the inline sizes a few controls are drawn with. */
  html { touch-action: manipulation; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  @media (pointer: coarse) {
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
    select, textarea { font-size: 16px !important; }
  }
  body { background: var(--bg); color: var(--text); min-height: 100%; padding-top: var(--under-topbar); overflow: visible; }

  /* Anti-flash guard. The head bootstrap stamps .theme-boot on <html> while it
     (re)applies the theme, then removes it on the next committed paint. While it
     is present, every element's transitions are killed so a bfcache restore /
     tab re-activation can't animate a dark->light token flip on pills, cards,
     and toggles (the residual Safari flash). Animations are left alone — only
     the colour transitions cause the flash. */
  html.theme-boot *,
  html.theme-boot *::before,
  html.theme-boot *::after { transition: none !important; }

  /* ── Accessibility ── */
  /* Visually-hide content but keep it readable for screen readers. */
  .sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0, 0, 0, 0);
    white-space: nowrap; border: 0;
  }
  /* Skip link — visually hidden until keyboard focus brings it onto screen.
     First tab-stop on the page so keyboard users can jump past the topbar. */
  .skip-link {
    position: absolute; top: -100px; left: 12px;
    background: var(--amber); color: var(--on-amber);
    padding: 8px 16px; border-radius: 6px;
    font-weight: 600; font-size: 14px;
    z-index: 10000; text-decoration: none;
    transition: top 0.15s ease;
  }
  .skip-link:focus,
  .skip-link:focus-visible {
    top: 12px; outline: 2px solid var(--amber); outline-offset: 2px;
  }
  /* Visible focus ring across the SPA. The default browser focus ring
     is invisible-on-dark for many elements; this guarantees a high-
     contrast outline that works on both light and dark themes. */
  :focus-visible {
    outline: 2px solid var(--amber);
    outline-offset: 2px;
  }
  /* Buttons / inputs that already style their own focus state opt out. */
  button:focus:not(:focus-visible),
  input:focus:not(:focus-visible),
  textarea:focus:not(:focus-visible),
  select:focus:not(:focus-visible),
  a:focus:not(:focus-visible) { outline: none; }

  /* ── Topbar ── */
  .topbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 200;
    height: 56px; display: flex; align-items: center; gap: 10px;
    padding: 0 16px; background: var(--topbar-bg); backdrop-filter: blur(20px) saturate(180%);
    -webkit-backdrop-filter: blur(20px) saturate(180%);
    border-bottom: 1px solid var(--border);
  }
  .topbar .logo {
    font-size: 18px; font-weight: 700; letter-spacing: -0.5px;
    text-decoration: none; white-space: nowrap; flex-shrink: 0; cursor: pointer;
    background: linear-gradient(135deg, #f59e0b, #f97316, #ef4444);
    -webkit-background-clip: text; -webkit-text-fill-color: transparent;
    background-clip: text;
  }

  /* ── Connection banner ──
     Shown whenever the app cannot vouch for what it is displaying: the server
     is unreachable, or answered but the library did not load. Calm by design —
     a thin bar in the app's own surface colours with an amber status dot, not a
     red alarm. Being offline is normal for an offline-first app; it should feel
     handled. Never a modal: cached articles, bookmarks and the almanac stay
     usable behind it. */
  .conn-banner {
    position: fixed; top: 56px; left: 0; right: 0; z-index: 190;
    display: flex; align-items: center; gap: 10px;
    padding: 8px 16px; font-size: 13px; line-height: 1.35;
    color: var(--text2); background: var(--surface);
    border-bottom: 1px solid var(--amber-border);
  }
  .conn-banner .conn-dot {
    width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
    background: var(--amber); box-shadow: 0 0 0 3px var(--amber-glow);
    animation: conn-pulse 2.4s ease-in-out infinite;
  }
  @keyframes conn-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
  .conn-banner .conn-msg { flex: 1; min-width: 0; }
  .conn-retry {
    flex-shrink: 0; font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
    color: var(--amber); background: var(--amber-glow);
    border: 1px solid var(--amber-border); border-radius: 6px; padding: 4px 12px;
  }
  .conn-retry:hover:not(:disabled) { background: var(--amber); color: var(--on-amber); border-color: var(--amber); }
  .conn-retry:disabled { opacity: 0.5; cursor: default; }
  .conn-retry-inline { margin-top: 16px; padding: 6px 16px; font-size: 13px; }
  .conn-empty .hint { opacity: 0.75; max-width: 34em; margin: 0 auto; }
  @media (prefers-reduced-motion: reduce) { .conn-banner .conn-dot { animation: none; } }
  @media (max-width: 600px) { .conn-banner { padding: 8px 12px; gap: 8px; font-size: 12.5px; } }

  .back-btn {
    display: none; align-items: center; justify-content: center;
    background: none; border: 1px solid transparent; border-radius: var(--radius);
    color: var(--text2); font-size: 16px; padding: 4px 6px; cursor: pointer;
    flex-shrink: 0; transition: all 0.15s; line-height: 1;
  }
  .back-btn:hover,
  .random-btn:hover, .manage-btn:hover, .newtab-btn:hover, .library-btn:hover,
  .create-btn:hover,
  .reader-btn:hover,
  .catalog-lang-search-btn:hover,
  .lang-selector-btn:hover,
  .library-btn.panel-open,
  .reader-btn.panel-open,
  #readerview-btn[aria-pressed="true"],
  #tts-btn.speaking { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  /* Reader View ON: the book button becomes the settings affordance — a small
     amber dot hints that a second tap opens the palette (not exits the mode). */
  #readerview-btn.rv-on { position: relative; }
  #readerview-btn.rv-on::after {
    content: ''; position: absolute; top: 5px; right: 5px;
    width: 5px; height: 5px; border-radius: 50%;
    background: var(--amber); box-shadow: 0 0 0 2px var(--surface);
  }
  /* One-shot coachmark floated beside the book button on first Reader View. */
  .reader-coach {
    position: fixed; z-index: 9999; max-width: 220px;
    background: var(--amber); color: var(--on-amber); font-size: 12.5px; font-weight: 600;
    line-height: 1.3; padding: 8px 11px; border-radius: 10px;
    box-shadow: var(--shadow-popup); cursor: pointer;
    opacity: 0; transform: translateY(-4px); transition: opacity .2s, transform .2s;
  }
  .reader-coach.visible { opacity: 1; transform: none; }
  .reader-coach::before {
    content: ''; position: absolute; top: -5px; right: 14px;
    border-left: 6px solid transparent; border-right: 6px solid transparent;
    border-bottom: 6px solid var(--amber);
  }

  /* One-shot tip floated bottom-centre on first article open when a wiktionary
     is installed — teaches the select-a-word Define gesture. Dismissible. */

  @media (prefers-reduced-motion: reduce) {
  }

  /* ── History trail dropdown ── */
  #history-trail {
    position: fixed; z-index: 9999; min-width: 220px; max-width: 360px;
    max-height: 400px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-popup);
    padding: 4px 0; display: none;
  }
  #history-trail.visible { display: block; }
  .history-item {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 14px; cursor: pointer; font-size: 13px;
    color: var(--text); transition: background 0.1s;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .history-item:hover, .history-item.hover,
  .lang-dropdown-item:hover,
  .ctx-item:hover { background: var(--amber-glow); color: var(--amber); }
  #history-trail, .back-btn { user-select: none; -webkit-user-select: none; }
  .history-item .hi-source { flex-shrink: 0; font-size: 10px; font-weight: 600; color: var(--text2); background: var(--surface2); border-radius: 3px; padding: 1px 5px; text-transform: uppercase; }
  .history-item .hi-title { overflow: hidden; text-overflow: ellipsis; }

  .topbar-breadcrumb { display: flex; align-items: center; gap: 8px; flex-shrink: 0; min-width: 0; }
  .bc-sep { color: var(--text2); font-size: 18px; opacity: 0.5; font-weight: 300; }
  .bc-icon { display: none; flex-shrink: 0; line-height: 0; cursor: pointer; }
  .bc-icon img { width: 18px; height: 18px; border-radius: 4px; object-fit: cover; background: #fff; padding: 1px; }
  .bc-icon .bc-letter {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; border-radius: 5px;
    background: var(--surface2); color: var(--accent);
    font-size: 12px; font-weight: 700;
  }

  .topbar-search { flex: 1; position: relative; margin-left: 4px; }
  .topbar-search input {
    width: 100%; padding-block: 9px; padding-inline: 36px 14px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    color: var(--text); font-size: 16px; outline: none;
    transition: border-color 0.2s, box-shadow 0.2s;
  }
  .topbar-search input:focus { border-color: var(--amber-border); box-shadow: 0 0 0 3px var(--amber-glow), 0 0 20px var(--amber-glow); }
  .topbar-search input::placeholder { color: var(--text2); }
  /* What is typed reads in its own direction: "-ted" typed under a Hebrew
     interface showed as "ted-", an operator that is not the one typed. */
  .topbar-search input { unicode-bidi: plaintext; }
  /* The box is written in logical sides: in a right-to-left UI the glass
     sits at the right, where the text starts, and ×, ? and / at the left. */
  .topbar-search .search-icon { position: absolute; inset-inline-start: 11px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; color: var(--text2); pointer-events: none; }
  .topbar-search .kbd {
    position: absolute; inset-inline-end: 10px; top: 50%; transform: translateY(-50%);
    font-size: 11px; color: var(--text2); background: var(--surface2);
    border: 1px solid var(--border); border-radius: 4px; padding: 1px 6px;
    font-family: inherit; pointer-events: none; opacity: 0.7; transition: opacity 0.15s;
  }
  .topbar-search input:focus ~ .kbd { opacity: 0; }
  /* Clear (×): shown only when the field has a value (placeholder hidden). The
     kbd hint gives way to it so they never overlap. Pure CSS — the value drives
     :placeholder-shown, including programmatic changes. */
  .topbar-search .search-clear {
    position: absolute; inset-inline-end: 8px; top: 50%; transform: translateY(-50%);
    display: none; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0; border: none; border-radius: 5px;
    background: transparent; color: var(--text2); cursor: pointer;
    transition: color 0.12s, background 0.12s;
  }
  .topbar-search .search-clear:hover { color: var(--amber); background: var(--amber-glow); }
  .topbar-search input:not(:placeholder-shown) ~ .search-clear { display: flex; }
  .topbar-search input:not(:placeholder-shown) ~ .kbd { opacity: 0; }

  /* ── Suggest dropdown ── */
  .suggest-dropdown {
    position: absolute; top: 100%; left: 0; right: 0;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 0 0 var(--radius) var(--radius);
    display: none; z-index: 300; max-height: 300px; overflow-y: auto;
    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
  }
  .suggest-item { padding: 8px 14px; cursor: pointer; font-size: 13px; transition: background 0.1s; }
  .suggest-item:hover, .suggest-item.active { background: var(--amber-glow); }
  .suggest-item .sg-title { color: var(--text); }
  .suggest-item .sg-source { font-size: 11px; color: var(--text2); margin-top: 1px; }

  .random-btn, .manage-btn, .newtab-btn, .library-btn, .lang-selector-btn, .reader-btn,
  .create-btn {
    display: flex; align-items: center; justify-content: center;
    color: var(--text2); flex-shrink: 0; width: 36px; height: 28px;
    border-radius: var(--radius); transition: color 0.2s, background 0.2s, border-color 0.2s;
    border: 1px solid transparent; background: none; cursor: pointer;
  }
  .random-btn, .manage-btn, .newtab-btn, .library-btn { text-decoration: none; font-size: 16px; }
  .lang-selector-btn { line-height: 1; }
  .newtab-btn { display: none; }
  /* Reader-only controls (font size + read-aloud) — appear when an article is open */
  .reader-btn { line-height: 1; }
  /* The "A" glyph scales with the active font level as a live preview of the setting */
  .reader-btn .font-glyph { font-weight: 700; font-size: 15px; transition: font-size 0.15s; }
  /* Read-aloud button swaps between speaker and stop icons via the .speaking class */
  #tts-btn .tts-icon-stop { display: none; }
  #tts-btn.speaking .tts-icon-speak { display: none; }
  #tts-btn.speaking .tts-icon-stop { display: inline; }
  .manage-btn { position: relative; }
  .random-btn .dice { display: inline-block; transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .random-btn:hover .dice { transform: rotate(20deg) scale(1.15); }
  .random-btn.rolling .dice { animation: diceRoll 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); }
  @keyframes diceRoll {
    0% { transform: rotate(0deg) scale(1); }
    30% { transform: rotate(180deg) scale(1.3); }
    60% { transform: rotate(360deg) scale(0.9); }
    100% { transform: rotate(360deg) scale(1); }
  }

  /* ── Layout ── */
  .container { max-width: 820px; margin: 0 auto; padding: 28px 16px; }

  /* ── Stats bar ── */
  .stats-bar { text-align: center; padding: 12px 0; font-size: 14px; color: var(--text2); }
  .stats-bar .num { color: var(--amber); font-weight: 600; }

  /* ── Pills (categories + search source filters) ── */
  .pills { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; justify-content: center; margin-bottom: 20px; }
  .pills-row { display: flex; align-items: center; gap: 6px; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding-bottom: 2px; width: 100%; }
  .pills-row::-webkit-scrollbar { display: none; }
  /* ── Catalog lang row (single-row scroll with search icon) ── */
  .catalog-lang-row { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; overflow: hidden; }
  .catalog-lang-scroll { display: flex; gap: 6px; overflow-x: auto; overflow-y: hidden; flex: 1; min-width: 0; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding: 2px 0; }
  .catalog-lang-scroll::-webkit-scrollbar { display: none; }
  .catalog-lang-search-btn {
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 30px; height: 30px; border-radius: 20px; border: 1px solid var(--border);
    background: var(--surface); color: var(--text2); cursor: pointer;
    transition: color 0.2s, border-color 0.2s, background 0.2s, width 0.25s;
  }
  .catalog-lang-search-btn.expanded { width: 140px; border-color: var(--amber-border); background: var(--surface); justify-content: flex-start; gap: 6px; padding: 0 10px; }
  .catalog-lang-search-btn.expanded input { border: none; background: transparent; color: var(--text); font-size: 11px; outline: none; width: 100%; font-weight: 500; }
  /* One base geometry for every filter/category/source/language/recency pill.
     Variants (.cat-pill, .dl-filter-pill, .lang-pill…)
     ride on top of .pill and may override colour/accent ONLY — never height,
     padding, or font-size, so rows never render at mismatched heights. */
  .pill, .lang-pill {
    display: inline-flex; align-items: center; gap: 5px; box-sizing: border-box;
    min-height: 28px; padding: 6px 14px; font-family: inherit; font-size: 12px; line-height: 1;
    font-weight: 500; white-space: nowrap;
    background: var(--surface); border: 1px solid var(--border); border-radius: 20px;
    color: var(--text2); cursor: pointer; transition: color 0.2s, background 0.2s, border-color 0.2s;
  }
  .pill:hover, .lang-pill:hover,
  .ci-variant-btn:hover, .flavor-pill:hover { border-color: var(--amber-border); color: var(--text); background: var(--amber-glow); }
  .pill.active, .lang-pill.active { background: linear-gradient(135deg, var(--amber), var(--amber2)); color: var(--on-amber); border-color: transparent; font-weight: 600; }
  /* Count chip: a subtle padded pill so the number reads as a distinct count and
     never crowds the label or the pill's edge. The parent gap (5px) separates it
     from the label; the padding gives the digits breathing room inside the chip. */
  .pill .pill-count { opacity: 0.7; font-size: 10px; font-weight: 600; margin-left: 1px; padding: 1px 6px; background: rgba(255, 255, 255, 0.07); border-radius: 8px; }
  /* Dark chip on the amber active pill — amber-on-amber is unreadable */
  .pill.active .pill-count { opacity: 1; background: rgba(0, 0, 0, 0.22); color: var(--on-amber); padding: 1px 6px; border-radius: 8px; }
  .pill.dimmed, .lang-pill.dimmed { opacity: 0.35; pointer-events: none; }
  /* A language the filter leaves out: faded, and still a tap away. */
  .pill.other-lang { opacity: 0.5; }
  .pill.other-lang:hover, .pill.other-lang:focus-visible { opacity: 1; }
  .pills-divider { width: 60px; height: 1px; background: var(--border); margin: 0 auto 4px; opacity: 0.5; }
  /* Collection pills carry the layers glyph so they read as groupings, not
     categories, in the filter row. */
  .col-pill-glyph { flex-shrink: 0; opacity: 0.8; }
  /* ── Language pills (above source pills in search results) ── */
  .lang-pills { display: flex; align-items: center; gap: 6px; overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: none; padding-bottom: 2px; width: 100%; }
  .lang-pills::-webkit-scrollbar { display: none; }

  /* ── Filter pills inside the search dropdown ── the recency + language pills
     ride at the top of the suggestions panel, above RECENT, separated by a rule.
     Each row keeps its own horizontal scroll (.pills-row / .lang-pills). */
  .suggest-filters {
    display: flex; flex-direction: column; gap: 6px;
    padding: 8px 14px; border-bottom: 1px solid var(--border);
  }

  /* ── Language banner (view-in-lang / download) ── */
  .lang-banner {
    display: flex; align-items: center; gap: 6px; padding: 5px 12px;
    background: var(--amber-glow); border-bottom: 1px solid var(--amber-border);
    font-size: 12px; color: var(--text); flex-wrap: wrap;
    flex-shrink: 0;
  }
  .lang-banner-btn {
    padding: 3px 10px; border-radius: 4px; border: 1px solid var(--amber-border);
    background: var(--surface2); color: var(--text); font-size: 12px; font-weight: 500;
    cursor: pointer; white-space: nowrap; transition: all 0.15s;
  }
  .lang-banner-btn:hover { background: var(--amber); color: var(--on-amber); border-color: transparent; }
  .lang-banner-dismiss { margin-inline-start: auto; background: none; border: none; color: var(--text2); cursor: pointer; font-size: 14px; padding: 2px 6px; }
  .lang-banner-dismiss:hover { color: var(--text); }

  /* ── Language switch panel (ephemeral, bottom of reader) ── */
  /* Mobile overflow menu */
  .topbar-more { display: none; background: none; border: none; color: var(--text2); font-size: 18px; cursor: pointer; padding: 4px 8px; border-radius: 6px; letter-spacing: 2px; }
  .topbar-more:hover { color: var(--text); background: var(--surface2); }
  .topbar-menu {
    display: none; position: absolute; inset-inline-end: 8px; top: 48px; z-index: 200;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: 0 4px 16px rgba(0,0,0,0.3); min-width: 180px; padding: 4px 0;
    /* With Reader View's settings inline the menu can get tall; cap it to the
       viewport and scroll cleanly rather than overflow off-screen. */
    max-height: calc(100vh - 56px); overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  .topbar-menu.visible { display: block; }
  /* Inline Reader View settings inside the ⋯ menu — the rv-* rows own their own
     compact layout; this wrapper just insets them from the menu edge and widens
     the menu enough for the theme swatches / font pills to breathe. */
  .topbar-menu .tbm-reader-settings { padding: 2px 6px 4px; min-width: 232px; }
  .topbar-menu-item {
    display: flex; align-items: center; gap: 10px; padding: 10px 14px;
    color: var(--text); font-size: 13px; cursor: pointer; border: none;
    background: none; width: 100%; text-align: start;
  }
  .topbar-menu-item:hover { background: var(--surface2); }
  /* Every leading glyph occupies the same 16px box so the labels line up in a
     single column — SVG icons are 16px, but the 🎲 emoji has a wider advance
     and taller line box, which pushed the Random label out of alignment. */
  .topbar-menu-item svg, .topbar-menu-item .dice { flex-shrink: 0; width: 16px; }
  .topbar-menu-item .dice { line-height: 1; text-align: center; display: inline-block; }
  /* Thin rule separating the reader-control group from the app-navigation group. */
  .topbar-menu-divider { height: 1px; margin: 4px 0; background: var(--border); }
  .topbar-menu-label { padding: 6px 12px 2px; font-size: 11px; color: var(--text2); text-transform: uppercase; letter-spacing: 0.04em; }

  /* ── Reader header layout ──
     While an article is open the inline Reader View / font / read-aloud / pop-out
     controls fold into the ⋯ menu (on every viewport — mobile already did this
     via the max-width rule; this brings desktop to parity). Flex `order` pins the
     close X (the .manage-btn slot) to the far right, with ⋯ immediately before it,
     so the header reads: title … ✕. */
  body.reading #font-btn, body.reading #tts-btn,
  body.reading #readerview-btn, body.reading #newtab-btn { display: none !important; }
  body.reading .topbar-more { display: flex; }
  /* The gear is the last thing on the bar, on every page: the + (the one-item
     ⋯) sits before it (Eric: "Gear should be top-right on main page not the +"). */
  .topbar-more { order: 990; }
  .manage-btn { order: 1000; }
  @keyframes gear-turn { to { transform: rotate(360deg); } }
  .manage-btn.busy svg { animation: gear-turn 1s linear infinite; }

  /* ── Users management (multi-user v1) ── */
  .ms-btn { padding: 6px 12px; font-size: 12px; font-family: inherit; color: var(--text2); background: none; border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; transition: all 0.15s; }
  .ms-btn:hover { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .ms-btn-primary { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .ms-btn-primary:hover { color: #fff; background: var(--amber); border-color: var(--amber); }
  .ms-btn-danger:hover { color: #ef4444; border-color: #ef444455; background: #ef444411; }
  /* Users list — rows carry no controls at rest; every mutation lives behind a
     single ⋯ menu (.ms-user-menu-btn, styled by .ci-gear). 4px grid throughout. */
  .ms-users-intro { color: var(--text2); font-size: 13px; margin-bottom: 16px; }
  .ms-users-list { margin-bottom: 4px; }
  .ms-user-row { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--border); }
  .ms-user-main { flex: 1; min-width: 0; }
  .ms-user-meta { color: var(--text2); font-size: 12px; }
  .ms-user-menu-btn { flex-shrink: 0; }
  .ms-user-addbar { margin-top: 16px; }
  .ms-user-add { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; max-width: 420px; }
  .ms-user-add[hidden] { display: none; }  /* [hidden] must beat the display:flex above */
  .ms-user-add input:not([role="switch"]), .create-field { padding: 8px 10px; font-size: 13px; font-family: inherit; color: var(--text); background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius); box-sizing: border-box; width: 100%; }
  /* A checkbox (the allowlist's) is not a text field: the width:100% above
     stretched it across the row. Keep it its own size. */
  .ms-user-add input[type="checkbox"]:not([role="switch"]) { width: auto; padding: 0; accent-color: var(--amber); }
  .ms-user-add input:not([role="switch"]):focus, .create-field:focus { outline: none; border-color: var(--amber-border); box-shadow: 0 0 0 3px var(--amber-glow); }
  .ms-form-label { font-size: 12px; color: var(--text2); }
  .ms-user-account { margin-top: 24px; border-top: 1px solid var(--border); padding-top: 16px; }
  /* "Your Account" now leads the pane: no top border, and a divider below it
     separates it from the other-users section. */
  .ms-user-account-self { margin-top: 0; border-top: none; padding-top: 0; padding-bottom: 16px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
  .ms-user-account-self .ms-user-row { border-bottom: none; }
  /* Your-account actions: visible Change password / Log out buttons, not a ⋯. */
  .ms-user-self-actions { flex-shrink: 0; display: flex; gap: 8px; }
  /* Role badges in the users list. Muted by default; admin gets amber. */
  .ms-role-badge { display: inline-block; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; padding: 1px 7px; border-radius: 10px; color: var(--text2); background: var(--surface2); border: 1px solid var(--border); vertical-align: middle; }
  .ms-role-badge.ms-role-admin { color: var(--on-amber); background: linear-gradient(135deg, var(--amber), var(--amber2)); border-color: transparent; }
  /* Role picker reuses .pill; keep it wrapping rather than scrolling in the narrow users pane. */
  #new-user-role, #edit-user-role { flex-wrap: wrap; overflow: visible; margin-bottom: 4px; }
  /* Limited scope on a user row → clickable shortcut into the allowlist editor. */
  .ms-user-scope-link { color: var(--amber); cursor: pointer; text-decoration: none; border-bottom: 1px dotted var(--amber-border); }
  .ms-user-scope-link:hover { border-bottom-color: var(--amber); }

  /* ── Allowlist picker (per-user Limited + public-access Limited) ──
     Searchable, legible checklist: real titles, language badge, article count,
     select-all/none, live count summary. Comfortable rows, mobile-friendly. */
  .ms-allowlist-picker { border: 1px solid var(--border); border-radius: 10px; background: var(--surface2); overflow: hidden; max-width: 480px; }
  .ms-allow-tools { display: flex; align-items: center; gap: 6px; padding: 8px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
  .ms-allow-search { flex: 1; min-width: 120px; padding: 7px 10px; font-size: 13px; font-family: inherit; color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-sizing: border-box; }
  .ms-allow-search:focus { outline: none; border-color: var(--amber-border); box-shadow: 0 0 0 3px var(--amber-glow); }
  .ms-allow-link { padding: 5px 9px; font-size: 12px; font-family: inherit; color: var(--text2); background: none; border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; transition: all 0.15s; white-space: nowrap; }
  .ms-allow-link:hover { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .ms-allow-summary { padding: 6px 10px; font-size: 12px; color: var(--text2); border-bottom: 1px solid var(--border); }
  .ms-allow-list { max-height: 260px; overflow-y: auto; padding: 4px; }
  .ms-allow-row { display: flex; align-items: center; gap: 10px; padding: 8px 8px; font-size: 13px; cursor: pointer; border-radius: 6px; }
  .ms-allow-row:hover { background: var(--amber-glow); }
  .ms-allow-row input { accent-color: var(--amber); flex-shrink: 0; width: 15px; height: 15px; }
  .ms-allow-name { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; }
  .ms-allow-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ms-allow-lang { flex-shrink: 0; font-size: 9.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; padding: 1px 5px; border-radius: 8px; color: var(--text2); background: var(--surface); border: 1px solid var(--border); }
  .ms-allow-count { flex-shrink: 0; font-size: 11px; color: var(--text2); font-variant-numeric: tabular-nums; }
  .ms-allow-empty { color: var(--text2); font-size: 12px; padding: 4px 0; }

  /* ── Public access card ── governs the anonymous view; sits atop the pane. */
  .ms-pa-card { margin-bottom: 24px; padding-bottom: 20px; border-bottom: 1px solid var(--border); }
  .ms-pa-sub { color: var(--text2); font-size: 13px; margin: 2px 0 12px; }
  .ms-pa-env { font-size: 12px; color: var(--text2); background: var(--amber-glow); border: 1px solid var(--amber-border); border-radius: var(--radius); padding: 6px 10px; margin-bottom: 12px; }
  .ms-pa-env code { font-size: 11px; color: var(--amber); }
  .ms-pa-choices { display: flex; flex-direction: column; gap: 8px; max-width: 480px; }
  /* The choice tile is the app's one "pick one of these" surface. */
  .ms-pa-choice { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; cursor: pointer; transition: all 0.15s; }
  .ms-pa-choice:hover { border-color: var(--amber-border); background: var(--amber-glow); }
  .ms-pa-choice.active { border-color: var(--amber-border); background: var(--amber-glow); }
  .ms-pa-choice.disabled { opacity: 0.55; cursor: not-allowed; }
  .ms-pa-choice input { accent-color: var(--amber); margin-top: 2px; flex-shrink: 0; width: 15px; height: 15px; }
  .ms-pa-choice-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .ms-pa-choice-title { font-size: 13px; font-weight: 600; color: var(--text); }
  .ms-pa-choice-desc { font-size: 12px; color: var(--text2); }
  .ms-pa-allowlist { margin-top: 12px; max-width: 480px; }
  .ms-pa-allowlist[hidden] { display: none; }
  .ms-pa-save { margin-top: 12px; }

  /* ── Create a ZIM ──
     A full-page surface over the library, same geometry as the Almanac overlay
     but built from the manage-settings vocabulary above: .create-field inputs
     (which share the .ms-user-add input recipe), .ms-btn / .ms-btn-primary
     actions and .ms-form-label headings.

     Round 3's shape: a row of mode CHIPS, then ONE panel carrying whichever
     mode is lit. Round 2 gave every mode its own expanding form, each with its
     own copy of Title and Advanced, and the page read as six near-identical
     sections — worse, an open form's Create button sat one row above the tile
     that opened the next mode, so reaching for Create and hitting "Video"
     instead was a matter of a few pixels. Hence .create-go: the primary action
     is full-width, rule-separated, at the bottom of the panel, with nothing
     beside it to land on by accident. */
  .create-view { position: fixed; top: var(--under-topbar); left: 0; right: 0; bottom: 0; display: none; flex-direction: column; z-index: 100; background: var(--bg); }
  .create-view.open { display: flex; animation: fadeIn 0.15s ease; }
  .create-content { flex: 1; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; padding: 24px 16px 40px; overscroll-behavior: contain; }
  .create-inner { max-width: 560px; margin: 0 auto; display: flex; flex-direction: column; gap: 20px; }
  .create-head { display: flex; flex-direction: column; gap: 4px; }
  .create-beta { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; margin-top: -4px; font-size: 13px; color: var(--text2); }
  .create-beta-tag { font-family: inherit; font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--amber); border: 1px solid var(--amber-border); background: var(--amber-glow); padding: 2px 7px; border-radius: 999px; }
  .create-beta-note a { color: var(--amber2); }
  .create-title { font-size: 20px; font-weight: 600; color: var(--text); }
  /* Mode chips. A chip is a name and a glyph; the sentence explaining the mode
     belongs to the panel of the one you picked, not to five you did not. */
  /* A display rule beats the `hidden` attribute. Every flex container on this
     page that JS hides needs saying twice, or `el.hidden = true` is a no-op:
     the picker would stay on screen underneath the job it just started, and an
     old server with no events would show an inert phase strip and an empty
     tree box — the two things the degraded path exists to avoid. */
  .create-picker[hidden], .create-phases[hidden], .create-tree-wrap[hidden] { display: none; }
  /* An option that belongs to one engine, hidden when another is chosen (see
     _createSyncEngine). [hidden] must beat the display:flex below, exactly as
     it must for .ms-user-add. */
  .create-flag[hidden] { display: none; }
  .create-picker { display: flex; flex-direction: column; gap: 12px; }
  .create-modes { display: flex; flex-wrap: wrap; gap: 8px; }
  .create-address { display: flex; flex-direction: column; gap: 6px; }
  .create-address[hidden] { display: none; }
  .create-address textarea.create-field { resize: vertical; min-height: 40px; }
  /* Folder mode's picker: the create root as a tree, one level per request.
     Rows are 40px for a finger; the indent is logical (inline-start), so RTL
     indents from the right. Not .create-tree: that is the run view's. */
  .create-ftree { max-height: 360px; overflow-y: auto; overscroll-behavior: contain; padding: 4px 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface2); }
  .create-ftree[hidden] { display: none; }
  .create-ftree-row { display: flex; align-items: center; gap: 8px; min-height: 40px; padding-inline: calc(6px + var(--depth, 0) * 18px) 12px; font-size: 14px; color: var(--text); cursor: pointer; }
  .create-ftree-row:hover { background: var(--amber-glow); }
  .create-ftree-row:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
  .create-ftree-row.is-off { color: var(--text3); cursor: default; }
  .create-ftree-row.is-off:hover { background: none; }
  .create-ftree-row.is-note { color: var(--text3); font-size: 13px; cursor: default; min-height: 34px; }
  .create-ftree-row.is-note:hover { background: none; }
  .create-ftree-twist { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 24px; width: 24px; height: 24px; padding: 0; color: var(--text2); background: none; border: 0; border-radius: 6px; cursor: pointer; }
  .create-ftree-twist svg { transition: transform 0.15s; }
  .create-ftree-row[aria-expanded="true"] > .create-ftree-twist svg { transform: rotate(90deg); }
  [dir="rtl"] .create-ftree-row[aria-expanded="false"] > .create-ftree-twist svg { transform: scaleX(-1); }
  .create-ftree-check { flex: 0 0 18px; width: 18px; height: 18px; margin: 0; accent-color: var(--amber); }
  .create-ftree-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .create-ftree-what { flex: 0 1 auto; max-width: 48%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--text3); }
  /* A file left out says why under its name, in full: the line wraps, it is
     never cut, and it lines up with the names (twist 24 + gap 8 + check 18 + gap 8). */
  .create-ftree-row.is-off { flex-wrap: wrap; align-content: center; row-gap: 2px; padding-block: 6px; }
  .create-ftree-why { flex: 1 0 100%; padding-inline-start: 58px; font-size: 12px; line-height: 1.35; color: var(--text3); white-space: normal; }
  .create-where { font-size: 12px; color: var(--text2); }
  .create-where[hidden] { display: none; }
  .create-where summary { cursor: pointer; width: max-content; }
  .create-where .create-caption { margin-top: 4px; overflow-wrap: anywhere; }
  .create-ftree-more { padding: 6px 0; font: inherit; font-size: 13px; color: var(--amber); background: none; border: 0; cursor: pointer; }
  .create-ftree-summary { padding: 8px 12px 4px; }
  .create-left-out { margin: 6px 0 0; padding-inline-start: 18px; font-size: 13px; color: var(--text2); }
  @media (prefers-reduced-motion: reduce) { .create-ftree-twist svg { transition: none; } }
  .create-chip { display: flex; align-items: center; gap: 7px; padding: 7px 12px; font-family: inherit; font-size: 13px; color: var(--text2); background: none; border: 1px solid var(--border); border-radius: 999px; cursor: pointer; transition: all 0.15s; }
  .create-chip:hover { color: var(--text); border-color: var(--amber-border); background: var(--amber-glow); }
  .create-chip.active { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .create-chip.disabled { opacity: 0.45; cursor: not-allowed; }
  .create-chip.disabled:hover { color: var(--text2); border-color: var(--border); background: none; }
  .create-chip-glyph { display: flex; flex-shrink: 0; }
  .create-chip-name { white-space: nowrap; }
  .create-panel { display: flex; flex-direction: column; gap: 12px; padding: 16px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
  .create-panel-desc { font-size: 12.5px; color: var(--text2); }
  .create-panel-blocked { font-size: 13px; color: var(--text2); padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface2); }
  .create-go { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; padding-top: 16px; border-top: 1px solid var(--border); }
  .create-go-btn { width: 100%; padding: 10px 12px; font-size: 13px; font-weight: 600; }
  .create-flags { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
  .create-flag { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text2); cursor: pointer; }
  .create-flag input[type="checkbox"] { accent-color: var(--amber); width: 15px; height: 15px; flex-shrink: 0; }
  /* ── The capture engine picker ──
     Two answers side by side, each with the sentence that tells you which one
     you want, taking the whole width of the flags row: this is the one control
     on the panel that changes what the ZIM CONTAINS, and burying it in a
     dropdown beside "Max pages" would have said the opposite. The lit state is
     the page's own amber, the same one a chosen chip wears. An option this
     server cannot run is dimmed rather than removed — see _createEngineHtml. */
  .create-seg-field { flex: 1 0 100%; display: flex; flex-direction: column; gap: 6px; }
  .create-seg-label { font-size: 13px; color: var(--text2); }
  .create-engine-fold > summary { cursor: pointer; list-style: none; display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
  .create-engine-fold > summary::-webkit-details-marker { display: none; }
  .create-engine-fold > summary b { font-weight: 600; color: var(--text); }
  .create-engine-change { color: var(--amber); font-size: 12px; }
  .create-engine-fold[open] .create-engine-change { display: none; }
  .create-seg { display: flex; flex-wrap: wrap; gap: 8px; }
  .create-seg-opt { flex: 1 1 220px; display: grid; grid-template-columns: auto 1fr;
    align-items: start; gap: 2px 8px; padding: 10px 12px; border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--surface2); cursor: pointer; }
  .create-seg-opt:hover { border-color: var(--amber-border); }
  .create-seg-opt input[type="radio"] { accent-color: var(--amber); width: 15px; height: 15px;
    margin: 2px 0 0; grid-row: 1 / span 2; }
  .create-seg-name { font-size: 13px; color: var(--text); }
  .create-seg-desc { grid-column: 2; font-size: 12px; color: var(--text2); }
  .create-seg-opt:has(input:checked) { border-color: var(--amber-border); background: var(--amber-glow); }
  .create-seg-opt:has(input:checked) .create-seg-name { color: var(--amber); }
  .create-seg-opt.is-off { opacity: 0.45; cursor: not-allowed; }
  .create-seg-opt.is-off:hover { border-color: var(--border); }
  .create-num { width: 96px; flex: 0 0 auto; }
  .create-short { width: 140px; flex: 0 0 auto; }
  /* 16px minimum on touch widths — iOS Safari auto-zooms (and stays zoomed)
     on focus of any input smaller than 16px; the create form's address box
     was the reported offender. Same rule .pw-box documents. */
  @media (max-width: 720px) { .create-field { font-size: 16px; } }
  .create-pick { width: auto; flex: 0 0 auto; padding-right: 6px; }
  .create-field:disabled { opacity: 0.5; cursor: not-allowed; }
  .create-caption { font-size: 12px; color: var(--text2); }
  /* "Advanced" is a sentence you can open, not a control: the default marker is
     replaced by a caret and there is deliberately no box, border or button
     around it — the simple form has to stay the thing you see. The run pane's
     log toggle is the same idea and shares the recipe: the log is the truth,
     but the picture is what you watch, so the truth waits behind one quiet
     word rather than filling the pane by default. */
  .create-adv > summary, .create-logbox > summary { display: flex; align-items: center; gap: 6px; width: fit-content; list-style: none; cursor: pointer; font-size: 12px; color: var(--text2); }
  .create-adv > summary::-webkit-details-marker, .create-logbox > summary::-webkit-details-marker { display: none; }
  .create-adv > summary::before, .create-logbox > summary::before { content: "\203A"; display: inline-block; transition: transform 0.15s ease; }
  .create-adv[open] > summary::before, .create-logbox[open] > summary::before { transform: rotate(90deg); }
  /* The caret is a mirrored character, so RTL already flips it to point at the
     text — which means the open state has to turn the other way to end up
     pointing DOWN rather than back up at the topbar. */
  [dir="rtl"] .create-adv[open] > summary::before,
  [dir="rtl"] .create-logbox[open] > summary::before { transform: rotate(-90deg); }
  .create-adv > summary:hover, .create-logbox > summary:hover { color: var(--amber); }
  .create-adv[open] > summary, .create-logbox[open] > summary { margin-bottom: 10px; }
  .create-adv .create-caption { margin-top: 8px; }
  .create-credit a { color: var(--text2); text-decoration: underline; text-underline-offset: 2px; }
  .create-credit a:hover { color: var(--amber); }
  .create-actions { display: flex; align-items: center; gap: 8px; }
  /* ── Pre-flight preview ──
     What the server found at the source, shown before anything runs. Quieter
     than the form it sits inside: it is an answer, not another control. The
     not-ok variant borrows the error token for its edge so a refusal reads as
     a refusal without shouting in red over the whole box. */
  .create-source-row { display: flex; gap: 8px; align-items: center; }
  .create-source-row .create-field { flex: 1 1 auto; min-width: 0; }
  .create-preview-box { margin-top: 2px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface2); display: flex; flex-direction: column; gap: 4px; }
  .create-preview-box.not-ok { border-color: var(--error); }
  .create-pv-row { display: flex; gap: 10px; font-size: 12px; align-items: baseline; }
  .create-pv-k { color: var(--text2); flex: 0 0 auto; min-width: 92px; }
  .create-pv-v { color: var(--text); min-width: 0; overflow-wrap: anywhere; }
  .create-pv-warn { font-size: 12px; color: var(--error); }
  .create-pv-detail { color: var(--text2); margin-top: 4px; }
  /* ── Folder picker ──
     Directories only, never file names — see the design note on _create_browse
     in manage.py. It is a list of buttons rather than a tree: one level at a
     time is the whole interaction, and a tree would imply a recursion the
     endpoint deliberately does not do. */
  .create-browse-box { margin-top: 8px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface2); display: flex; flex-direction: column; gap: 8px; }
  .create-browse-head { font-family: monospace; font-size: 11.5px; color: var(--text2); overflow-wrap: anywhere; }
  .create-browse-list { display: flex; flex-direction: column; gap: 2px; max-height: 220px; overflow-y: auto; overscroll-behavior: contain; }
  .create-dir { text-align: left; font-family: inherit; font-size: 13px; color: var(--text); background: none; border: 1px solid transparent; border-radius: 6px; padding: 5px 8px; cursor: pointer; }
  .create-dir:hover { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .create-dir.up { color: var(--text2); }
  /* ── Watching the work ──
     Round 2 showed a spinner and a log, which says "something is happening" and
     nothing more; an hour-long crawl looked identical to a hung one, and the
     packaging phase — where the engine writes tens of thousands of entries with
     nothing to say about it — looked exactly like a crash. What the server now
     emits is structure: phases, page nodes with their assets, and counters. So
     this is a phase strip, a tree that grows, and numbers that move. */
  .create-run { display: flex; flex-direction: column; gap: 16px; }
  .create-phases { display: flex; align-items: flex-start; }
  .create-step { flex: 1 1 0; position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text3); text-align: center; }
  /* The rail between two steps belongs to the one on the right, which is what
     lets the step on its LEFT light it with a sibling selector.
     It runs BETWEEN the dots, not through them. Spanning centre-to-centre put
     the line under each dot and out the other side, and the dot's fill is not
     the page's, so what showed was a line ending halfway into a circle (Eric:
     "the line is half way through the first step, looks bad"). The inset is
     the dot's radius plus a little air. */
  .create-step + .create-step::before {
    content: ""; position: absolute; top: 5px; height: 1px; background: var(--border);
    left: calc(-50% + 11px); right: calc(50% + 11px);
  }
  .create-step[data-state="done"] + .create-step::before { background: var(--amber-border); }
  .create-step-dot { position: relative; z-index: 1; width: 11px; height: 11px; border-radius: 50%; border: 1px solid var(--border-strong); background: var(--surface2); }
  .create-step[data-state="done"] { color: var(--text2); }
  .create-step[data-state="done"] .create-step-dot { border-color: var(--amber-border); background: var(--amber-border); }
  .create-step[data-state="failed"] { color: var(--error); }
  .create-step[data-state="failed"] .create-step-dot { border-color: var(--error); background: var(--error); }
  .create-step[data-state="active"] { color: var(--amber); }
  .create-step[data-state="active"] .create-step-dot { border-color: var(--amber); background: var(--amber); animation: createPulse 1.6s ease-in-out infinite; }
  .create-phase-detail { min-height: 16px; font-size: 12px; color: var(--text2); text-align: center; overflow-wrap: anywhere; }
  .create-metrics { display: flex; flex-wrap: wrap; gap: 8px; }
  .create-metric { display: flex; align-items: baseline; gap: 5px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface2); }
  .create-metric-n { font-size: 16px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
  .create-metric-of { font-size: 12px; color: var(--text3); font-variant-numeric: tabular-nums; }
  .create-metric-k { font-size: 11.5px; color: var(--text2); }
  /* The tree. Rows are nested rather than indented by a computed depth, so the
     guide line is the parent's own border and no arithmetic can get it wrong.
     Bounded height, and bounded node count in create.js — a five-thousand-page
     crawl keeps counting and stops drawing. */
  .create-tree-wrap { display: block; }
  .create-tree-wrap > .create-tree-more { margin-top: 6px; }
  .create-tree-summary { cursor: pointer; font-size: 12px; color: var(--text2); padding: 6px 2px; }
  .create-tree-summary[hidden] { display: none; }
  .create-tree-wrap[open] > .create-tree-summary { margin-bottom: 6px; }
  .create-tree { max-height: 320px; overflow-y: auto; overscroll-behavior: contain; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface2); }
  .create-node-kids { margin-left: 7px; padding-left: 10px; border-left: 1px solid var(--border); }
  .create-node-kids:empty { display: none; }
  .create-node-row { display: flex; align-items: center; gap: 7px; min-width: 0; padding: 2px 0; font-size: 12px; }
  .create-node-dot { flex: 0 0 auto; width: 6px; height: 6px; border-radius: 50%; background: var(--text3); }
  .create-node-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text2); }
  .create-node-assets { flex: 0 0 auto; margin-left: auto; font-size: 11px; color: var(--text3); font-variant-numeric: tabular-nums; }
  .create-node-bar { height: 2px; margin: 0 0 3px 13px; border-radius: 1px; background: var(--border); overflow: hidden; }
  .create-node-bar > i { display: block; height: 100%; width: 0; background: var(--amber-border); transition: width 0.3s ease; }
  .create-node[data-state="active"] > .create-node-row .create-node-dot { background: var(--amber); animation: createPulse 1.6s ease-in-out infinite; }
  .create-node[data-state="active"] > .create-node-row .create-node-label { color: var(--text); }
  .create-node[data-state="done"] > .create-node-row .create-node-dot { background: var(--success); }
  .create-node[data-state="failed"] > .create-node-row .create-node-dot { background: var(--error); }
  .create-node[data-state="failed"] > .create-node-row .create-node-label { color: var(--error); }
  .create-node-new > .create-node-row { animation: createNodeIn 0.28s ease both; }
  .create-tree-more { font-size: 11.5px; color: var(--text3); }
  @keyframes createPulse {
    0%, 100% { box-shadow: 0 0 0 0 var(--amber-glow); }
    50% { box-shadow: 0 0 0 5px var(--amber-glow); }
  }
  @keyframes createNodeIn { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }
  /* The running tail. Bounded height so a thousand-page crawl scrolls inside
     its own box instead of walking the page down forever. */
  .create-log { max-height: 260px; overflow-y: auto; overscroll-behavior: contain; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface2); font-family: monospace; font-size: 11.5px; line-height: 1.6; color: var(--text2); }
  .create-log-line { white-space: pre-wrap; word-break: break-word; }
  .create-log-line:last-child { color: var(--text); }
  .create-status { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text); }
  .create-error { font-size: 13px; color: var(--error); white-space: pre-wrap; }
  .create-notice { font-size: 13px; color: var(--text2); padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface2); }
  /* ── The done moment ──
     The reward is the thing you made, arriving: the icon lands, the name
     settles under it, the size rolls up to what it really is, and the way in
     appears. Under a second and a half, once. It celebrates a finished ZIM and
     nothing else — there is no streak here and nothing to come back for. */
  .create-done { display: flex; align-items: center; gap: 12px; padding: 14px; border: 1px solid var(--amber-border); border-radius: var(--radius); background: var(--amber-glow); }
  .create-done-icon { flex: 0 0 auto; display: flex; color: var(--amber); }
  .create-done-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .create-done-name { font-size: 15px; font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
  .create-done-facts { display: flex; flex-wrap: wrap; gap: 2px 10px; font-size: 11.5px; color: var(--text2); }
  .create-done-fact { font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
  .create-done-warn { color: var(--amber2); }
  .create-report { color: var(--amber2); text-decoration: underline; }
  .create-done-open { flex: 0 0 auto; }
  .create-again-row { margin-top: 8px; }
  /* What the finished ZIM is made of: the shared segmented bar (.cache-bar /
     .cache-legend) under the done card, with the on-disk total on its header
     row. Sits on the 4px grid and borrows the card's radius. */
  /* Elapsed time beside the packaging line — quiet, tabular so it does not
     jitter as the seconds tick. */
  .create-elapsed { color: var(--text3); font-variant-numeric: tabular-nums; }
  .create-inside { margin-top: 12px; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
  .create-inside-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
  .create-inside-title { font-size: 11px; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text3); }
  .create-inside-total { font-size: 13px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
  /* ── Recent jobs and the queue ──
     Before round 3 a crawl that finished while the tab was closed left no trace
     on this page at all, and a job the server restart took left a spinner that
     never stopped. Both are rows here now. */
  .create-recent { display: flex; flex-direction: column; gap: 6px; }
  .create-hist-log { margin: -2px 0 6px; padding: 10px 12px; max-height: 260px; overflow: auto; font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text2); background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius); }
  .create-hist { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
  .create-hist-dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: var(--text3); }
  .create-hist[data-state="ok"] .create-hist-dot { background: var(--success); }
  .create-hist[data-state="failed"] .create-hist-dot { background: var(--error); }
  .create-hist[data-state="interrupted"] .create-hist-dot { background: var(--amber); }
  .create-hist-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
  .create-hist-name { font-size: 13px; color: var(--text); overflow-wrap: anywhere; }
  .create-hist-why { font-size: 11.5px; color: var(--text2); overflow-wrap: anywhere; }
  .create-queued { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px dashed var(--border); border-radius: var(--radius); font-size: 12.5px; color: var(--text2); }
  .create-queued.mine { border-color: var(--amber-border); color: var(--text); }
  .create-queued-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .create-queued-pos { flex: 0 0 auto; margin-left: auto; color: var(--text2); font-variant-numeric: tabular-nums; }
  @media (prefers-reduced-motion: reduce) {
    .create-view.open { animation: none; }
    .create-chip { transition: none; }
    .create-adv > summary::before, .create-logbox > summary::before { transition: none; }
    /* Everything the run pane does to draw attention: the arrival animation
       of a tree row resolves to its finished state; the two pulses stop being
       pulses. The result card has no entrance animation at any setting. */
    .create-node-new > .create-node-row,
    .create-step[data-state="active"] .create-step-dot,
    .create-node[data-state="active"] > .create-node-row .create-node-dot { animation: none; }
    .create-node-bar > i { transition: none; }
  }

  /* ── Reader View settings palette ── */
  /* Popover from the book button (desktop) / ⋯ Reader View row (mobile). Anchored
     top-right under the topbar, same visual family as .topbar-menu. Chrome (app UI)
     always uses Zimi's dark palette — only the article inside the iframe changes
     theme, so this popover intentionally does NOT recolor with the reader theme. */
  .reader-palette {
    display: none; position: absolute; inset-inline-end: 8px; top: 48px; z-index: 220;
    background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: var(--shadow-popup); width: 264px; max-width: calc(100vw - 16px); padding: 6px;
  }
  .reader-palette.visible { display: block; animation: fadeIn 0.12s ease; }
  .rv-pal-head {
    display: flex; align-items: center; gap: 8px; padding: 6px 8px 8px;
    color: var(--text); font-size: 13px; font-weight: 600;
  }
  .rv-pal-head svg { color: var(--amber); }
  .rv-row { padding: 7px 8px; }
  .rv-row-label { font-size: 11px; color: var(--text2); margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.4px; }
  /* Theme swatches */
  .rv-swatches { display: flex; gap: 6px; }
  .rv-swatch {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px;
    padding: 8px 4px 6px; border: 1px solid var(--border); border-radius: 9px;
    background: var(--surface2); color: var(--text2); font-size: 11px; cursor: pointer;
    transition: border-color 0.15s, color 0.15s;
  }
  .rv-swatch:hover { border-color: var(--amber-border); color: var(--text); }
  .rv-swatch.active { border-color: var(--amber); color: var(--text); }
  /* One ring on every swatch, readable on a light sheet and a dark one; the
     same as the reading sheets' .zb-dot (_READING_CSS in app.js). */
  .rv-sw-dot { width: 22px; height: 22px; border-radius: 50%; box-sizing: border-box; border: 1.5px solid rgba(128,128,128,0.6); background-clip: padding-box; }
  /* Auto shows what it paints: the Sepia swatch by day, the Dark one by night,
     split corner to corner (READER_AUTO_SWATCH in app.js). */
  .rv-sw-auto .rv-sw-dot { background-image: linear-gradient(135deg, #f4ecd8 calc(50% - 0.5px), #0a0a0b calc(50% + 0.5px)); }
  .rv-sw-dark .rv-sw-dot { background-color: #0a0a0b; }
  .rv-sw-light .rv-sw-dot { background-color: #fbfbf9; }
  .rv-sw-sepia .rv-sw-dot { background-color: #f4ecd8; }
  .rv-swatch.active .rv-sw-dot { box-shadow: 0 0 0 2px var(--surface2), 0 0 0 3px var(--amber); }
  /* Family pills */
  .rv-pills { display: flex; gap: 6px; }
  .rv-pill {
    flex: 1; padding: 8px 6px; border: 1px solid var(--border); border-radius: 8px;
    background: var(--surface2); color: var(--text2); font-size: 14px; cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .rv-pill:hover, .rv-size .tsz-btn:hover { border-color: var(--amber-border); color: var(--text); }
  .rv-pill.active, .rv-size .tsz-btn[aria-checked="true"] { border-color: var(--amber); color: var(--amber); background: var(--amber-glow); }
  /* Text size: five A's that grow, one per step, picked like a family pill.
     Each A's size is inline (TEXT_SIZE_GLYPH_PX). */
  .rv-size { display: flex; gap: 4px; }
  .rv-size .tsz-btn {
    flex: 1; display: flex; align-items: center; justify-content: center; min-width: 0;
    height: 36px; padding: 0; line-height: 1; border: 1px solid var(--border); border-radius: 8px;
    background: var(--surface2); color: var(--text2); font-family: Georgia, serif; cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  /* ── Compact reader controls (⋯ menu, Reader View on) ── */
  /* Label-less: theme swatches shrink to ~24px colour chips (the text label is
     hidden, aria-label carries the name), and font family + size share one row.
     Sized to fit a 390px viewport without horizontal scroll. */
  .rv-compact { display: flex; flex-direction: column; gap: 8px; padding: 2px 2px 4px; }
  .rv-compact .rv-swatches { gap: 8px; }
  .rv-compact .rv-swatch { flex-direction: row; justify-content: center; padding: 5px; gap: 0; }
  .rv-compact .rv-sw-label { display: none; }
  .rv-compact .rv-sw-dot { width: 24px; height: 24px; }
  .rv-compact-fontrow { display: flex; align-items: stretch; gap: 8px; flex-wrap: wrap; }
  .rv-compact-fontrow .rv-pills { flex: 1 1 108px; }
  .rv-compact-fontrow .rv-size { flex: 1 1 170px; }
  /* Full-palette swatch keeps its stacked label. */
  .rv-swatch .rv-sw-label { line-height: 1; }
  /* Reader View row rendered as a switch inside the ⋯ menu. */
  .topbar-menu-item.tbm-rv-toggle .rv-switch { margin-inline-start: auto; }
  /* AUTO toggle row */
  .rv-toggle-row {
    display: flex; align-items: center; gap: 10px; width: 100%; text-align: start;
    padding: 9px 8px; margin-top: 2px; background: none; border: none; cursor: pointer; border-radius: 8px;
  }
  .rv-toggle-row:hover { background: var(--surface2); }
  .rv-toggle-text { flex: 1; display: flex; flex-direction: column; gap: 1px; }
  .rv-toggle-title { font-size: 13px; color: var(--text); }
  .rv-switch {
    flex-shrink: 0; width: 38px; height: 22px; border-radius: 11px; background: var(--surface2);
    border: 1px solid var(--border); position: relative; transition: background 0.15s, border-color 0.15s;
  }
  .rv-switch.on { background: var(--amber); border-color: var(--amber); }
  .rv-knob {
    position: absolute; top: 2px; inset-inline-start: 2px; width: 16px; height: 16px;
    border-radius: 50%; background: #fff; transition: inset-inline-start 0.15s;
  }
  .rv-switch.on .rv-knob { inset-inline-start: 18px; }
  /* Light theme: same fix as the .switch iOS toggle — the OFF track needs a
     warm-grey fill + AA outline, and its white knob (fine on the dark ON amber)
     must darken to --text2 when off so it doesn't vanish on the grey track. */
  :root[data-theme="light"] .rv-switch {
    background: #e0dacd; border-color: var(--border-strong);
  }
  :root[data-theme="light"] .rv-switch:not(.on) .rv-knob { background: var(--text2); }
  .rv-pal-divider { height: 1px; margin: 6px 6px; background: var(--border); }
  .rv-exit-row, .rv-action-row {
    display: flex; align-items: center; gap: 10px; width: 100%; text-align: start;
    padding: 9px 8px; background: none; border: none; cursor: pointer; border-radius: 8px;
    color: var(--text); font-size: 13px;
  }
  .rv-exit-row:hover, .rv-action-row:hover { background: var(--surface2); color: var(--amber); }
  .rv-action-row svg { flex-shrink: 0; color: var(--text2); }
  .rv-action-row:hover svg { color: var(--amber); }
  /* Language welcome card on home */
  .lang-welcome-card {
    display: flex; align-items: center; gap: 12px; padding: 14px 16px; margin-bottom: 16px;
    background: var(--amber-glow); border: 1px solid var(--amber-border); border-radius: 10px;
  }
  .lang-welcome-card p { margin: 4px 0 0; font-size: 13px; color: var(--text2); }
  .lang-welcome-text { flex: 1; }
  .lang-welcome-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
  .lang-welcome-subtle { padding: 8px 12px; font-size: 13px; color: var(--text2); }
  /* Management LAN-only notice (passwordless + non-private client, issue #36) */
  .manage-locked-card { max-width: 520px; margin: 32px auto; }
  .manage-locked-card strong { font-size: 15px; }

  /* ── Tab bar ── */
  /* ── Language selector (globe icon) ── */
  .lang-dropdown {
    position: fixed; z-index: 9999; min-width: 240px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 10px; box-shadow: var(--shadow-popup);
    padding: 6px 0; display: none; max-height: 460px; overflow-y: auto;
  }
  .lang-dropdown.visible { display: block; }
  .lang-dropdown-item {
    padding: 7px 14px; cursor: pointer; font-size: 13px;
    color: var(--text); transition: background 0.15s;
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
  }
  .lang-dropdown-item.active { color: var(--amber); font-weight: 600; }
  .lang-dropdown-item .check { font-size: 14px; }
  /* ── Map picker (same place, another map) ── */
  .map-picker { min-width: 260px; max-width: min(340px, calc(100vw - 8px)); padding: 4px 0; }
  .mp-row {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 8px 14px; font-size: 13px; color: var(--text); cursor: pointer; line-height: 1.25;
  }
  .mp-row:hover { background: var(--amber-glow); }
  .mp-row.active { color: var(--amber); font-weight: 600; }
  .mp-name { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
  .mp-name > :first-child, .mp-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mp-tag { font-size: 11px; font-weight: 400; color: var(--text2); flex: none; }
  .mp-meta {
    display: inline-flex; align-items: center; gap: 6px; flex: none;
    font-size: 11px; font-weight: 400; color: var(--text2); font-variant-numeric: tabular-nums;
  }
  .mp-row.active .mp-meta { color: var(--amber); }
  .mp-check { font-size: 13px; }
  .mp-offer .mp-meta { color: var(--text); }
  .mp-offer .mp-meta svg { color: var(--amber); }
  .mp-head {
    padding: 10px 14px 2px; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text2); cursor: default;
  }
  .mp-fold .mp-meta::after { content: '\203A'; font-size: 14px; transform: rotate(90deg); display: inline-block; }
  .mp-fold[aria-expanded="true"] .mp-meta::after { transform: rotate(-90deg); }
  .mp-link { color: var(--text2); border-top: 1px solid var(--border); margin-top: 4px; padding-top: 10px; }
  .mp-link:hover { color: var(--amber); }
  .mp-wait { color: var(--text2); cursor: default; }
  .mp-busy { opacity: 0.6; pointer-events: none; }
  /* A place kept: its name, the map it is on when not this one, and its lists. */
  .mp-place .mp-name > span { unicode-bidi: plaintext; }
  .mp-save { color: var(--amber); font-weight: 600; }
  .mp-save svg { flex: none; }
  .mp-lists {
    display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; margin: -6px -8px -6px 0;
    border: 0; border-radius: 8px; background: none; color: var(--text2); cursor: pointer; padding: 0;
  }
  [dir="rtl"] .mp-lists { margin: -6px 0 -6px -8px; }
  .mp-lists:hover, .mp-lists:focus-visible { color: var(--amber); background: var(--amber-glow); outline: none; }
  @media (pointer: coarse) {
    .map-picker .mp-row { min-height: 44px; box-sizing: border-box; }
    .mp-lists { width: 44px; height: 44px; margin-block: -12px; }
  }
  .ld-interlang { display: flex; align-items: center; padding: 2px 6px; border-radius: 4px; cursor: pointer; opacity: 0.4; transition: all 0.15s; margin: -2px 0; }
  .ld-interlang:hover { opacity: 1; color: var(--amber); background: var(--amber-glow); }
  .lang-dropdown-item .ld-switch { display: flex; align-items: center; }
  .ld-switch-icon { opacity: 0.5; transition: opacity 0.15s; }
  .lang-dropdown-item.switchable:hover .ld-switch-icon { opacity: 1; color: var(--amber); }
  .lang-dropdown-item.switchable { border-left: 2px solid transparent; padding: 8px 14px; }
  .lang-dropdown-item.switchable:hover { border-left-color: var(--amber); }
  .ld-switch-label { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
  .ld-switch-lang { font-weight: 500; }
  .ld-switch-article { font-size: 11px; color: var(--text3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .lang-dropdown-item.switchable:hover .ld-switch-article { color: var(--amber); opacity: 0.7; }
  .ld-divider { height: 1px; background: var(--border); margin: 4px 0; }
  .ld-download { font-size: 12px; color: var(--text3); }
  .ld-download:hover { color: var(--amber); }
  .ld-download svg { opacity: 0.5; flex-shrink: 0; }
  .ld-download:hover svg { opacity: 1; color: var(--amber); }

  /* ── RTL support ── */
  [dir="rtl"] .topbar { direction: rtl; }
  [dir="rtl"] .back-btn .back-arrow { display: inline-block; transform: scaleX(-1); }
  /* Article/ZIM titles can be RTL (Arabic, Hebrew, Farsi) inside LTR
     chrome (and vice versa) — isolate so neighboring punctuation and
     counts don't reshuffle around them. */
  .title, .sg-title, .ci-title, .dc-title, .dl-item .dl-name, .browse-drilldown-title { unicode-bidi: isolate; }
  [dir="rtl"] .bc-sep { transform: scaleX(-1); }
  [dir="rtl"] .result { direction: rtl; }
  [dir="rtl"] .result-body { text-align: right; }
  /* A result's lines read in their own direction, still set from the
     interface's side: an English snippet under a Hebrew interface ended
     in "…" at its start. (Its source row needs no reversing: the result
     is already right to left, and reversing it again sent the row to the
     left, away from its title.) */
  .result .snippet { unicode-bidi: plaintext; }
  [dir="rtl"] .result-thumb { order: 1; }
  [dir="rtl"] .footer { direction: rtl; }
  [dir="rtl"] .footer-sub { direction: ltr; } /* keep kbd hints LTR */
  [dir="rtl"] .lang-dropdown { direction: rtl; text-align: right; }
  [dir="rtl"] .lang-dropdown-item.switchable { border-left: none; border-right: 2px solid transparent; }
  [dir="rtl"] .lang-dropdown-item.switchable:hover { border-right-color: var(--amber); }
  [dir="rtl"] .stat-card { direction: rtl; }
  [dir="rtl"] .star-btn { right: auto; left: 8px; }
  [dir="rtl"] .manage-tab { direction: rtl; }
  [dir="rtl"] .empty { direction: rtl; }

  /* ── Category headings ── */
  .cat-heading {
    font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
    margin: 32px 0 10px; padding-left: 2px;
    background: linear-gradient(90deg, var(--amber), var(--text2));
    -webkit-background-clip: text; -webkit-text-fill-color: transparent;
    background-clip: text;
  }
  .cat-heading:first-child { margin-top: 0; }
  .cat-heading.clickable { cursor: pointer; }
  .cat-heading.clickable:hover { opacity: 0.8; }
  /* The Other catch-all heading reads quieter than a named section (it's the
     leftover bucket) — flat muted colour instead of the amber gradient. */
  .cat-heading-other {
    background: none; -webkit-text-fill-color: initial; color: var(--text2); opacity: 0.7;
  }
  /* The global view toggle rides on the first section header's line, pinned to
     its trailing edge. Absolute (not flex) so the gradient-clipped heading text
     is untouched; text-fill-color reset so the toggle glyphs aren't transparent. */
  /* The view toggle's own glyphs sit visually heavier than plain heading text,
     so the shared 10px cat-heading bottom margin reads tight against the grid
     below it — nudge just this variant down by one 4px-grid step. */
  .cat-heading.has-view-toggle { position: relative; padding-inline-end: 210px; margin-bottom: 18px; }
  /* Order and view sit together at the end of the first section header: both
     answer "how do I want to look at my library", so they share one line and
     one anchor rather than each taking their own. */
  .cat-heading .lib-controls {
    position: absolute; inset-inline-end: 0; top: 50%; transform: translateY(-50%);
    display: flex; align-items: center; gap: 8px;
    -webkit-text-fill-color: initial;
  }
  .cat-heading .lib-view-toggle { -webkit-text-fill-color: initial; }

  /* ── Source header ── */
  .source-header { display: flex; gap: 16px; align-items: flex-start; padding: 4px 0 24px; }
  .sh-icon { width: 64px; height: 64px; flex-shrink: 0; border-radius: 12px; overflow: hidden; background: var(--surface2); display: flex; align-items: center; justify-content: center; }
  .sh-icon img { width: 58px; height: 58px; object-fit: cover; background: #fff; border-radius: 10px; padding: 2px; }
  .sh-info { flex: 1; min-width: 0; }
  .source-header h1 { font-size: 20px; font-weight: 600; color: var(--accent); margin: 0; }
  .sh-meta { font-size: 13px; color: var(--text2); margin-top: 4px; }
  .sh-desc { font-size: 13px; color: var(--text2); margin-top: 4px; line-height: 1.5; }
  .sh-actions { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; }
  .dl-pill { text-decoration: none; display: inline-block; }
  /* Compact download link on home cards for bookmark exports — lives inline in
     the .detail meta line, so it dresses like the text until hovered. */
  .card-dl-pill {
    text-decoration: none; color: var(--text2); font-weight: 600;
    border-bottom: 1px dotted var(--border);
  }
  .card-dl-pill:hover { color: var(--amber); border-bottom-color: var(--amber-border); }
  /* zimgit "document collection" flag in the source header meta line */
  .sh-chip {
    display: inline-block; font-size: 11px; font-weight: 600; letter-spacing: 0.03em;
    color: var(--amber); background: var(--amber-glow); border: 1px solid var(--amber-border);
    border-radius: 999px; padding: 1px 8px; vertical-align: middle;
  }
  /* ── zimgit / PDF collection document list ── */
  .zg-filter {
    width: 100%; box-sizing: border-box; margin: 0 0 12px; padding: 9px 12px;
    background: var(--surface); color: var(--text); font-size: 14px;
    border: 1px solid var(--border); border-radius: var(--radius); outline: none;
    transition: border-color 0.2s, box-shadow 0.2s;
  }
  .zg-filter:focus { border-color: var(--amber-border); box-shadow: 0 0 0 1px var(--amber-glow); }
  .zg-doc { display: flex; gap: 12px; align-items: flex-start; }
  .zg-icon {
    flex-shrink: 0; width: 38px; height: 46px; margin-top: 2px; border-radius: 4px;
    background: var(--surface2); border: 1px solid var(--border);
    display: flex; align-items: center; justify-content: center;
    font-size: 9px; font-weight: 700; letter-spacing: 0.05em; color: var(--amber);
  }
  .zg-meta { font-size: 11px; color: var(--text2); margin-top: 4px; }
  .zg-empty { padding: 28px 8px; text-align: center; color: var(--text2); font-size: 13px; }

  /* ── Source cards ── */
  .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 8px; margin-top: 8px; }
  .stat-card {
    background: var(--surface); border-radius: var(--radius); padding: 14px;
    border: 1px solid var(--card-border, var(--border));
    /* On the light cream the 1px border is ~1.2:1 and cards read as loose
       icon+text. A soft shadow (--card-shadow, light-only) lifts them into
       defined cards; dark keeps the flat surface-contrast look. Hover overrides
       both border-color and box-shadow at higher specificity. */
    box-shadow: var(--card-shadow, none);
    transition: background 0.25s, border-color 0.25s, box-shadow 0.25s, transform 0.25s;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    display: flex; gap: 14px; align-items: stretch; text-decoration: none; color: inherit; cursor: pointer;
    /* Long-press opens the context menu (#37) — suppress iOS's own text-selection
       callout / magnifier so the press reads as a clean right-click. */
    -webkit-touch-callout: none;
  }
  /* Zimi Maps' tile: a source among the sources, its icon a pin. */
  .apps-grid { margin-bottom: 16px; }
  /* An app saves from its own page (a video's Watch later, a question's
     Save, beside Like and Lists), so the header offers no second bookmark
     for the same thing; its home has nothing to save at all. The Saved
     panel's opener stays, and opens on the app's own saved things (B still
     saves what is on screen). A map's places are the map's: saved and
     listed in its own picker, Places and maps (map-source-btn). */
  body.app-page #library-btn, body.map-page #bm-panel-btn, body.map-page #library-btn { display: none !important; }
  /* Zimi's own stand-in (an article the ZIM does not hold): nothing to save. */
  body.own-page #library-btn { display: none !important; }
  /* An app's name and what it holds read together, beside its icon: a ZIM
     card's detail sits at the card's foot (margin-top: auto), which left a
     one-line app's name alone at the top and its detail a gap below. */
  .app-tile .card-info { justify-content: center; }
  .stat-card.app-tile .detail { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-top: 4px; }
  .app-tile .card-icon { color: var(--amber); display: flex; align-items: center; justify-content: center; }
  /* Quiet, like any other detail line: an empty app is not an error (Eric). */
  .app-tile.app-empty .detail { color: var(--text2); }
  /* An app with nothing inside is drawn as a place still to fill: a dotted
     outline reads "not set up yet" before the words do (Eric, 2026-09-29).
     Hover and focus keep the dots and take the amber like any card. */
  .stat-card.app-tile.app-empty { border: 1.5px dotted var(--text3); background: transparent; box-shadow: none; }
  .stat-card.app-tile.app-empty:hover, .stat-card.app-tile.app-empty:focus-visible { border-color: var(--amber); background: var(--surface2); }
  /* The Apps page: a section per app. The app is a banner, not a card shaped
     like a ZIM: a tinted band with its mark in a larger rounded square, its
     name set bigger, how many sources it reads, and a chevron, because it is
     a door. Its ZIMs follow set in on a rail, so they read as what the app is
     made of; the space between sections separates one app from the next. */
  .app-section { margin-top: 30px; }
  .cat-heading + .app-section { margin-top: 10px; }
  .stat-card.app-banner { flex-direction: row; align-items: center; gap: 14px; padding: 14px 16px; border-color: var(--amber-border);
    background: linear-gradient(to right, var(--amber-glow), transparent 80%), var(--surface); }
  [dir="rtl"] .stat-card.app-banner { background: linear-gradient(to left, var(--amber-glow), transparent 80%), var(--surface); }
  .app-banner .card-icon { width: 48px; height: 48px; border-radius: 14px; background: var(--amber-glow); box-shadow: inset 0 0 0 1px var(--amber-border); }
  .app-banner .zt { font-size: 18px; font-weight: 700; letter-spacing: -0.01em; }
  .stat-card.app-banner .detail { color: var(--text2); margin-top: 2px; }
  .app-banner::after { content: '\203A'; margin-inline-start: auto; padding-inline-start: 8px; font-size: 26px; line-height: 1; color: var(--amber); }
  /* The chevron mirrors itself in right to left (U+203A is bidi-mirrored). */
  .stat-card.app-banner.app-empty, [dir="rtl"] .stat-card.app-banner.app-empty { background: transparent; }
  .app-section > .stats-grid { margin: 10px 0 0; margin-inline-start: 23px; padding-inline-start: 13px; border-inline-start: 2px solid var(--border); gap: 6px; }
  /* In the list, the ZIMs are its contents, not headliners: a name, its
     numbers, a smaller mark. A ZIM's description is a long-press away (About
     this ZIM). */
  .app-section > .stats-grid:not(.tiles) .stat-card { padding: 8px 12px; gap: 12px; }
  .app-section > .stats-grid:not(.tiles) .card-icon { width: 34px; height: 34px; border-radius: 8px; }
  .app-section > .stats-grid:not(.tiles) .card-icon img { width: 30px; height: 30px; }
  .app-section > .stats-grid:not(.tiles) .desc { display: none; }
  .app-section > .stats-grid:not(.tiles) .card-info { justify-content: center; }
  .app-section > .stats-grid:not(.tiles) .detail { margin-top: 2px; }
  /* Reddot's dot keeps its colour in the breadcrumb, where line marks take the
     text's. Only a filled dot: Zimipedia's globe is a line mark too, and
     filling its circle painted a dark disc on the light header. */
  #bc-icon circle[fill="currentColor"] { fill: var(--amber); }
  .suggest-item.sg-place .sg-title::before { content: '\25CF'; color: var(--amber); font-size: 8px; margin-right: 8px; vertical-align: middle; }
  .stat-card:hover, .stat-card:focus-visible {
    background: var(--surface2); border-color: var(--amber-border);
    box-shadow: 0 4px 24px var(--amber-glow), 0 0 0 1px var(--amber-glow);
    transform: translateY(-2px); outline: none;
  }
  /* Brief amber pulse on a just-added source (e.g. a fresh bookmarks export)
     so the eye lands on it after the library refreshes. Self-clears in JS. */
  .stat-card.zimi-just-added {
    animation: zimiJustAdded 2.6s ease-out both;
  }
  @keyframes zimiJustAdded {
    0%   { border-color: var(--amber); box-shadow: 0 0 0 1px var(--amber), 0 6px 28px var(--amber-glow); }
    70%  { border-color: var(--amber); box-shadow: 0 0 0 1px var(--amber-border), 0 4px 20px var(--amber-glow); }
    100% { border-color: var(--border); box-shadow: none; }
  }
  .card-icon { width: 48px; height: 48px; flex-shrink: 0; border-radius: 10px; overflow: hidden; background: var(--surface2); display: flex; align-items: center; justify-content: center; align-self: center; }
  .card-icon img { width: 42px; height: 42px; object-fit: cover; background: #fff; border-radius: 8px; padding: 2px; }
  /* On paper, a source whose favicon is white/transparent (e.g. Army Publishing
     Directorate) rendered as an invisible white block on a white card. An inset
     ring on the icon block gives it a defined edge regardless of the artwork. */
  :root[data-theme="light"] .card-icon { box-shadow: inset 0 0 0 1px rgba(20, 15, 5, 0.10); }
  :root[data-theme="light"] .card-icon img { box-shadow: inset 0 0 0 1px rgba(20, 15, 5, 0.06); }
  .icon-letter { font-size: 20px; font-weight: 700; background: linear-gradient(135deg, var(--amber), #f97316); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
  .card-info { flex: 1; min-width: 0; display: flex; flex-direction: column; padding-right: 28px; }
  .stat-card .name { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 2px; }
  .stat-card .desc { font-size: 12px; color: var(--text2); line-height: 1.4; margin-bottom: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  /* Removed opacity here — multiplying the already-secondary --text2
     by 0.6 dropped contrast below WCAG AA. --text2 alone is the
     "secondary" cue. */
  .stat-card .detail { font-size: 11px; color: var(--text2); margin-top: auto; }

  /* ── Library view toggle (compact tiles vs full list) ── */
  .lib-view-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; vertical-align: middle; }
  .lib-view-btn {
    display: flex; align-items: center; justify-content: center; width: 32px; height: 28px;
    background: none; border: none; color: var(--text2); cursor: pointer; padding: 0;
    transition: background 0.15s, color 0.15s;
  }
  .lib-view-btn + .lib-view-btn { border-left: 1px solid var(--border); }
  .lib-view-btn:hover { color: var(--text); background: var(--surface2); }
  .lib-view-btn.active { color: var(--amber); background: var(--amber-glow); }

  /* Compact tiles: square-ish cells sharing .stat-card markup (same surface,
     radius, border, hover as the detail cards — one family, only the layout
     differs). A fixed vertical rhythm on a 4px grid keeps every tile reading
     the same regardless of title length:
       28px top band (reserved for the corner chips) → icon → two-line-tall
       title (anchors the baseline so short titles don't float) → language chip
       in normal flow.
     The old layout floated the title and pinned the language badge bottom-left,
     where it collided with any title that wrapped to two lines. */
  .stats-grid.tiles { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 10px; }
  .stats-grid.tiles .stat-card {
    flex-direction: column; align-items: center; justify-content: flex-start;
    gap: 8px; padding: 20px 12px 12px; text-align: center;
  }
  .stats-grid.tiles .card-icon { align-self: center; }
  .stats-grid.tiles .card-info { align-items: center; padding-right: 0; gap: 6px; flex: 0 0 auto; }
  .stats-grid.tiles .desc, .stats-grid.tiles .detail, .stats-grid.tiles .card-cat, .stats-grid.tiles .qid-badge { display: none; }
  /* The name row becomes a centred column: the title (.zt) on top, the language
     chip on its own line below. */
  .stats-grid.tiles .name {
    display: flex; flex-direction: column; align-items: center; gap: 6px; margin: 0;
  }
  /* Reserve two lines (2 x 1.35 line-height) so a one-line title lands at the
     same Y as a two-line one — the anchor that stops titles from floating. */
  .stats-grid.tiles .zt {
    font-size: 12px; line-height: 1.35; min-height: 2.7em;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; word-break: break-word;
  }
  /* Deterministic badge layout: status chip top-left, star top-right — opposite
     corners, so they can never collide at any title length. The language chip
     flows on its own line under the title (always visible, never clipped by the
     title clamp, never overlapping). Consistent tone with the list's inline chip.
     The top band is tight (20px pad): the chips sit high enough to slightly
     overlap the icon's top corners, which reads as intentional and reclaims a
     few px of tile height. */
  .stats-grid.tiles .new-badge { position: absolute; top: 6px; left: 8px; margin: 0; z-index: 1; }
  .stats-grid.tiles .star-btn { top: 5px; right: 6px; z-index: 1; }
  /* The chip owns its own line under the title and spells the language out in
     full ("Français") — no letter-spacing tracking (that's for the terse code
     form in list rows), and allow it to wrap rather than overflow the tile. */
  .stats-grid.tiles .lang-badge { margin: 0; }
  .stats-grid.tiles .lang-badge-full { letter-spacing: 0; white-space: normal; max-width: 100%; }
  /* Compact view hides the empty (unfavorited) star — only a filled star or a
     hover (pointer devices) reveals it, so tiles aren't peppered with grey stars. */
  .stats-grid.tiles .star-btn:not(.starred) { opacity: 0; pointer-events: none; }
  @media (hover: hover) {
    .stats-grid.tiles .stat-card:hover .star-btn:not(.starred) { opacity: 0.4; pointer-events: auto; }
  }
  /* Desktop has the width for roomier tiles — bump the min column so titles get
     more room; mobile keeps the tighter ~3-across from the base rule. */
  @media (min-width: 601px) {
    .stats-grid.tiles { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  }

  /* ── Search results ── */
  .meta { display: flex; gap: 12px; align-items: center; margin-bottom: 20px; font-size: 12px; color: var(--text2); }
  .meta .count { color: var(--amber); font-weight: 600; }
  .meta .time { opacity: 0.6; }

  .results { display: flex; flex-direction: column; gap: 3px; }
  /* Navigation cards are real links (#49) so browser link gestures work —
     keep the card look, not the UA's blue underlined text. */
  a.result, a.discover-card, a.history-item, a.bc-icon { color: inherit; text-decoration: none; }
  .result {
    display: flex; gap: 12px; padding: 14px 16px; background: var(--surface); border-radius: var(--radius);
    cursor: pointer; border: 1px solid transparent;
    transition: background 0.2s, border-color 0.2s; opacity: 0; animation: fadeUp 0.3s ease forwards;
  }
  .result:hover, .result:focus-visible { background: var(--surface2); border-color: var(--amber-border); box-shadow: 0 2px 12px var(--amber-glow); transform: translateX(4px); outline: none; }
  .result-thumb { flex-shrink: 0; width: 72px; height: 72px; border-radius: 8px; overflow: hidden; background: var(--surface2); display: none; align-self: center; order: 1; }
  .result-thumb.loaded { display: block; }
  .result-thumb img { width: 100%; height: 100%; object-fit: cover; }
  .result-body { flex: 1; min-width: 0; order: 0; }
  .result-source { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
  /* The search bar's offer to run the words in a map's own search box. */
  .result.map-find .title, .result.map-place .title { display: flex; align-items: center; gap: 8px; color: var(--amber); }
  .result.map-find .title svg, .result.map-place .title svg { flex-shrink: 0; }
  .result-source img { width: 20px; height: 20px; border-radius: 4px; flex-shrink: 0; background: #fff; }
  .result-source .rs-letter {
    width: 20px; height: 20px; border-radius: 4px; background: var(--surface2);
    display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; color: var(--text2); flex-shrink: 0;
  }
  .result-source .rs-name { font-size: 12px; color: var(--text2); font-weight: 500; }
  .result .title { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 4px; }
  .result .snippet { font-size: 12px; color: var(--text2); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; transition: opacity 0.3s; }
  /* The words searched for: bold, in the title and in the snippet (an amber
     underline under every title read as too much). Never a filled block,
     which read as a selection. */
  .result mark.hit { background: none; color: inherit; }
  .result .title mark.hit { font-weight: 800; }
  .result .snippet mark.hit { color: var(--text); font-weight: 600; }
  /* Grouped by source: each source's best, under its name, and "More from". */
  .results.grouped { gap: 20px; }
  .result-group { display: flex; flex-direction: column; gap: 3px; }
  .result-group-head { margin: 0 0 4px; padding-inline: 4px; font: inherit; }
  .result-group-head .result-source { margin-bottom: 0; }
  .result-group-head .rs-name { font-size: 13px; font-weight: 600; color: var(--text); }
  .result-more {
    align-self: flex-start; margin-inline-start: 8px; padding: 6px 8px; border: none; border-radius: 8px;
    background: transparent; color: var(--amber); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  }
  .result-more:hover, .result-more:focus-visible { background: var(--amber-glow); outline: none; }

  @keyframes fadeUp {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
  }
  .result:nth-child(1) { animation-delay: 0s; }
  .result:nth-child(2) { animation-delay: 0.04s; }
  .result:nth-child(3) { animation-delay: 0.08s; }
  .result:nth-child(4) { animation-delay: 0.12s; }
  .result:nth-child(5) { animation-delay: 0.16s; }
  .result:nth-child(n+6) { animation-delay: 0.2s; }

  /* ── Load more ── */
  .load-more { text-align: center; padding: 16px; }
  .load-more button {
    padding: 10px 24px; font-size: 13px; font-weight: 500; cursor: pointer;
    background: var(--surface); color: var(--text); border: 1px solid var(--border);
    border-radius: var(--radius); transition: all 0.2s;
  }
  .load-more button:hover,
  .ci-add-btn:hover { border-color: var(--amber-border); background: var(--amber-glow); }

  /* ── Reader ── */
  .reader-view { position: fixed; top: var(--under-topbar); left: 0; right: 0; bottom: 0; display: none; flex-direction: column; z-index: 100; background: var(--bg); }
  .reader-view.open { display: flex; animation: fadeIn 0.15s ease; }
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
  /* Find in page (find.js): floats over the article's top corner, the width
     of the reader on a phone. */
  .find-bar {
    display: none; position: absolute; top: 8px; inset-inline-end: 12px; z-index: 5;
    width: min(380px, calc(100% - 24px)); box-sizing: border-box;
    align-items: center; gap: 2px; padding-block: 4px; padding-inline: 12px 4px;
    background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.22);
  }
  .find-bar.open { display: flex; animation: fadeIn 0.12s ease; }
  .find-input {
    flex: 1; min-width: 0; padding: 6px 2px; border: none; outline: none; background: transparent;
    color: var(--text); font: inherit; font-size: 15px; -webkit-appearance: none; appearance: none;
  }
  .find-input::-webkit-search-cancel-button { -webkit-appearance: none; display: none; }
  .find-count { flex-shrink: 0; padding-inline: 6px; font-size: 12px; color: var(--text2); white-space: nowrap; font-variant-numeric: tabular-nums; }
  .find-bar.find-none .find-count { color: var(--error); }
  .find-bar .find-step, .find-bar .find-close { width: 32px; height: 32px; }
  .find-bar .find-step:disabled { opacity: 0.35; cursor: default; background: transparent; color: var(--text2); }
  /* A phone: a band over the article, not a card on it, so the first lines
     move down rather than hide under it. */
  @media (max-width: 600px) {
    .find-bar { position: static; order: -1; width: auto; margin: 8px 8px 0; box-shadow: none; }
  }
  #reader-frame { flex: 1; border: none; width: 100%; background: #fff; }
  /* An app page is drawn on the app's ground, not on a ZIM page's white:
     whatever of the frame shows past the page (iOS's bounce at the top, a
     part pixel as the header slides) was a white hairline in either theme. */
  body.app-page #reader-frame { background: var(--bg); }
  /* The header steps aside on a phone while an app or the Almanac is read
     (body.chrome-away, set by _chromeScroll in app.js): it slides up and
     the page takes its room. Keyboard focus in it brings it back. */
  @media (max-width: 900px), (max-height: 500px) {
    .topbar { transition: transform 0.2s ease; }
    .reader-view, body .almanac-view { transition: top 0.2s ease; }
    body.chrome-away { --under-topbar: var(--conn-h); }
    body.chrome-away .topbar:not(:focus-within) { transform: translateY(var(--topbar-away)); }
    body.chrome-away .conn-banner { top: 0; }
  }
  @media (prefers-reduced-motion: reduce) {
    .topbar, .reader-view, body .almanac-view { transition: none; }
  }
  /* Held away (a book being read, a video on a phone turned sideways):
     at every size, since the book's own header takes its place. */
  body.chrome-held { --under-topbar: var(--conn-h); }
  body.chrome-held .topbar:not(:focus-within) { transform: translateY(var(--topbar-away)); }
  body.chrome-held .conn-banner { top: 0; }

  /* ── Context Menu ── */
  .zimi-ctx {
    position: fixed; z-index: 10000; min-width: 180px;
    background: var(--card-bg, #1a1a1a); border: 1px solid var(--border);
    border-radius: 8px; padding: 4px 0; box-shadow: 0 8px 24px rgba(0,0,0,0.5);
    font-size: 13px; color: var(--text); display: none;
  }
  .zimi-ctx.open { display: block; }
  .zimi-ctx-item { padding: 8px 14px; cursor: pointer; display: flex; align-items: center; gap: 8px; }
  .zimi-ctx-item:hover { background: var(--hover); }
  .zimi-ctx-item .ctx-key { margin-inline-start: auto; font-size: 11px; color: var(--text2); }
  /* ── Where a link to the web goes (#99): beside the link, over the frame ── */
  .ext-sheet {
    position: fixed; z-index: 10002; display: none;
    max-width: min(320px, calc(100vw - 16px)); padding: 10px 12px;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: 10px; box-shadow: var(--shadow-popup);
    color: var(--text); font-size: 13px; line-height: 1.4;
  }
  .ext-sheet.open { display: block; }
  .ext-sheet-where { display: flex; align-items: flex-start; gap: 8px; }
  .ext-sheet-where svg { flex: none; width: 13px; height: 13px; margin-top: 3px; color: var(--text2); }
  [dir="rtl"] .ext-sheet-where svg { transform: scaleX(-1); }
  .ext-sheet-where bdi { font-weight: 600; overflow-wrap: anywhere; }
  .ext-sheet-note { color: var(--text2); margin: 4px 0 0; padding-inline-start: 21px; }
  .ext-sheet-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; padding-inline-start: 21px; }
  @media (pointer: coarse) { .ext-sheet { font-size: 14px; } }
  /* ── Word lookup (Define) popover ── */
  .define-popover {
    position: fixed; z-index: 10001; display: none;
    background: var(--card-bg, #1a1a1a); border: 1px solid var(--border);
    border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.5);
    color: var(--text); font-size: 13px;
    /* Never wider than the viewport minus the 8px edge margins the JS clamps to,
       so a card near the right edge still fits after _definePosition runs. */
    max-width: min(320px, calc(100vw - 16px));
  }
  .define-popover.open { display: block; }
  /* Stage 1: the compact "Define" trigger button */
  .define-trigger {
    display: flex; align-items: center; gap: 6px;
    padding: 7px 12px; cursor: pointer; white-space: nowrap; font-weight: 500;
  }
  .define-trigger:hover { color: var(--amber); }
  .define-trigger svg { width: 14px; height: 14px; }
  /* Stage 2: the definition card */
  .define-card { padding: 10px 14px 12px; max-height: 40vh; overflow-y: auto; }
  .define-card .define-word {
    font-weight: 600; font-size: 15px; margin-bottom: 4px;
    display: flex; align-items: baseline; gap: 8px;
  }
  .define-card .define-word .define-lang { font-size: 11px; color: var(--text2); font-weight: 400; }
  .define-card .define-body { color: var(--text); line-height: 1.5; }
  .define-card .define-body ol { margin: 4px 0 0; padding-inline-start: 20px; }
  .define-card .define-body li { margin: 2px 0; }
  .define-card .define-status { color: var(--text2); font-style: italic; }
  .define-card .define-open {
    display: inline-block; margin-top: 8px; font-size: 12px;
    color: var(--amber); cursor: pointer; text-decoration: none;
  }
  .define-card .define-open:hover { text-decoration: underline; }
  /* ── Highlights: the selection bar and the note card (highlights.js) ──
     The four colours are the page's own highlight colours at full strength. */
  .hl-c-yellow { background: #ffcc00; } .hl-c-green { background: #34c759; }
  .hl-c-blue { background: #409cff; } .hl-c-pink { background: #ff4081; }
  .hl-bar {
    position: fixed; z-index: 10001; display: none; align-items: center; gap: 2px; padding: 4px;
    background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: var(--shadow-popup); color: var(--text); font-size: 13px;
    max-width: calc(100vw - 16px); -webkit-user-select: none; user-select: none;
  }
  .hl-bar.open { display: flex; }
  .hl-bar button {
    display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 38px; min-width: 38px;
    padding: 0 10px; border: 0; border-radius: 8px; background: none; color: inherit;
    font: inherit; font-weight: 500; cursor: pointer; white-space: nowrap; -webkit-tap-highlight-color: transparent;
  }
  .hl-bar button.hl-icon, .hl-bar button.hl-swatch { padding: 0; width: 38px; }
  .hl-bar button:hover, .hl-bar button:focus-visible { color: var(--amber); background: var(--amber-glow); outline: none; }
  .hl-bar svg { width: 17px; height: 17px; flex-shrink: 0; }
  .hl-swatches { display: flex; }
  .hl-swatch span { width: 20px; height: 20px; border-radius: 50%; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.18); }
  .hl-swatch[aria-pressed="true"] span { box-shadow: 0 0 0 2px var(--card-bg), 0 0 0 4px var(--text); }
  .hl-sep { width: 1px; align-self: stretch; margin: 6px 3px; background: var(--border); }
  .hl-bar.hl-tight .hl-note-btn { padding: 0; }
  .hl-bar.hl-tight .hl-note-btn > span { display: none; }
  /* A thumb's size on a touch screen. */
  @media (pointer: coarse) {
    .hl-bar button { min-height: 44px; min-width: 44px; }
    .hl-bar button.hl-icon, .hl-bar button.hl-swatch { width: 44px; }
    .hl-note-actions .pill { min-height: 44px; }
  }
  /* The note card sits near the top: on a phone the keyboard takes the bottom. */
  .hl-note {
    position: fixed; z-index: 10002; display: none; flex-direction: column; gap: 10px;
    top: calc(12px + env(safe-area-inset-top, 0px)); left: 50%; transform: translateX(-50%);
    width: min(440px, calc(100vw - 24px)); padding: 14px;
    background: var(--card-bg); border: 1px solid var(--border); border-radius: 14px;
    box-shadow: var(--shadow-popup); color: var(--text);
  }
  .hl-note.open { display: flex; }
  .hl-note-quote {
    padding-inline-start: 10px; border-inline-start: 3px solid var(--hl-c, #ffcc00); color: var(--text2);
    font-size: 13px; line-height: 1.45; overflow: hidden;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  }
  .hl-b-yellow { --hl-c: #ffcc00; } .hl-b-green { --hl-c: #34c759; } .hl-b-blue { --hl-c: #409cff; } .hl-b-pink { --hl-c: #ff4081; }
  /* 16px: iOS does not zoom the page into a field this size. */
  .hl-note-text {
    min-height: 96px; resize: vertical; padding: 10px; font: inherit; font-size: 16px; line-height: 1.4;
    color: var(--text); background: var(--surface); border: 1px solid var(--border-strong); border-radius: 10px;
  }
  .hl-note-text:focus { outline: 2px solid var(--amber-border); border-color: var(--amber); }
  .hl-note-actions { display: flex; justify-content: flex-end; gap: 8px; }
  .hl-note-actions .pill { min-height: 36px; padding: 0 16px; }
  /* ── Library health report ── */
  .library-health:empty { display: none; }
  .library-health { margin-top: 12px; }
  .health-summary { font-weight: 600; margin: 8px 0; color: var(--text); }
  .health-table { border-top: 1px solid var(--border); }
  .health-row {
    display: flex; align-items: baseline; gap: 8px;
    padding: 6px 2px; border-bottom: 1px solid var(--border); font-size: 13px;
  }
  .health-row .health-mark { flex: 0 0 auto; width: 16px; text-align: center; }
  .health-row .health-name { flex: 0 0 auto; font-weight: 500; color: var(--text); }
  .health-row .health-detail { flex: 1 1 auto; color: var(--text2); text-align: end; }
  /* Small labelled chips for the "has title index / Q-ID index" indicators. */
  .health-idx-badge {
    display: inline-block; margin-inline-start: 5px; padding: 1px 6px;
    font-size: 10px; font-weight: 600; letter-spacing: 0.04em; border-radius: 4px;
    background: var(--surface2); color: var(--text2); border: 1px solid var(--border);
    vertical-align: middle;
  }
  .health-row.health-warn .health-detail { color: var(--amber); }
  .health-row.health-info .health-detail { color: var(--text2); }
  .health-row .health-redownload-btn {
    flex: 0 0 auto; font-size: 11px; padding: 2px 8px; border-radius: 5px; cursor: pointer;
    background: var(--surface2); color: var(--amber); border: 1px solid var(--border);
  }
  .health-row .health-redownload-btn:hover { border-color: var(--amber); }
  .health-row .health-redownload-btn.confirming { background: var(--amber-glow, rgba(245,158,11,.15)); font-weight: 600; }
  /* ── Bookmarks panel: Export to ZIM ── */
  .hp-actions { display: flex; align-items: center; gap: 10px; padding: 8px 12px; flex-wrap: wrap; }
  .hp-action-btn {
    font-size: 12px; padding: 5px 12px; border-radius: 6px; cursor: pointer;
    background: var(--surface2); color: var(--text); border: 1px solid var(--border);
  }
  .hp-action-btn:hover:not(:disabled) { color: var(--amber); border-color: var(--amber-border, var(--amber)); }
  .hp-action-btn:disabled { opacity: 0.5; cursor: default; }
  .hp-action-status { font-size: 12px; color: var(--text2); }
  .reader-loading {
    position: absolute; inset: 0; display: flex; align-items: center;
    justify-content: center; flex-direction: column; gap: 12px;
    background: var(--bg); color: var(--text2); font-size: 13px;
    z-index: 5; transition: opacity 0.3s;
  }
  .reader-loading.hidden { opacity: 0; pointer-events: none; }
  .reader-loading .spinner,
  .loading .spinner-inline,
  .create-status .spinner-inline,
  .content-search-indicator .spinner-inline { border: 2px solid var(--border); border-top-color: var(--amber); border-radius: 50%; animation: spin 0.7s linear infinite; }
  .reader-loading .spinner { width: 24px; height: 24px; }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* ── Footer ── */
  .footer { text-align: center; padding: 20px 16px 16px; font-size: 12px; color: var(--text2); border-top: 1px solid var(--border); margin-top: 16px; }
  .footer .footer-brand {
    background: linear-gradient(135deg, #f59e0b, #f97316);
    -webkit-background-clip: text; -webkit-text-fill-color: transparent;
    background-clip: text; font-weight: 600; text-decoration: none;
  }
  .footer .footer-brand:hover { opacity: 0.8; }

  /* ── Utility ── */
  .loading { text-align: center; padding: 40px; color: var(--text2); font-size: 13px; }
  .loading .spinner-inline,
  .create-status .spinner-inline { display: inline-block; width: 14px; height: 14px; vertical-align: middle; margin-right: 8px; }
  .content-search-indicator { text-align: center; padding: 12px; color: var(--text2); font-size: 12px; animation: fadeIn 0.3s ease; }
  .content-search-indicator .spinner-inline { display: inline-block; width: 12px; height: 12px; vertical-align: middle; margin-right: 6px; }
  .empty { text-align: center; padding: 48px 20px; color: var(--text2); }
  .empty p { font-size: 14px; margin-bottom: 8px; }
  .empty .hint { font-size: 12px; opacity: 0.5; }
  .did-you-mean { font-size: 14px; color: var(--text2); margin-bottom: 16px; }
  .empty .did-you-mean { margin-bottom: 12px; }
  .did-you-mean a { color: var(--amber); font-style: italic; text-decoration: none; border-bottom: 1px solid var(--amber-border); }
  .did-you-mean a:hover { border-bottom-color: var(--amber); }
  .search-deeper {
    text-align: center; padding: 14px 20px; margin-top: 16px;
    color: var(--amber); font-size: 13px; cursor: pointer;
    border: 1px dashed var(--border); border-radius: var(--radius);
    transition: all 0.2s;
  }
  .search-deeper:hover, .search-deeper:focus-visible { border-color: var(--amber-border); background: var(--surface); outline: none; }
  #main-view { min-height: calc(100vh - 56px); }
  #main-view.hidden { display: none; }

  /* ── Manage page ── */
  .manage-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 16px; }
  .manage-card h2 { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 12px; }
  .manage-card .mc-row, .ms-pane .mc-row { display: flex; justify-content: space-between; align-items: center; font-size: 13px; color: var(--text2); padding: 4px 0; }
  .manage-card .mc-row .mc-label, .ms-pane .mc-label { color: var(--text2); }
  .manage-card .mc-row .mc-value, .ms-pane .mc-value { color: var(--text); font-weight: 500; }
  /* What a count above is made of: indented under it, smaller and quieter. */
  .ms-pane .mc-row.mc-sub { font-size: 12px; padding: 1px 0; padding-inline-start: 14px; }
  .ms-pane .mc-row.mc-sub .mc-value { color: var(--text2); font-weight: 400; }
  .ms-pane .mc-row.mc-sub:last-child { padding-bottom: 5px; }
  /* App updates (Server pane): quiet when current, amber when a release is out */
  /* The four atoms every settings section states a fact with: the notable
     value, the quiet one, an outbound link, and a command to copy. Scoped to
     the pane rather than to the app-update block they were written for — the
     Creator and auto-update sections say the same four kinds of thing, and
     three copies of these rules under three block classes is how a pane ends
     up with a differently-coloured link in each section. */
  .ms-pane .app-update-badge { color: var(--amber); font-weight: 600; }
  .ms-pane .app-update-quiet { color: var(--text2); }
  .ms-pane .app-update-link { color: var(--amber); }
  .ms-pane code.app-update-cmd { display: block; margin: 6px 0 2px; padding: 6px 8px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; font-size: 12px; color: var(--amber); user-select: all; overflow-x: auto; white-space: nowrap; }
  /* The ZIMs the auto-updater cannot reach, as a wrapped row of names. Chips
     rather than a list: this is a set to scan for a name you recognise, not
     something to read top to bottom. */
  .au-skipped { display: flex; flex-wrap: wrap; gap: 5px; margin: 8px 0 2px; }
  .au-skipped .act-chip { max-width: 220px; }
  /* Caches: data-dir storage breakdown */
  .mc-section-title { font-size: 12px; font-weight: 600; color: var(--text); margin: 2px 0 8px; }
  .cache-bar { display: flex; width: 100%; height: 14px; border-radius: 6px; overflow: hidden; background: var(--surface2); border: 1px solid var(--border); }
  .cache-seg { height: 100%; min-width: 2px; }
  .cache-seg + .cache-seg { box-shadow: inset 1px 0 0 rgba(0,0,0,0.28); }
  .cache-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 8px 0 2px; font-size: 11px; color: var(--text2); }
  .cache-legend-item { display: inline-flex; align-items: center; gap: 5px; }
  .cache-legend-swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; }
  .cache-legend-item b { color: var(--text); font-weight: 600; }
  /* The per-kind count beside a value ("· 410"), quieter than the size it
     qualifies. Shared with the create page's finished-capture summary. */
  .cache-legend-n { color: var(--text3); font-variant-numeric: tabular-nums; }
  .cache-top-zims { font-size: 11px; color: var(--text2); margin-top: 8px; line-height: 1.5; }
  .cache-top-label { color: var(--text); font-weight: 600; }
  .manage-btn-action,
  .desktop-card .btn-primary,
  .coll-form button { background: linear-gradient(135deg, var(--amber), var(--amber2)); color: var(--on-amber); border: none; border-radius: var(--radius); cursor: pointer; }
  .manage-btn-action:hover,
  .desktop-card .btn-primary:hover { opacity: 0.85; }
  .manage-btn-action:disabled,
  .desktop-card .btn-primary:disabled { opacity: 0.4; cursor: default; }
  .manage-btn-action { padding: 8px 16px; font-size: 12px; font-weight: 600; transition: opacity 0.2s; white-space: nowrap; }
  /* macOS-style settings panel */
  .manage-settings { display: flex; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 16px; min-height: 180px; overflow: hidden; }
  .ms-nav { width: 140px; flex-shrink: 0; border-right: 1px solid var(--border); padding: 8px 0; display: flex; flex-direction: column; }
  .ms-nav-item { padding: 8px 16px; font-size: 13px; color: var(--text2); cursor: pointer; border: none; background: none; text-align: left; transition: all 0.15s; font-family: inherit; }
  .ms-nav-item:hover { color: var(--text); background: var(--amber-glow); }
  .ms-nav-item.active { color: var(--amber); background: var(--amber-glow); }
  .ms-pane { flex: 1; padding: 16px 20px; }
  .ms-pane .ms-actions { margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap; }
  /* Settings' on/off rows: the sharing card's row shape, the whole row the
     label of its switch. Apps add their icon at the start. */
  .set-rows { margin: 10px 0 0; }
  label.set-row { cursor: pointer; }
  .set-row .share-row-title, .set-row .share-row-desc { display: block; }
  .set-row .share-row-title { font-weight: 500; font-size: 14px; }
  .set-row-icon { display: flex; flex-shrink: 0; color: var(--amber); }
  .set-row-icon svg { width: 22px; height: 22px; }
  .set-row.share-locked { cursor: default; }
  /* A section header with its actions at the far end (Apps: All, None). */
  .ms-section-head { display: flex; align-items: center; gap: 4px; }
  .ms-section-head > span { margin-inline-start: auto; display: flex; gap: 2px; }
  .set-head-btn {
    font: inherit; font-size: 12px; font-weight: 500; text-transform: none; letter-spacing: 0;
    color: var(--amber); background: none; border: none; border-radius: 6px; padding: 4px 8px; cursor: pointer;
  }
  .set-head-btn:hover { background: var(--amber-glow); }
  .ms-pane .ms-field { margin-bottom: 10px; }
  /* Field labels stay mixed-case 12px — uppercase is reserved for section
     headers so the two hierarchy levels read as different levels. */
  .ms-pane .ms-field label { display: block; font-size: 12px; font-weight: 500; color: var(--text2); margin-bottom: 4px; }
  .ms-pane .ms-field input {
    width: 100%; padding: 7px 10px; border-radius: var(--radius); border: 1px solid var(--border);
    background: var(--bg); color: var(--text); font-size: 13px; box-sizing: border-box;
  }
  .ms-pane .ms-field input:focus { outline: none; border-color: var(--amber-border); }
  .ms-pane .ms-field input[readonly] { opacity: 0.6; }
  /* A hint reads the same wherever it sits: the settings panes, and the
     catalog (the StreetZim note under the Maps toggle was 16px body text). */
  .ms-hint { font-size: 12px; line-height: 1.5; color: var(--text2); margin-top: 8px; }
  /* Settings controls, one language (Eric, 2026-10-03: "upgrade all controls
     and buttons in settings to be beautiful with this new language"). Cards
     are .share-rows, choices of three or four are the segmented selector
     (.app-theme-seg), on/off is .switch; buttons, selects and fields are
     these: one height, one round quiet button, one amber primary. */
  .ms-pane { --set-ctl-h: 34px; }
  .set-btn, .ms-pane .pill:not(.cat-pill):not(.reorder-add), .ms-pane .ms-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px; box-sizing: border-box;
    min-height: var(--set-ctl-h); padding: 0 14px; border-radius: 999px;
    font: inherit; font-size: 13px; font-weight: 500; line-height: 1; white-space: nowrap;
    background: var(--surface2); border: 1px solid var(--border); color: var(--text);
    cursor: pointer; transition: color 0.15s, background 0.15s, border-color 0.15s;
  }
  .set-btn:hover:not(:disabled), .ms-pane .pill:not(.cat-pill):not(.reorder-add):hover, .ms-pane .ms-btn:hover {
    color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow);
  }
  .set-btn:focus-visible, .ms-pane .pill:focus-visible, .ms-pane .ms-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
  .set-btn:disabled { opacity: 0.45; cursor: default; }
  .set-btn.primary, .ms-pane .ms-btn-primary, .ms-pane .pill.active,
  .ms-pane .manage-btn-action { background: var(--amber); color: var(--on-amber); border-color: transparent; font-weight: 600; }
  .set-btn.primary:hover:not(:disabled), .ms-pane .ms-btn-primary:hover, .ms-pane .pill.active:hover { color: var(--on-amber); background: var(--amber); filter: brightness(1.06); }
  .ms-pane .manage-btn-action { min-height: var(--set-ctl-h); border-radius: 999px; font-size: 13px; padding: 0 16px; }
  .ms-pane select {
    min-height: var(--set-ctl-h); box-sizing: border-box; padding: 0 30px 0 12px; border-radius: var(--radius);
    font: inherit; font-size: 13px; color: var(--text); background-color: var(--surface2); border: 1px solid var(--border);
    -webkit-appearance: none; appearance: none; cursor: pointer;
    /* The caret, drawn in the theme's own text colour. */
    background-image: linear-gradient(45deg, transparent 50%, var(--text2) 50%), linear-gradient(135deg, var(--text2) 50%, transparent 50%);
    background-position: calc(100% - 16px) 52%, calc(100% - 11px) 52%; background-size: 5px 5px; background-repeat: no-repeat;
  }
  [dir="rtl"] .ms-pane select { padding: 0 12px 0 30px; background-position: 16px 52%, 11px 52%; }
  .ms-pane select:hover:not(:disabled) { border-color: var(--amber-border); }
  .ms-pane select:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
  .ms-pane select:disabled { opacity: 0.55; cursor: default; }
  .ms-pane .set-rows .set-row { min-height: 54px; box-sizing: border-box; }
  .ms-pane .ms-section-label:first-child { margin-top: 0; }
  @media (pointer: coarse) { .ms-pane { --set-ctl-h: 40px; } }
  .browse-drilldown-header + .maps-source-toggle + .ms-hint, .maps-source-toggle + .ms-hint { margin: 6px 0 14px; }

  /* Environment panel: a name, its value, and what it takes over. Monospace
     on the first two because they are literals an admin will retype into a
     unit file or a shell, where a proportional font makes that error-prone. */
  .env-rows { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
  .env-row { display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr); gap: 4px 10px; align-items: baseline; }
  .env-name { font-size: 12px; color: var(--text); word-break: break-all; }
  .env-value { font-size: 12px; color: var(--amber); word-break: break-all; }
  .env-secret { color: var(--text3); font-style: italic; }
  .env-what { grid-column: 1 / -1; font-size: 12px; color: var(--text2); }
  /* On a line of its own: run on, "…unauthenticated locks Set password" read as one sentence. */
  .zi-ev-fact.zi-ev-stopped, .zi-warn.zi-incomplete { color: var(--amber2); }
  .env-locks { color: var(--text3); display: block; }
  @media (max-width: 560px) {
    /* Value under the name rather than beside it: a long path or a proxy list
       otherwise pushes the row sideways on a phone. */
    .env-row { grid-template-columns: minmax(0, 1fr); }
  }
  /* What Zimi fetches from the internet: folded to its counts, which is the
     answer; open, a name and its state on one line, what and where under it.
     The state is the one word that matters, so "on its own" is amber. */
  .net-details > summary { cursor: pointer; font-size: 13px; color: var(--text); padding: 4px 0; }
  .net-rows { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
  .net-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 10px; align-items: baseline; }
  .net-name { font-size: 13px; color: var(--text); }
  .net-state { justify-self: end; font-size: 12px; color: var(--text2); white-space: nowrap; }
  .net-state-auto { color: var(--amber); }
  .net-state-off, .net-state-unset { color: var(--text3); }
  .net-what { font-size: 12px; line-height: 1.5; color: var(--text2); }
  /* The way to a row's switch: the state's column, under the state, on
     every row, so the eye finds it in one place. */
  .net-act { justify-self: end; }
  /* Where it goes on a line of its own, whole names (a host broken mid-word
     reads as two). */
  .net-where { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; font-size: 11px; color: var(--text3); }
  .net-where code { overflow-wrap: anywhere; }
  .net-go { font-size: 12px; color: var(--amber); white-space: nowrap; }
  /* The environment folded to the names it sets. */
  .env-details > summary { overflow-wrap: anywhere; }
  .env-names { font-family: monospace; font-size: 12px; }
  .env-details[open] > summary { margin-bottom: 10px; }
  /* App-theme segmented control (Auto / Dark / Light). Three equal pills in a
     recessed track, amber-filled when active — mirrors the reader palette's
     control language so the two theme pickers feel like one family. */
  .ms-theme-label { font-size: 13px; color: var(--text); margin: 4px 0 8px; }
  .app-theme-seg {
    display: flex; gap: 4px; padding: 4px; border-radius: var(--radius);
    background: var(--surface2); border: 1px solid var(--border); max-width: 340px;
  }
  .app-theme-btn {
    flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    padding: 7px 8px; border: 1px solid transparent; border-radius: 7px;
    background: none; color: var(--text2); font-size: 12.5px; font-weight: 500;
    cursor: pointer; transition: color 0.15s, background 0.15s, border-color 0.15s;
  }
  .app-theme-btn svg { flex-shrink: 0; }
  .app-theme-btn:hover { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .app-theme-btn.active {
    background: var(--amber); color: var(--on-amber); border-color: transparent; font-weight: 600;
  }
  .app-theme-btn:disabled { cursor: default; opacity: 0.5; }
  .app-theme-btn:disabled:hover { color: var(--text2); border-color: transparent; background: none; }
  .app-theme-btn.active:disabled:hover { color: var(--on-amber); background: var(--amber); }
  .app-theme-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
  .ms-pane .app-theme-btn { min-height: calc(var(--set-ctl-h) - 2px); }
  .ci-section-label, .hp-group-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text2); }
  /* Every Settings section's title: the text's own colour, a size above the
     rows it heads, more room above than below (Eric, 2026-10-05: "the titles
     now are like soo small and hidden and dull"). One rule, every section. */
  .ms-section-label { font-size: 16px; font-weight: 600; line-height: 1.3; color: var(--text); margin: 32px 0 10px; }
  /* Creator "made here" breakdown: amber count chips over a sortable table.
     Numbers are tabular so the columns line up; the table scrolls inside its
     own box on a phone rather than pushing the pane sideways. */
  .cr-made-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
  .cr-made-chip { font-size: 12px; color: var(--text2); padding: 4px 10px; border: 1px solid var(--border); border-radius: 999px; }
  .cr-made-chip b { color: var(--amber); font-weight: 600; }
  .cr-made-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .cr-made-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
  .cr-made-table th, .cr-made-table td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border); white-space: nowrap; }
  .cr-made-th { font-weight: 500; color: var(--text2); cursor: pointer; user-select: none; }
  .cr-made-th:hover { color: var(--amber); }
  .cr-made-name { color: var(--text); max-width: 240px; overflow: hidden; text-overflow: ellipsis; }
  /* The name is a link, and an unstyled one gets the browser's blue and an
     underline — the only place in the app that happened. It reads as ordinary
     text and answers to the pointer the way the sortable headers beside it do. */
  .cr-made-name a { color: inherit; text-decoration: none; }
  .cr-made-name a:hover, .cr-made-name a:focus-visible { color: var(--amber); }
  .cr-made-num { font-variant-numeric: tabular-nums; color: var(--text2); }
  /* Auto-fit grid for catalog + downloads. 320px min → 2 columns in the
     manage pane (~790px wide), 3 on wide screens, 1 on mobile. Cards switch
     to vertical stack inside grid cells so they don't get cramped. */
  .catalog-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 6px; align-items: stretch;
  }
  /* Compact grid-card: icon top-left, title+summary right of icon, actions bottom-right.
     Saves vertical space vs full-vertical-stack. */
  .catalog-grid .catalog-item {
    display: grid;
    grid-template-columns: 32px 1fr;
    grid-template-areas: "icon info" "actions actions";
    gap: 2px 10px; align-items: start;
    max-height: none; padding: 8px 10px;
    position: relative;
  }
  .catalog-grid .catalog-item .ci-icon { grid-area: icon; width: 32px; height: 32px; }
  .catalog-grid .catalog-item .ci-icon img { width: 28px; height: 28px; }
  .catalog-grid .catalog-item .ci-icon .ci-letter { width: 32px; height: 32px; font-size: 14px; }
  /* Reserve the checkbox corner on the WHOLE text column, not just the title:
     the 18px visual box (centred in its 40px hit area) reaches 29px in from
     the card's right edge, i.e. 19px past the 10px card padding. 24px here
     keeps every line — title, description AND meta — clear of it with a
     breathing gap, at any viewport. (Padding only the title let long
     descriptions run under the box.) */
  .catalog-grid .catalog-item .ci-info { grid-area: info; min-width: 0; padding-right: 24px; }
  .catalog-grid .catalog-item .ci-title { font-size: 13px; margin-bottom: 1px; }
  .catalog-grid .catalog-item .ci-meta { font-size: 11px; }
  .catalog-grid .catalog-item .ci-actions {
    grid-area: actions; justify-content: flex-end; margin-left: 0;
  }
  .catalog-grid .catalog-item .ci-summary {
    -webkit-line-clamp: 1; line-clamp: 1;
    font-size: 11.5px; margin-bottom: 2px;
  }
  .catalog-grid .catalog-item .ci-hier,
  .catalog-grid .catalog-item .ci-peer { margin-top: 2px; }
  /* Batch checkbox: a custom appearance:none box drawn entirely in CSS so it
     renders identically across engines. A native checkbox scales with its
     width/height/padding on iOS Safari into a huge dark rounded square — the
     bug this replaces. 40px transparent hit area (tap target) centring an 18px
     visual box (::before), pinned to the card's top-right corner. The check
     stroke is theme-scoped: black on the bright dark-theme amber (#f59e0b),
     white on the dark light-theme amber (#9d4a08) — black-on-#9d4a08 was
     nearly invisible. */
  .catalog-grid .catalog-item .ci-select {
    appearance: none; -webkit-appearance: none;
    position: absolute; top: 0; right: 0; margin: 0; padding: 0;
    width: 40px; height: 40px; box-sizing: border-box;
    background: none; border: none; cursor: pointer;
    display: grid; place-content: center;
  }
  .catalog-grid .catalog-item .ci-select::before {
    content: ""; width: 18px; height: 18px; box-sizing: border-box;
    /* Unchecked contrast: a --border (#27272b) box on a --surface (#141416) card
       nearly vanished on-device. --text2 (#8a8a94) border + --surface2 (#1c1c20)
       fill (one step lighter than the card) reads clearly as an empty checkbox. */
    border: 2px solid var(--text2); border-radius: 4px; background: var(--surface2);
    transition: background 0.15s, border-color 0.15s;
  }
  /* Light theme: --surface2 is pure white, so the unchecked box vanished on the
     near-white card. Match the fixed-toggle family — a stronger --border-strong
     edge over a warm tinted fill — so the empty box clears the 3:1 contrast bar. */
  :root[data-theme="light"] .catalog-grid .catalog-item .ci-select::before {
    border-color: var(--border-strong); background: #e0dacd;
  }
  /* The light-theme unchecked rule above is more specific (extra :root[data-theme])
     than a bare :checked selector, which left checked boxes rendering as empty in
     light mode — hence the doubled selectors so checked/focus win in BOTH themes. */
  .catalog-grid .catalog-item .ci-select:checked::before,
  :root[data-theme="light"] .catalog-grid .catalog-item .ci-select:checked::before {
    border-color: var(--amber);
    background: var(--amber) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") center/12px no-repeat;
  }
  /* Light theme's --amber (#9d4a08) is a dark burnt orange — the black check
     above disappeared on it. Swap in a white stroke there (same geometry).
     Must come AFTER the doubled checked rule so equal specificity resolves
     by source order. */
  :root[data-theme="light"] .catalog-grid .catalog-item .ci-select:checked::before {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  }
  .catalog-grid .catalog-item .ci-select:focus-visible { outline: none; }
  .catalog-grid .catalog-item .ci-select:focus-visible::before,
  :root[data-theme="light"] .catalog-grid .catalog-item .ci-select:focus-visible::before {
    border-color: var(--amber); box-shadow: 0 0 0 3px var(--amber-glow);
  }
  [dir="rtl"] .catalog-grid .catalog-item .ci-select { right: auto; left: 0; }
  [dir="rtl"] .catalog-grid .catalog-item .ci-info { padding-right: 0; padding-left: 24px; }
  .mc-row-clickable:hover, .mc-row-clickable:focus-visible { color: var(--amber); }
  /* Caret hints the Updates line expands; nudges right on hover. */
  .mc-caret { vertical-align: -2px; margin-left: 4px; opacity: 0.7; transition: transform 0.12s; }
  .mc-row-clickable:hover .mc-caret { transform: translateX(2px); }
  /* The Updates count and its "Update all", side by side at the row's end. */
  .mc-updates-end { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
  .ms-pane .mc-updates-end .manage-btn-action { min-height: 28px; padding: 0 12px; font-size: 12px; }
  .mc-value .spinner-inline {
    display: inline-block; width: 12px; height: 12px; vertical-align: -1px; margin-right: 6px;
    border: 2px solid var(--border); border-top-color: var(--amber);
    border-radius: 50%; animation: spin 0.7s linear infinite;
  }
  .catalog-item {
    display: flex; gap: 14px; align-items: center; padding: 14px 16px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    margin-bottom: 6px; transition: border-color 0.2s, opacity 0.3s;
    max-height: 200px; overflow: hidden;
    -webkit-touch-callout: none;  /* long-press → context menu, not iOS callout (#37) */
  }
  /* Grid-cell catalog-items don't need a margin — the grid gap handles spacing */
  .catalog-grid .catalog-item { margin-bottom: 0; }
  .catalog-item:hover { border-color: var(--amber-border); }
  .catalog-item.ci-installed-item { opacity: 0.5; }
  .catalog-item.ci-covered { opacity: 0.6; }
  .ci-icon { flex-shrink: 0; width: 40px; height: 40px; border-radius: 8px; overflow: hidden; background: var(--surface2); }
  .ci-icon img { width: 34px; height: 34px; object-fit: cover; background: #fff; border-radius: 6px; padding: 2px; }
  .ci-icon .ci-letter { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; font-size: 16px; font-weight: 700; color: var(--accent); }
  .catalog-item .ci-info { flex: 1; min-width: 0; }
  .catalog-item .ci-title { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 3px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  .ci-lang-tag { font-size: 10px; font-weight: 600; padding: 1px 5px; border-radius: 4px; background: var(--surface2); color: var(--text2); border: 1px solid var(--border); text-transform: uppercase; letter-spacing: 0.06em; flex-shrink: 0; }
  .catalog-item .ci-summary { font-size: 12px; color: var(--text2); line-height: 1.4; margin-bottom: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .catalog-item .ci-meta { font-size: 12px; color: var(--text2); word-spacing: 0.1em; }
  .catalog-item .ci-meta > span { white-space: nowrap; }
  .catalog-item .ci-actions { flex-shrink: 0; display: flex; align-items: center; gap: 6px; margin-left: 8px; }
  .ci-installed-badge { font-size: 11px; color: var(--amber); font-weight: 600; padding: 4px 10px; border: 1px solid var(--amber-border); border-radius: 20px; background: var(--amber-glow); }
  .ci-delete-btn, .coll-item .coll-del {
    background: none; border: 1px solid transparent; color: var(--text2); opacity: 0.3;
    font-size: 16px; cursor: pointer; padding: 2px 6px; border-radius: 6px;
    transition: all 0.2s; line-height: 1;
  }
  .ci-delete-btn:hover, .coll-item .coll-del:hover { opacity: 1; color: var(--error); border-color: var(--error); }
  .ci-delete-btn.confirming { opacity: 1; color: var(--error); border-color: var(--error); font-size: 11px; padding: 4px 10px; }
  .ci-update-btn {
    font-size: 11px; font-weight: 600; padding: 4px 12px; cursor: pointer;
    color: #fff; background: var(--amber2); border: none; border-radius: 20px;
    transition: all 0.2s; white-space: nowrap;
  }
  .ci-update-btn:hover { background: var(--amber); }
  .ci-has-update { border-color: var(--amber-border); }
  /* Collection picker: selected member card + its checkmark badge */
  .catalog-item.ci-selected { border-color: var(--amber); background: var(--amber-glow); }
  .ci-coll-check { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; color: var(--amber); font-weight: 700; font-size: 15px; }
  .coll-picker { margin-top: 10px; }
  .ci-add-btn {
    padding: 6px 14px; font-size: 12px; font-weight: 600; cursor: pointer;
    background: var(--surface2); color: var(--text); border: 1px solid var(--border);
    border-radius: var(--radius); transition: all 0.2s;
    /* Centers the inline SVG download arrow with the label text */
    display: inline-flex; align-items: center; gap: 5px; justify-content: center;
  }
  /* Circular download progress indicator */
  .ci-dl-circle { background: none !important; border: none !important; padding: 2px !important; }
  .ci-dl-ring { position: relative; display: inline-flex; cursor: pointer; }
  .ci-dl-ring svg { display: block; }
  .ci-dl-x {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    font-size: 16px; font-weight: 600; color: var(--text2); opacity: 0; transition: opacity 0.15s;
  }
  .ci-dl-ring:hover .ci-dl-x { opacity: 1; }
  .ci-dl-ring:hover svg circle:last-child { opacity: 0.3; }
  .ci-variants { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; align-items: center; }
  .ci-dl-split { display: inline-flex; align-items: stretch; border-radius: var(--radius); overflow: hidden; }
  .ci-dl-split .ci-dl-main { border-radius: var(--radius) 0 0 var(--radius); border-right: none; }
  .ci-dl-split .ci-dl-chevron {
    display: flex; align-items: center; padding: 0 8px; cursor: pointer;
    background: var(--surface2); border: 1px solid var(--border); border-left: none;
    border-radius: 0 var(--radius) var(--radius) 0; color: var(--text2); transition: all 0.2s;
  }
  .ci-dl-split .ci-dl-chevron:hover { background: var(--amber-glow); color: var(--text); }
  .ci-variant-btn {
    padding: 3px 10px; font-size: 11px; font-weight: 500; cursor: pointer;
    background: var(--surface2); color: var(--text2); border: 1px solid var(--border);
    border-radius: 20px; transition: all 0.2s; white-space: nowrap;
  }
  /* Flavor picker pill + popup */
  .flavor-pill {
    display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 500;
    color: var(--text2); background: var(--surface2); border: 1px solid var(--border);
    border-radius: 20px; padding: 4px 10px; cursor: pointer; transition: all 0.2s;
    white-space: nowrap; position: relative;
    min-height: 24px;  /* WCAG 2.5.5 touch target */
  }
  .flavor-pill svg { opacity: 0.5; flex-shrink: 0; }
  .flavor-pill:hover svg { opacity: 0.8; }
  .flavor-popup {
    position: fixed; z-index: 1000; background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-popup); padding: 12px;
    min-width: 220px; max-width: 300px; display: flex; flex-direction: column;
  }
  .flavor-popup-title { font-size: 12px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
  .flavor-option { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 6px; cursor: pointer; transition: all 0.15s; border: 1px solid transparent; margin-bottom: 2px; }
  .flavor-option:hover { background: var(--surface2); border-color: var(--border); }
  .flavor-option.current { background: var(--amber-glow); border-color: var(--amber-border); cursor: default; }
  .flavor-option-label { flex: 1; font-size: 12px; font-weight: 500; color: var(--text); }
  .flavor-option-size { font-size: 11px; color: var(--text2); white-space: nowrap; }
  .flavor-option-check { width: 16px; color: var(--amber); font-weight: bold; text-align: center; flex-shrink: 0; }
  .flavor-popup-backdrop { position: fixed; inset: 0; z-index: 999; }
  .ci-section-label { padding: 16px 0 8px; }
  /* The count after a label: quiet, as the apps' .n and the home's pill-count. */
  .label-n { color: var(--text3); font-weight: 400; font-variant-numeric: tabular-nums; margin-inline-start: 2px; }
  /* min-width:0 — a grid item defaults to min-width:auto, so a wide action row
     used to stretch its column and push the whole page into horizontal scroll. */
  .dl-item { padding: 12px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); min-width: 0; font-size: 13px; }
  /* Wraps, so a long status moves to its own line instead of crushing the title
     down to an ellipsis. Seed cards use the same rule (they used to re-declare it). */
  .dl-row { display: flex; flex-wrap: wrap; column-gap: 10px; row-gap: 4px; align-items: center; }
  .dl-item .dl-name { flex: 1 1 150px; min-width: 0; color: var(--text); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dl-item .dl-size { color: var(--text2); flex-shrink: 0; }
  .dl-item .dl-done { color: var(--amber); font-weight: 600; }
  .dl-item .dl-error { color: var(--error); font-weight: 600; min-width: 0; }
  .dl-item .dl-scheduled { color: var(--text2); flex-shrink: 0; font-weight: 500; white-space: nowrap; }
  /* Download-scheduling card */
  .ms-dl-window { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 10px; }
  .ms-dl-window label { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text2); }
  .ms-dl-window input[type="time"] {
    background: var(--surface2); color: var(--text); border: 1px solid var(--border);
    border-radius: 6px; padding: 4px 6px; font-size: 13px;
  }
  .ms-dl-window input[type="time"]:disabled { opacity: 0.6; }
  .ms-dl-speed { margin-top: 12px; }
  .dl-window-chip { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
  .dl-window-chip.dl-window-in { color: var(--amber); background: var(--amber-glow); border: 1px solid var(--amber-border); }
  .dl-window-chip.dl-window-out { color: var(--text2); background: var(--surface2); border: 1px solid var(--border); }
  /* Backup & export hub */
  .ms-backup-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
  .ms-backup-import { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
  /* Import preview: a bordered diff summary the admin confirms before anything applies. */
  .ms-backup-preview {
    align-self: stretch; padding: 12px; border: 1px solid var(--border);
    border-radius: 8px; background: var(--surface2); display: flex; flex-direction: column; gap: 4px;
  }
  .ms-backup-pv-line { font-size: 13px; color: var(--text); }
  .ms-dl-trickle { margin-top: 8px; }
  .dl-progress { height: 4px; background: var(--surface2); border-radius: 2px; margin-top: 8px; overflow: hidden; }
  .dl-progress-bar { height: 100%; background: var(--amber); border-radius: 2px; transition: width 0.5s; }
  /* Two aligned columns: status badges/mirror on the left, buttons on the right,
     in every row whatever it contains. Both groups wrap internally, so a long
     label or a five-button cluster reflows inside the card instead of escaping
     it (the buttons used to overflow by up to 72px at 390px — issue #46). */
  .dl-actions {
    display: flex; flex-wrap: wrap; align-items: center;
    column-gap: 10px; row-gap: 6px; margin-top: 8px; min-width: 0;
  }
  .dl-meta, .dl-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
  .dl-meta { flex: 1 1 auto; }
  /* margin-left:auto (not justify-content) so the cluster hugs the right edge
     whether it shares the line with the chips or wraps onto one of its own —
     auto margins resolve per flex line, justify-content does not. */
  .dl-btns { flex: 0 0 auto; margin-left: auto; }
  /* Same auto-fit grid pattern as catalog so the two views feel consistent */
  .dl-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 6px;
  }
  .dl-filter-bar {
    display: flex; gap: 6px; flex-wrap: wrap;
    margin: 0 0 10px 0;
  }
  .dl-filter-pill {
    display: inline-flex; align-items: center; gap: 4px;
  }
  /* Top-level bulk controls (Pause all / Resume all / Delete all). Shares the
     download-row button shape; the danger variant flips to the error tone. */
  .dl-bulk-bar { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 10px 0; }
  .dl-bulk-btn.dl-bulk-danger:hover { border-color: var(--error); color: var(--error); }
  /* background:none beats .pill.active's amber gradient (same specificity,
     later in source) — amber label on amber fill was unreadable. */
  .dl-filter-pill.active { color: var(--amber); border-color: var(--amber); background: none; font-weight: 600; }
  .dl-filter-pill.active .pill-count { opacity: 1; background: var(--amber); color: var(--on-amber); padding: 1px 6px; border-radius: 8px; }
  /* Activity filter pills keep the base .pill treatment (amber fill when
     active) on purpose: they open DESELECTED like the Library view's category
     pills, so a lit pill is a filter the user chose — one or two at most. */
  .act-pill { flex: none; }
  /* One shape for EVERY button in the downloads panel — per-row controls, the
     bulk bar and the clear/show-all buttons alike. They had accreted three
     different shapes across features; the row buttons also missed
     font-family:inherit, so they rendered in the UA font (Arial) next to the
     bulk bar's app font. Fixed height + nowrap is what keeps them equal: a long
     label ("Switch to direct download") used to wrap to 49px against its 24px
     neighbours. Variants below may override colour ONLY — never the geometry. */
  .dl-pause-btn, .dl-cancel-btn, .dl-retry-btn, .dl-bulk-btn, .dl-clear-btn {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto; box-sizing: border-box; height: 26px; min-width: 62px; padding: 0 12px;
    background: none; border: 1px solid var(--border); color: var(--text2);
    border-radius: 999px; font-family: inherit; font-size: 11px; line-height: 1;
    white-space: nowrap; cursor: pointer; transition: all 0.15s;
  }
  .dl-pause-btn:disabled, .dl-cancel-btn:disabled, .dl-retry-btn:disabled,
  .dl-bulk-btn:disabled, .dl-clear-btn:disabled { opacity: 0.55; cursor: default; }
  .dl-pause-btn:not(:disabled):hover, .dl-bulk-btn:hover { border-color: var(--amber); color: var(--amber); }
  .dl-progress.dl-paused .dl-progress-bar { opacity: 0.5; }
  /* Total size unknown (BT metadata / finding peers) — sweeping indeterminate bar */
  .dl-progress.dl-indeterminate .dl-progress-bar {
    width: 30%; margin-left: -30%; transition: none;
    animation: dl-indet 1.3s ease-in-out infinite;
  }
  @keyframes dl-indet { to { margin-left: 100%; } }
  @media (prefers-reduced-motion: reduce) {
    /* Static centered segment — a full-width bar reads as "almost done" */
    .dl-progress.dl-indeterminate .dl-progress-bar { animation: none; width: 34%; margin-left: 33%; opacity: 0.6; }
  }
  .dl-cancel-btn:hover { border-color: var(--error); color: var(--error); }
  .dl-retry-btn:hover { border-color: var(--amber); color: var(--amber); }
  /* Ellipsises rather than forcing the row wide — mirror hostnames run long. */
  .dl-mirror {
    font-size: 11px; color: var(--text2); flex: 1 1 120px; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* BT card controls: always rendered (fixed height); dimmed + non-interactive
     when BT is off so a toggle never adds/removes rows and nothing jumps.
     A strict two-column grid (label | control) so every textbox lines up in a
     single column, however wide the label. Each .share-field goes
     display:contents so its <label> + control drop straight into this grid. */
  .share-bt-controls { margin-top: 8px; display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 7px 12px; align-items: center; }
  .share-bt-controls .share-field { display: contents; }
  .share-bt-controls .share-field > label { min-width: 0; }
  /* The controls share the card row with the toggle, so column 2 is narrow on a
     phone. The wide port group (input + dot + recheck + UPnP) wraps within its
     cell — its input stays top-left, aligned with the numeric inputs above,
     while the extras drop to a second line rather than overrun the card. */
  .share-bt-controls .share-port-group { flex-wrap: wrap; row-gap: 5px; }
  /* Server name fills its grid cell rather than a fixed 130-160px that would
     overrun the narrow phone column; border-box so padding stays inside. */
  .share-bt-controls .peer-name-input { width: 100%; box-sizing: border-box; }
  .share-bt-controls.share-controls-off { opacity: 0.4; }
  .share-bt-controls.share-controls-off .share-port-dot { opacity: 0.5; }
  /* Dim what is inert, not the whole row: the note that says WHY the row is
     inactive lives outside .share-row-dim and stays at full strength. */
  .share-inactive .share-row-dim,
  .share-inactive .share-row-right { opacity: 0.5; }
  .share-inactive .switch { cursor: default; }
  .share-port-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; font-size: 12px; color: var(--text2); flex-wrap: wrap; }
  .share-port-label { font-family: monospace; font-size: 11px; }
  /* Under BitTorrent's switch, with its status, in the right column. */
  .share-upnp { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-size: 12px; color: var(--text2); white-space: nowrap; }
  .share-inactive .share-upnp, .share-upnp:has(input:disabled) { opacity: 0.5; cursor: default; }
  /* Icon button matching the num-input chrome beside it (6px radius,
     surface fill) — an SVG reload glyph, centered, amber on hover. */
  .share-port-retry { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text2); cursor: pointer; transition: color 0.15s, border-color 0.15s; }
  .share-port-retry:hover { border-color: var(--amber); color: var(--amber); }
  .share-port-retry svg { display: block; }
  .share-port-retry.spinning svg { animation: share-spin 0.7s linear infinite; }
  @keyframes share-spin { to { transform: rotate(360deg); } }
  .share-port-note { font-family: monospace; font-size: 10px; color: var(--text3); }
  .seeding-snagged .seeding-name { color: var(--error); }
  .seeding-snag { color: var(--error); }
  .seeding-bar-snag { background: var(--error); opacity: 0.5; }
  .share-nearby-warn { color: var(--error); margin-top: 4px; }
  .share-rows-skeleton { min-height: 300px; }
  .share-row-skeleton { display: flex; flex-direction: column; gap: 8px; padding: 16px 0; border-bottom: 1px solid var(--border); }
  .share-row-skeleton .sk-line { height: 12px; border-radius: 4px; background: linear-gradient(90deg, var(--surface2) 25%, var(--border) 50%, var(--surface2) 75%); background-size: 200% 100%; animation: dc-shimmer 1.5s ease-in-out infinite; }
  .share-row-skeleton .sk-line-wide { width: 45%; }
  .share-row-skeleton .sk-line-narrow { width: 70%; height: 10px; opacity: 0.6; }
  /* (.share-row-text is declared with the sharing hero below, once.) */
  .share-bt-state { font-size: 12px; color: var(--text2); }
  .share-mirror-progress { margin-top: 6px; color: var(--amber); }
  /* Toggles pin to the top-right of tall cards; status tucks under.
     (A `.share-row { align-items: flex-start }` sat here and had never once
     applied — the sharing hero further down declares the same bare selector
     with align-items:center, same specificity and later in the file, so this
     one lost every time. The pinning that DOES happen is the right column's
     own flex-end below.) */
  .share-row-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; margin-top: 2px; }
  /* Shared status light for BT + Mirror — same spot (under each toggle,
     right column) so the two cards' dots line up. */
  /* The dot and the word sit on one line and must not break. The reason under
     them is a sentence and has to: as a plain child of an inline-flex row with
     nowrap it was laid out BESIDE the word and forbidden to wrap, so it ran
     off the panel (#74). flex-basis:100% gives it the next line instead. */
  .share-bt-status-right { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text2); white-space: nowrap; }
  .share-bt-status-right .share-port-dot { width: 8px; height: 8px; }
  /* Aligned label/control rows in the sharing cards */
  .share-field { display: flex; align-items: center; gap: 10px; margin-top: 7px; font-size: 12px; color: var(--text2); }
  .share-field > label { min-width: 78px; color: var(--text2); }
  .share-field-note { color: var(--text3); font-size: 11px; }
  /* One width for EVERY small numeric field in Settings — sized to the widest
     tenant, the 5-digit port (digits + padding + the number spinner arrows).
     The ratio/limit/connection boxes used to be a narrower 64px, which made
     the column read as two mismatched families; extra room on a 3-digit value
     is free. .share-port-input needs no width of its own anymore. */
  .share-num-input { width: 78px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-family: monospace; font-size: 12px; padding: 3px 6px; text-align: left; }
  /* Disabled (env-locked / BT-off) port + numeric fields: half-opacity turned the
     value into illegible grey-on-grey on paper. Keep the value readable with a
     muted colour on a recessed fill instead — still clearly inactive, but the
     number (e.g. an env-locked 16881) stays legible. */
  .share-num-input:disabled { opacity: 1; color: var(--text2); background: var(--bg); border-color: var(--border); cursor: not-allowed; }
  /* (.peer-name-input is styled with the sharing hero below. A second, older
     definition sat here asking for monospace at 12px in a 160px box; the later
     rule declares every one of those properties again — `font: inherit` covers
     the family and the size together — so none of it had ever rendered.) */
  /* Ring so the status dot stays visible even when its colour is a low-key grey
     (unknown / env-locked reachability) sitting on a same-tone field. */
  .share-port-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.32); }
  .dl-seed-link { cursor: pointer; }
  .dl-seed-link:hover { color: var(--amber); }
  /* Seed cards: icon + title on the first row, the "Sharing · …" status
     wraps to its own full-width row below so it never crushes the title.
     (The wrap itself now comes from the shared .dl-row rule.) */
  .dl-seed-icon { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 4px; overflow: hidden; background: var(--surface2); }
  .dl-seed-icon img { width: 22px; height: 22px; object-fit: contain; }
  .dl-seed-icon .rs-letter { font-size: 12px; font-weight: 600; color: var(--text2); }
  .dl-seed-item .dl-size { flex: 1 1 100%; min-width: 0; color: var(--text2); }
  /* Uploaded-vs-goal line under the seed row: quiet, tabular, its own row. */
  .dl-seed-goal { margin-top: 6px; font-size: 12px; color: var(--text2); font-variant-numeric: tabular-nums; }
  /* Seed age ("added 3 days ago"): quieter still — context, not data. */
  .dl-seed-age { color: var(--text3); }
  /* Seeds section header: title left, bulk actions (Pause all / Resume all /
     Remove all) top right — same reading order as the downloads bulk bar.
     Spans the card grid; wraps to two lines on phones instead of crushing. */
  /* Verifying after a restart: the bar is what has been read back, not what arrived. */
  .dl-progress.dl-checking .dl-progress-bar { background: var(--text3); }
  .dl-seed-head { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 6px 10px; }
  /* Seeds follow the downloads. A rule between the two keeps them from reading as one list. */
  .dl-seed-head:not(:first-child) { margin-top: 6px; padding-top: 14px; border-top: 1px solid var(--border); }
  .dl-seed-head-title { font-size: 13px; font-weight: 600; color: var(--accent); }
  .dl-seed-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
  /* What Remove means — files stay on disk. Quiet, its own grid row. */
  .dl-seed-hint { grid-column: 1 / -1; font-size: 11px; color: var(--text3); margin: -2px 0 2px; }
  /* Sharing cards on phones: text keeps its width, fields wrap cleanly */
  /* UPnP + dot + retry wrap as one unit, never leaving the retry circle
     stranded on its own line */
  .share-port-group { display: inline-flex; align-items: center; gap: 10px; white-space: nowrap; }
  @media (max-width: 600px) {
    .share-field { flex-wrap: wrap; row-gap: 5px; }
    .share-field > label { min-width: 62px; }
    .share-bt-controls { column-gap: 8px; }
    .share-row-right { gap: 6px; }
    .peer-name-input { width: 130px; }
    .share-port-group { gap: 8px; }
    .share-port-row { gap: 8px; }
    /* Whole numeric family narrows together on phones, keeping the one-width rule */
    .share-num-input { width: 70px; }
  }
  /* Rounded like every other status chip in the app (.dl-window-chip geometry)
     rather than the square 3px tag it used to be. */
  .dl-source { flex: 0 0 auto; font-size: 10px; font-weight: 600; padding: 2px 8px;
    border-radius: 999px; border: 1px solid var(--border);
    color: var(--text2); white-space: nowrap; line-height: 1.5; }
  .dl-source-bt { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  /* Delta-update salvage badge: a "savings" cue, distinct from the amber BT pill. */
  .dl-source-reuse { color: #4fd1a5; border-color: rgba(79,209,165,.35); background: rgba(79,209,165,.10); }
  .dl-clear-btn:hover { border-color: var(--amber); color: var(--amber); }
  /* Card header: title left, Clear right. Replaces a float + margin-top:-28px
     that lifted the button onto the h2's line and narrowed the filter bar
     (a flex container won't overlap a float). */
  .dl-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
  .dl-head h2 { margin: 0; }
  /* Same button, standing alone at the end of the Activity list. */
  .hist-show-all { display: flex; margin: 10px 0 0 auto; }

  /* ── Activity ──
     The unified journal list: every kind of change to the library in one
     place, each row saying who made it. Rows ARE catalog-items (same shell,
     same hover, same icon column) — everything here is the difference: the
     type glyph, the actor chip, and the two filter pill rows above the list. */
  .act-filters { margin-bottom: 12px; }
  .act-filter-row { display: flex; align-items: center; gap: 10px; }
  .act-filter-row + .act-filter-row { margin-top: 6px; }
  /* A fixed inline-start column so the two rows' pills line up under each
     other — ragged starts read as two unrelated controls rather than as two
     questions about the same list. */
  .act-filter-label {
    flex: none; width: 48px; font-size: 11px; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.06em; color: var(--text3);
  }
  /* Wraps rather than scrolls, unlike the language rows this borrows its shape
     from. Those hold dozens of pills and are meant to be swiped; these hold a
     handful and are a picture of the current filter — a pill clipped at the
     edge of a scroller is a filter state you cannot see without dragging. */
  .act-filter-pills { flex: 1; min-width: 0; flex-wrap: wrap; overflow: visible; row-gap: 6px; }
  .act-day + .act-day { margin-top: 18px; }
  .act-day .ci-section-label { margin-bottom: 8px; }
  /* The type glyph sits where a ZIM icon would, so rows line up whether or not
     the subject is still an installed source. */
  .act-icon { display: flex; align-items: center; justify-content: center; color: var(--text2); }
  .act-row:hover .act-icon { color: var(--amber); }
  .act-verb { font-weight: 600; color: var(--text); }
  .act-verb.bad { color: var(--error); }
  .act-detail { color: var(--text3); }
  /* A failed job's reason can be a full sentence. Let the activity row's meta
     wrap inside the card instead of running off its right edge (the catalog
     row it borrows from is single-line); min-width:0 lets the flex column
     actually shrink so the wrap takes effect. */
  .act-row .ci-info { min-width: 0; }
  .act-row .ci-meta { white-space: normal; overflow-wrap: anywhere; }
  .act-side { flex-direction: column; align-items: flex-end; gap: 4px; }
  .act-chip {
    font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 20px;
    border: 1px solid var(--border); background: var(--surface2); color: var(--text2);
    white-space: nowrap; max-width: 140px; overflow: hidden; text-overflow: ellipsis;
  }
  /* A person is the notable case — server work is the background hum. */
  .act-chip-user { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .act-chip-unknown { opacity: 0.6; }
  .act-time { font-size: 11px; color: var(--text3); white-space: nowrap; }
  @media (max-width: 700px) {
    .act-row { align-items: flex-start; }
    .act-side { gap: 3px; }
    /* The label goes above its pills on a phone: 48px of "WHO" beside a
       scrolling row leaves the pills about two thumbs wide. */
    .act-filter-row { display: block; }
    .act-filter-label { display: block; width: auto; margin-bottom: 4px; }
  }

  /* ── Manage tabs ── */
  .manage-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 16px; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .manage-tabs::-webkit-scrollbar { display: none; }
  .manage-tab {
    padding: 10px 20px; font-size: 13px; font-weight: 500; cursor: pointer;
    background: none; border: none; border-bottom: 2px solid transparent;
    color: var(--text2); transition: color 0.2s, border-color 0.2s; white-space: nowrap;
  }
  .manage-tab:hover { color: var(--text); }
  .manage-tab.active { color: var(--amber); border-bottom-color: var(--amber); font-weight: 600; }
  .dl-tab-badge {
    display: inline-block; margin-left: 6px; padding: 1px 7px;
    background: var(--amber); color: var(--on-amber); border-radius: 10px;
    font-size: 11px; font-weight: 600; line-height: 1.4; min-width: 8px; text-align: center;
  }
  .dl-empty {
    padding: 32px 16px; color: var(--text2); font-size: 13px;
    text-align: center; border: 1px dashed var(--border); border-radius: var(--radius);
  }
  .hot-zims-list {
    max-height: 280px; overflow-y: auto;
    border: 1px solid var(--border); border-radius: var(--radius);
    padding: 6px; background: var(--bg);
  }
  .hot-zims-row {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 8px; cursor: pointer; border-radius: 4px;
    font-size: 13px; color: var(--text);
  }
  .hot-zims-row:hover { background: var(--surface2); }
  .hot-zims-row input[type="checkbox"] { accent-color: var(--amber); flex-shrink: 0; }
  .hot-zims-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .hot-zims-id { color: var(--text2); font-size: 11px; font-family: monospace; flex-shrink: 0; }
  .hot-zims-toolbar { display: flex; gap: 6px; margin-bottom: 8px; align-items: center; flex-wrap: wrap; }
  /* Settings is a regular first tab on every viewport — the card only
     shows while its tab is active. */
  .manage-settings { display: none; margin-top: 12px; }
  .manage-settings.as-tab-active { display: flex; }

  /* ── Sharing hero (v1.7's star): three switch rows, one quiet card ── */
  .share-rows {
    border: 1px solid var(--border); border-radius: 12px;
    background: var(--bg); overflow: hidden; margin-top: 8px;
  }
  .share-row { display: flex; align-items: center; gap: 14px; padding: 13px 14px; }
  .share-row + .share-row { border-top: 1px solid var(--border); }
  .share-row-text { flex: 1; min-width: 0; }
  .share-row-title { font-size: 13px; font-weight: 600; color: var(--text); letter-spacing: 0.01em; }
  .share-row-desc { font-size: 12px; color: var(--text2); margin-top: 2px; line-height: 1.45; }
  .share-row-locknote { color: var(--amber); opacity: 0.8; }
  /* Merge data: the replace warning shows only while the switch is off. */
  .merge-row .share-row-desc { display: none; color: var(--error); }
  .merge-row.merge-off .share-row-desc { display: block; }
  .share-locked .share-row-title, .share-locked .share-row-desc:not(.share-row-locknote) { opacity: 0.55; }
  .share-ratio { white-space: nowrap; }
  .share-ratio input {
    width: 46px; margin: 0 3px; padding: 2px 4px; font-size: 12px; text-align: center;
    background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text);
  }
  .share-ratio input:focus { outline: none; border-color: var(--amber); }
  .share-ratio input:disabled { opacity: 0.45; }
  .peer-name-input {
    width: 150px; padding: 2px 6px; font: inherit; text-align: left;
    background: var(--surface2); border: 1px solid var(--border);
    border-radius: 6px; color: var(--text);
  }
  .peer-name-input:focus { outline: none; border-color: var(--amber); }
  .peer-name-input:disabled { opacity: 0.55; border-color: transparent; background: none; }
  .share-ratio-note { color: var(--text2); opacity: 0.75; font-size: 11px; margin-left: 4px; }
  /* iOS-style switch: amber track when on, black knob slides right */
  .switch { position: relative; flex-shrink: 0; width: 42px; height: 24px; display: inline-block; cursor: pointer; }
  .switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
  .switch-slider {
    position: absolute; inset: 0; border-radius: 999px;
    background: var(--surface2); border: 1px solid var(--border);
    transition: background 0.18s, border-color 0.18s;
  }
  .switch-slider::before {
    content: ''; position: absolute; width: 18px; height: 18px; border-radius: 50%;
    top: 2px; left: 2px; background: var(--text2);
    transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), background 0.18s;
  }
  .switch input:checked + .switch-slider {
    background: linear-gradient(135deg, var(--amber), var(--amber2)); border-color: transparent;
  }
  .switch input:checked + .switch-slider::before { transform: translateX(18px); background: #000; }
  .switch input:focus-visible + .switch-slider { outline: 2px solid var(--amber); outline-offset: 2px; }
  .switch input:disabled, .switch input:disabled + .switch-slider { cursor: default; }
  .set-row .switch input:disabled + .switch-slider { opacity: 0.5; }
  /* The small one, inline beside a field (UPnP by the port). */
  .switch-sm { width: 32px; height: 18px; }
  .switch-sm .switch-slider::before { width: 12px; height: 12px; }
  .switch-sm input:checked + .switch-slider::before { transform: translateX(14px); }
  @media (prefers-reduced-motion: reduce) {
    .switch-slider, .switch-slider::before { transition: none; }
  }
  /* Light theme: the OFF track (--surface2 = #fff) vanishes on a near-white card
     (1.04:1) — the maintainer flagged the BitTorrent switch. Give it a warm-grey
     fill with an AA outline (--border-strong, 3.63:1) so it reads as a control,
     and flip the ON knob to white since the deep-amber ON track needs light ink
     (#000 knob would sink to ~3.6:1). The OFF knob stays --text2, which clears
     3:1 on the grey track (4.06:1). */
  :root[data-theme="light"] .switch-slider,
  :root[data-theme="light"] .share-controls-off .switch input:checked + .switch-slider {
    background: #e0dacd; border-color: var(--border-strong);
  }
  :root[data-theme="light"] .switch input:checked + .switch-slider {
    border-color: transparent;
  }
  :root[data-theme="light"] .switch input:checked + .switch-slider::before {
    background: var(--on-amber);
  }
  /* A switch in a card that is off (BitTorrent's UPnP) keeps its place, what
     it will be when the card is on, but not its amber: amber says "on now",
     and nothing here is. It takes the OFF track and knob instead (the light
     theme's track is in the rule above). */
  .share-controls-off .switch input:checked + .switch-slider { background: var(--surface2); border-color: var(--border); }
  .share-controls-off .switch input:checked + .switch-slider::before,
  :root[data-theme="light"] .share-controls-off .switch input:checked + .switch-slider::before { background: var(--text2); }

  /* Long option lists (languages, hot ZIMs) hide behind a Show-list toggle.
     :not() + !important beat later same-specificity display rules
     (a list with a display of its own further down); when open, the element
     keeps its natural display. */
  .ms-collapsed-list:not(.ms-open) { display: none !important; }
  /* Each sharing toggle owns its hint — indent under the checkbox text so
     they read as one unit, with clear space before the next toggle. */
  .ms-toggle-block { margin-bottom: 14px; }
  .ms-toggle-block .ms-check { padding-bottom: 0; }
  .ms-toggle-block .ms-hint { margin: 2px 0 0 26px; }

  .hot-zims-search {
    flex: 1; min-width: 0; padding: 6px 10px;
    background: var(--bg); border: 1px solid var(--border-strong); border-radius: var(--radius);
    color: var(--text); font-size: 13px;
  }
  .hot-zims-search:focus { outline: none; border-color: var(--amber); }
  .hot-zims-actions { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
  /* Settings > Languages: one card. Show languages, then (while on) the fold
     ("Languages: English, Español") and, open, Zimi's ten interface
     languages, each one row: a check, its own name and Zimi's name for it.
     Zimi's own is checked and locked, tagged In use. */
  .lang-fold { width: 100%; border: 0; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
  .lang-fold[hidden] { display: none; }
  .lang-fold:hover .share-row-title, .lang-fold:focus-visible .share-row-title { color: var(--amber); }
  .lang-fold:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
  .lang-chev { flex: none; width: 14px; font-size: 18px; line-height: 1; color: var(--text2); text-align: center; transition: transform 0.15s; }
  .lang-fold[aria-expanded="true"] > .lang-chev { transform: rotate(90deg); }
  [dir="rtl"] .lang-fold[aria-expanded="false"] > .lang-chev { transform: scaleX(-1); }
  .lang-list { border-top: 1px solid var(--border); }
  .lang-list[hidden] { display: none; }
  .lang-pick {
    width: 100%; min-height: 52px; box-sizing: border-box; gap: 12px; padding: 8px 14px;
    border: 0; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer;
  }
  .lang-pick:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
  .lang-pick .share-row-text { flex: 1; min-width: 0; }
  .lang-pick .share-row-title { font-weight: 500; font-size: 14px; unicode-bidi: isolate; }
  .lang-pick .share-row-title, .lang-pick .share-row-desc { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .lang-check {
    flex: none; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
    box-sizing: border-box; border: 1.5px solid var(--text3); border-radius: 5px; color: var(--on-amber);
  }
  .lang-pick.mine .lang-check { background: var(--amber); border-color: var(--amber); }
  .lang-pick:not([aria-disabled]):hover .lang-check { border-color: var(--amber); }
  .lang-pick[aria-disabled] { cursor: default; }
  .lang-pick[aria-disabled] .lang-check { opacity: 0.55; }
  .lang-dl { flex-basis: 100%; padding-inline-start: 30px; margin-top: -4px; }
  .lang-dl .dl-progress { margin-top: 4px; height: 3px; }
  .set-btn.confirming { color: var(--error); border-color: var(--error); background: none; }
  /* Settings > Voices' grid: a row per language, a column per flavor. A dot
     is green where the flavor says the language here, grey where it cannot;
     a download arrow (an admin's) gets it; a downloaded Clear voice's dot
     shows ✕ on hover, focus or a first tap, and ✕ removes it. */
  .vg { margin-top: 12px; }
  .vg-row {
    display: grid; grid-template-columns: minmax(0, 1fr) repeat(var(--vg-n), 44px); align-items: center;
    min-height: 44px; padding: 0 8px 0 14px; box-sizing: border-box;
  }
  [dir="rtl"] .vg-row { padding: 0 14px 0 8px; }
  .vg-row + .vg-row, .vg-all, .vg-all + .vg-row { border-top: 1px solid var(--border); }
  .vg-head { min-height: 32px; }
  .vg-h { font-size: 10.5px; font-weight: 600; color: var(--text2); text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .vg-name { min-width: 0; padding: 6px 0; }
  .vg-own, .vg-zimi { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .vg-own { font-size: 14px; font-weight: 500; unicode-bidi: isolate; }
  .vg-zimi { font-size: 11.5px; color: var(--text3); }
  .vg-cell { display: flex; align-items: center; justify-content: center; height: 40px; }
  .vg-cell.off { opacity: 0.45; }
  .vg-dot { display: block; width: 10px; height: 10px; border-radius: 50%; background: var(--success); }
  .vg-dot[data-s="none"] { width: 6px; height: 6px; background: var(--text3); opacity: 0.45; }
  .vg-dot[data-s="busy"] { background: var(--amber); animation: vg-pulse 1s ease-in-out infinite alternate; }
  @keyframes vg-pulse { to { opacity: 0.35; } }
  .vg-btn {
    position: relative; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px;
    padding: 0; border: 0; border-radius: 50%; background: none; color: var(--amber); cursor: pointer;
  }
  .vg-btn:disabled { color: var(--text3); cursor: default; }
  .vg-get:not(:disabled):hover { background: var(--amber-glow); }
  .vg-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 0; }
  .vg-x { display: none; font-size: 13px; font-weight: 700; color: var(--error); }
  .vg-rm:hover .vg-x, .vg-rm:focus-visible .vg-x, .vg-rm.show-x .vg-x { display: block; }
  .vg-rm:hover .vg-dot, .vg-rm:focus-visible .vg-dot, .vg-rm.show-x .vg-dot { display: none; }
  .vg-rm:hover, .vg-rm.show-x { background: var(--surface2); }
  .vg-dl { display: flex; align-items: center; gap: 10px; padding: 0 14px 10px; margin: 0; }
  .vg-dl .voice-dl { flex: 1; min-width: 0; }
  .vg-dl .set-btn { --set-ctl-h: 30px; padding: 0 10px; font-size: 12px; }
  .vg-all .share-row-title { font-weight: 500; color: var(--text2); }
  .ms-hot-show { margin-top: 6px; }
  .updates-detail {
    margin: 6px 0 12px 0; padding: 8px;
    background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius);
    max-height: 240px; overflow-y: auto;
  }
  .updates-detail-row {
    padding: 6px; font-size: 12px;
  }
  .updates-detail-row + .updates-detail-row { border-top: 1px solid var(--border); }
  .updates-detail-top {
    display: flex; justify-content: space-between; align-items: center; gap: 8px;
  }
  .updates-detail-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .updates-detail-versions { color: var(--text2); font-family: monospace; font-size: 11px; flex-shrink: 0; }
  .updates-detail-fnames {
    color: var(--text2); font-family: monospace; font-size: 10.5px;
    margin-top: 2px; opacity: 0.65; word-break: break-all;
  }

  /* Floating multi-select action bar (the checkbox itself is fully styled in
     the .catalog-grid rule above — appearance:none, so no accent-color here). */
  .ci-selection-bar {
    position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
    background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
    padding: 8px 14px; display: flex; gap: 12px; align-items: center;
    box-shadow: 0 8px 24px rgba(0,0,0,0.4); z-index: 100;
    font-size: 13px; color: var(--text);
  }
  .ci-selection-bar .ci-sel-count { font-weight: 500; }
  .ci-selection-bar button { white-space: nowrap; }

  /* Catalog hierarchy hints */
  .ci-hier { margin-top: 4px; font-size: 11px; }
  /* LAN peer pill — green for "this exists on a LAN peer" */
  .ci-peer { margin-top: 4px; font-size: 11px; }
  .ci-peer-pill {
    /* flex + gap gives real space after the 📡 (the emoji glyph box
       swallows a plain text space, and the dish leans right — needs 8px) */
    display: inline-flex; align-items: center; gap: 8px;
    padding: 2px 8px; border-radius: 10px;
    font-size: 11px; line-height: 1.4;
    background: rgba(120, 200, 120, 0.12); color: var(--success);
    border: 1px solid rgba(120, 200, 120, 0.3);
    /* Touch target — bumped to 24px for clickable variant; the static
       (already-installed) variant inherits the smaller size. */
  }
  .ci-peer-pill-clickable {
    cursor: pointer; min-height: 24px; padding: 4px 10px;
    font: inherit; font-size: 11px; transition: all 0.15s;
  }
  .ci-peer-pill-clickable:hover {
    background: rgba(120, 200, 120, 0.22);
    border-color: rgba(120, 200, 120, 0.55);
    color: var(--success-strong);
  }
  .ci-peer-pill-clickable:focus-visible {
    outline: 2px solid var(--success); outline-offset: 2px;
  }
  .ci-hier-installed,
  .ci-hier-subset,
  .ci-hier-bundle {
    display: inline-block; padding: 2px 8px; border-radius: 10px;
    font-size: 11px; line-height: 1.4;
  }
  .ci-hier-installed {
    background: rgba(120, 200, 120, 0.12); color: var(--success);
    border: 1px solid rgba(120, 200, 120, 0.3);
  }
  .ci-hier-subset {
    background: var(--surface2); color: var(--text2); border: 1px solid var(--border);
  }
  .ci-hier-bundle {
    background: var(--amber-glow); color: var(--amber); border: 1px solid var(--amber-border);
  }
  .ci-hier-coverage {
    display: inline-block; padding: 2px 8px; border-radius: 10px;
    font-size: 11px; line-height: 1.4;
    background: rgba(120, 200, 120, 0.08); color: var(--success);
    border: 1px solid rgba(120, 200, 120, 0.25);
  }
  .ci-hier-freshness {
    display: inline-block; padding: 2px 8px; border-radius: 10px;
    font-size: 11px; line-height: 1.4;
    background: rgba(255, 180, 80, 0.1); color: #ffb450;
    border: 1px solid rgba(255, 180, 80, 0.3);
  }
  .manage-tab-content { display: none; }
  .manage-tab-content.active { display: block; }
  .manage-installed-group { margin-bottom: 24px; }
  .manage-installed-group .ci-section-label { margin-top: 0; }
  .mig-updates .ci-section-label { color: var(--amber); }
  /* Drag-to-move (#37): the row being dragged dims; a section header lights up
     while it's a valid drop target. Both tokens theme-track (light + dark). */
  .catalog-item.ci-dragging { opacity: 0.4; }
  .ci-section-label[data-cat] { border-radius: var(--radius); transition: color 0.1s, background 0.1s, box-shadow 0.1s; }
  .ci-section-label.drop-target {
    color: var(--amber);
    background: var(--amber-glow);
    box-shadow: inset 0 0 0 1px var(--amber-border);
  }

  /* BT seeding panel in Server settings */
  .seeding-totals { font-size: 12px; color: var(--text2); margin-bottom: 8px; }
  .seeding-list { display: flex; flex-direction: column; gap: 6px; }
  .seeding-row {
    padding: 8px 10px; background: var(--bg);
    border: 1px solid var(--border); border-radius: var(--radius);
  }
  .seeding-name {
    font-size: 12px; font-family: monospace; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .seeding-meta {
    font-size: 11px; color: var(--text2); margin-top: 2px;
  }
  .seeding-bar {
    height: 4px; background: var(--surface2); border-radius: 2px;
    margin-top: 6px; overflow: hidden;
  }
  .seeding-bar-fill {
    height: 100%; background: var(--amber); transition: width 0.3s;
  }

  /* ── Featured Living Carousel ── */
  .featured-section { margin-bottom: 20px; }
  .featured-card {
    flex: 1 1 calc(50% - 4px); min-width: 200px; max-width: 100%;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    overflow: hidden; display: flex; align-items: center; gap: 10px;
    padding: 10px 14px; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .featured-card:hover { border-color: var(--amber-border); background: var(--surface2); transform: translateY(-1px); box-shadow: 0 4px 24px var(--amber-glow); }
  .browse-footer { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 20px 0 4px; color: var(--text2); font-size: 13px; }
  .browse-footer-count { text-align: center; }
  .browse-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 10px; }
  .browse-cat-card {
    display: flex; gap: 14px; align-items: stretch;
    padding: 14px; cursor: pointer;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); transition: background 0.2s, border-color 0.2s, transform 0.2s;
  }
  .browse-cat-card:hover { border-color: var(--amber-border); background: var(--surface2); transform: translateY(-2px); }
  .browse-cat-card.dimmed { opacity: 0.3; pointer-events: none; }
  .bcc-icon {
    width: 48px; height: 48px; color: var(--amber);
    display: flex; align-items: center; justify-content: center;
    background: var(--surface2); border-radius: 12px; flex-shrink: 0;
    align-self: center;
  }
  .bcc-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  .bcc-name { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 2px; }
  .bcc-desc { font-size: 12px; color: var(--text2); line-height: 1.4; }
  .bcc-count { font-size: 11px; color: var(--text2); opacity: 0.6; margin-top: auto; }
  .bcc-arrow { font-size: 20px; color: var(--text2); opacity: 0.3; flex-shrink: 0; transition: all 0.2s; align-self: center; }
  .browse-cat-card:hover .bcc-arrow { color: var(--amber); opacity: 1; }
  .browse-drilldown-header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
  .browse-back { background: none; border: none; color: var(--amber); font-size: 13px; cursor: pointer; padding: 4px 0; font-weight: 500; }
  .browse-back:hover { text-decoration: underline; }
  .browse-drilldown-title { font-size: 15px; font-weight: 600; color: var(--accent); }
  .browse-drilldown-icon { margin-inline-end: 8px; display: inline-flex; vertical-align: middle; color: var(--amber); }
  .browse-drilldown-icon svg { width: 20px; height: 20px; }
  .ci-pin { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; color: var(--amber); }
  .ci-pin svg { width: 24px; height: 24px; }
  .browse-drilldown-count { font-size: 12px; color: var(--text2); margin-inline-start: auto; }
  /* ── Search operators: chips and the "?" ──
     A chip says what one operator did ("without ted", "French"); its ×
     searches again without it. The same chip, minus the ×, explains each
     example behind the "?". Logical properties, so right-to-left mirrors. */
  .search-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
  .search-chip {
    display: inline-flex; align-items: center; gap: 2px; box-sizing: border-box; max-width: 100%;
    min-height: 28px; padding-block: 2px; padding-inline: 12px 2px;
    font-size: 12px; line-height: 1.25; color: var(--text);
    background: var(--amber-glow); border: 1px solid var(--amber-border); border-radius: 20px;
  }
  .search-chip.search-chip-static { padding-inline-end: 12px; }
  .search-chip-label { min-width: 0; overflow-wrap: anywhere; }
  .search-chip-x, .sg-forget, .find-step, .find-close {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 24px; height: 24px; padding: 0; border: none; border-radius: 50%;
    background: transparent; color: var(--text2); cursor: pointer; transition: color 0.12s, background 0.12s;
  }
  .search-chip-x:hover, .search-chip-x:focus-visible, .sg-forget:hover, .sg-recent-clear:hover,
  .find-step:hover:not(:disabled), .find-step:focus-visible, .find-close:hover, .find-close:focus-visible { color: var(--amber); background: var(--amber-glow); }
  /* Recent in the empty search dropdown: each row a × away from gone, the
     searches all at once from the header. */
  .sg-recent-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding-block: 6px 2px; padding-inline: 14px 8px;
    font-size: 11px; color: var(--text2); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  }
  .sg-recent-clear {
    padding: 2px 8px; border: none; border-radius: 10px; background: transparent; cursor: pointer;
    font: inherit; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--text2);
    transition: color 0.12s, background 0.12s;
  }
  .suggest-item.sg-recent { display: flex; align-items: center; gap: 8px; padding-inline-end: 8px; }
  .sg-recent-text { flex: 1; min-width: 0; }
  .sg-forget { opacity: 0.55; }
  .suggest-item:hover .sg-forget, .suggest-item.active .sg-forget { opacity: 1; }
  .topbar-search .search-help {
    display: none; position: absolute; inset-inline-end: 34px; top: 50%; transform: translateY(-50%);
    align-items: center; justify-content: center; box-sizing: border-box;
    width: 22px; height: 22px; padding: 0; border: 1px solid var(--border); border-radius: 50%;
    background: transparent; color: var(--text2); font: 600 12px/1 inherit; cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
  }
  .topbar-search.has-help .search-help { display: flex; }
  .topbar-search .search-help:hover, .topbar-search .search-help[aria-expanded="true"] { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .topbar-search.has-help input { padding-inline-end: 62px; }
  .search-tips-head { padding: 8px 14px 4px; font-size: 12px; font-weight: 600; color: var(--text2); }
  .search-example { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
  .search-example-q { color: var(--text); font-weight: 500; }

  /* ── Mobile ── */
  /* ── Footer ── */
  .footer-sub { margin-top: 6px; font-size: 11px; opacity: 0.7; }
  /* WCAG 1.4.1: links inside text need a non-color cue. Using a 1px
     dotted underline keeps the visual difference subtle but present. */
  .footer a { color: var(--amber); text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 2px; }
  .footer a:hover { text-decoration-style: solid; }
  .kbd-hints { opacity: 0.6; }

  /* ── Mobile ── */
  @media (max-width: 600px) {
    .topbar { padding: 0 10px; gap: 6px; }
    .topbar .logo { font-size: 16px; }
    .bc-icon img { width: 18px; height: 18px; }
    .bc-icon .bc-letter { width: 18px; height: 18px; font-size: 10px; }
    .stats-grid { grid-template-columns: 1fr; }
    .browse-grid { grid-template-columns: 1fr; }
    .container { padding: 20px 12px; }
    .topbar-search .kbd { display: none; }
    /* A phone's box is narrow, so the "?" costs it nothing: it takes the ×'s
       place while the box is empty, sits beside the × while you type, and
       steps aside while a search is shown, whose chips say what it did. */
    .topbar-search.has-help input:placeholder-shown,
    .topbar-search.has-help input:not(:focus) { padding-inline-end: 36px; }
    .topbar-search input:placeholder-shown ~ .search-help { inset-inline-end: 8px; }
    .topbar-search input:not(:placeholder-shown):not(:focus) ~ .search-help { display: none; }
    .kbd-hints { display: none; }
    .manage-card { padding: 16px 12px; }
    .manage-settings { flex-direction: column; min-height: auto; }
    .ms-pane { overflow-x: hidden; min-width: 0; }
    .hot-zims-id { display: none; }
    .ms-nav { width: auto; flex-shrink: 0; border-right: none; border-bottom: 1px solid var(--border); flex-direction: row; padding: 4px 8px; gap: 2px; overflow-x: auto; }
    .ms-nav-item { padding: 6px 12px; white-space: nowrap; border-radius: var(--radius); font-size: 12px; }
    .ms-pane { padding: 14px 12px; }
    .manage-tab { padding: 8px 14px; font-size: 12px; }
    body { overflow-x: hidden; }
    /* Mobile: hide secondary actions, show overflow menu.
       The + joins them rather than keeping an inline slot. Creating a ZIM is a
       deliberate, occasional, admin-only act; search is why the topbar exists
       and it is what a phone should give its width to. Same reasoning that put
       Random, Language and Manage in here — and the ⋯ menu row it becomes is
       reachable in one extra tap, which is the right price for an action you
       take once a week. */
    .random-btn, .lang-selector-btn, .manage-btn, .create-btn { display: none !important; }
    /* Reader controls (font size, read-aloud, pop-out) migrate into the ...
       menu on mobile — the topbar is too tight to hold them inline. */
    #font-btn, #tts-btn, #readerview-btn, #newtab-btn { display: none !important; }
    .topbar-more { display: flex !important; }
    /* ...except on Create, which has no ⋯ at any width (#68). The way out is
       the Zimi breadcrumb here, the X on desktop; everything the menu held is
       on the other side of it. Needs !important of its own to outrank the
       line above, which an inline style cannot. */
    body.creating .topbar-more { display: none !important; }
    .topbar.search-focused .create-btn,
    .topbar.search-focused .topbar-more,
    .topbar.search-focused .library-btn,
    .topbar.search-focused #history-btn,
    .topbar.search-focused #bm-panel-btn { display: none !important; }
    /* On a map the picker takes the panel openers' place (it keeps the
       map's places); H and B still open History and Saved. */
    body.map-page #bm-panel-btn, body.map-page #history-btn { display: none !important; }
    .topbar.search-focused .lang-dropdown { display: none !important; }
    /* v1.6: Language dropdown — full width on mobile */
    .lang-dropdown { min-width: 160px; }
    /* Catalog checkbox is engine-independent now (custom CSS box, sized in the
       base .catalog-grid rule) — no mobile size override needed; the base
       .ci-info corner reserve covers phones too. */
    /* Selection bar — a single-row pill centred above the tab bar, never
       wrapping into a blob that overlaps cards. */
    .ci-selection-bar {
      left: 12px; right: 12px; transform: none; margin: 0 auto;
      max-width: 420px; flex-wrap: nowrap; gap: 8px; padding: 8px 12px;
      justify-content: center;
      bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    }
    .ci-selection-bar .ci-sel-count {
      min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .ci-selection-bar button { flex-shrink: 0; }
  }

  /* ── Password modal ── */
  .pw-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:200; align-items:center; justify-content:center; }
  .pw-overlay.open { display:flex; }
  /* Login gate (private public-access mode): the overlay becomes fully opaque
     so the anonymous visitor sees nothing but the login card — no half-loaded
     home showing through. Highest layer, and the background page can't scroll. */
  body.login-gate { overflow:hidden; }
  body.login-gate .pw-overlay { background:var(--bg); z-index:1000; }
  .pw-box { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:24px; width:320px; max-width:90vw; }
  .pw-box h3 { margin:0 0 12px; font-size:15px; color:var(--text); }
  /* 16px minimum — iOS Safari auto-zooms (and stays zoomed) on focus of any
     input smaller than 16px. */
  .pw-box input[type="password"], .pw-box input[type="text"] { width:100%; padding:8px 10px; border-radius:var(--radius); border:1px solid var(--border-strong); background:var(--bg); color:var(--text); font-size:16px; box-sizing:border-box; }
  .pw-box input[type="password"]:focus, .pw-box input[type="text"]:focus { outline:none; border-color:var(--amber); }
  .pw-box input#pw-username { margin-bottom:8px; }
  .pw-box .pw-actions { display:flex; gap:8px; margin-top:12px; justify-content:flex-end; }
  .pw-box button { padding:6px 14px; border-radius:var(--radius); border:1px solid var(--border); background:var(--surface2); color:var(--text); cursor:pointer; font-size:13px; }
  .pw-box button.pw-primary { background:linear-gradient(135deg, var(--amber), var(--amber2)); color: var(--on-amber); border-color:transparent; font-weight:600; }
  .pw-box button.pw-primary:hover { opacity:0.85; }
  .pw-box .pw-error { color:var(--error); font-size:12px; margin-top:8px; display:none; }

  /* ── Desktop: onboarding + settings overlays ── */
  .desktop-overlay {
    display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.75);
    z-index: 500; align-items: center; justify-content: center;
    backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  }
  .desktop-overlay.open { display: flex; }
  .desktop-card { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 32px; width: 480px; max-width: 90vw; box-shadow: 0 24px 80px rgba(0,0,0,0.5); }
  .desktop-card h2 {
    font-size: 22px; font-weight: 700; margin: 0 0 8px;
    background: linear-gradient(135deg, #f59e0b, #f97316);
    -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
  }
  .desktop-card .subtitle { font-size: 13px; color: var(--text2); line-height: 1.5; margin-bottom: 24px; }
  .desktop-card label { display: block; font-size: 12px; font-weight: 600; color: var(--text2); margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.06em; }
  .desktop-card .field-row { display: flex; gap: 8px; margin-bottom: 16px; }
  .desktop-card input[type="text"], .desktop-card input[type="number"] {
    flex: 1; padding: 9px 12px; background: var(--bg); border: 1px solid var(--border-strong);
    border-radius: var(--radius); color: var(--text); font-size: 14px; font-family: inherit;
  }
  .desktop-card input[type="text"]:focus, .desktop-card input[type="number"]:focus { outline: none; border-color: var(--amber-border); box-shadow: 0 0 0 3px var(--amber-glow); }
  .desktop-card input[readonly] { color: var(--text2); cursor: default; }
  .desktop-card .hint { font-size: 11px; color: var(--text2); opacity: 0.6; margin-top: -10px; margin-bottom: 16px; }
  .desktop-card .btn-row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }
  .desktop-card .btn-primary { padding: 9px 24px; font-size: 14px; font-weight: 600; transition: opacity 0.2s; }
  .desktop-card .btn-secondary {
    padding: 9px 18px; font-size: 13px; cursor: pointer;
    background: var(--surface2); color: var(--text); border: 1px solid var(--border);
    border-radius: var(--radius); transition: all 0.2s;
  }
  .desktop-card .btn-secondary:hover { border-color: var(--amber-border); }
  .desktop-card .btn-browse {
    padding: 9px 14px; font-size: 13px; cursor: pointer; white-space: nowrap;
    background: var(--surface2); color: var(--text); border: 1px solid var(--border);
    border-radius: var(--radius); transition: all 0.2s; flex-shrink: 0;
  }
  .desktop-card .btn-browse:hover { border-color: var(--amber-border); color: var(--amber); }
  .settings-restart-msg {
    display: none; padding: 10px 14px; margin-top: 12px;
    background: rgba(245, 158, 11, 0.08); border: 1px solid var(--amber-border);
    border-radius: var(--radius); font-size: 13px; color: var(--amber);
    text-align: center;
  }
  .settings-restart-msg.show { display: block; }

  /* ── Card category badge (inline in detail row) ── */
  .card-cat { font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; }
  /* The age the list is currently ordered by. Deliberately the same colour as
     the rest of the detail line: it is one more fact about the file, not a
     highlight, and amber here read as a warning (Eric: "don't love that the
     age is yellow"). Tabular figures so a column of ages lines up. */
  .card-when { font-variant-numeric: tabular-nums; }
  .lang-badge { display: inline-block; font-size: 10px; font-weight: 600; padding: 1px 5px; margin-left: 6px; border-radius: 4px; background: var(--surface2); color: var(--text2); vertical-align: middle; letter-spacing: 0.06em; }
  .lang-badge.multi { background: var(--amber-glow); color: var(--amber); border: 1px solid var(--amber-border); }
  /* Downloads/seeds row chip: distinguishes identically-titled transfers. */
  .dl-lang-badge { flex-shrink: 0; }
  .qid-badge { display: inline-block; margin-left: 5px; vertical-align: middle; color: var(--text3); opacity: 0.5; }

  /* ── Star (favorite) toggle ── */
  .star-btn {
    position: absolute; top: 4px; right: 4px; z-index: 2;
    background: none; border: none; cursor: pointer; font-size: 18px; line-height: 1;
    color: var(--text2); opacity: 0.3; transition: all 0.2s;
    /* WCAG 2.5.5 minimum touch target — 24×24 on top of the visual icon */
    min-width: 24px; min-height: 24px; padding: 4px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .star-btn:hover { opacity: 1; color: var(--amber); transform: scale(1.2); }
  .star-btn.starred { opacity: 1; color: var(--amber); }
  .stat-card { position: relative; }
  /* While a long-press is armed/fired (#37), suppress text selection on the
     pressed cards so the hold opens the menu instead of highlighting a label.
     Scoped to the armed root and only for the press duration (user-select
     inherits to the card's children). */
  .lp-armed .stat-card, .lp-armed .catalog-item {
    -webkit-user-select: none; user-select: none;
  }
  /* "New"/"Updated" flag for recently-installed or changed ZIMs (#34). In the
     full list it sits inline at the head of the title — it can never cover the
     left icon. A subtle outlined amber pill in small caps, matching the app's
     accent tokens. */
  .new-badge {
    display: inline-block; vertical-align: middle;
    margin-inline-end: 6px; z-index: 2;
    background: var(--amber); color: var(--on-amber); font-size: 9px; font-weight: 700;
    letter-spacing: 0.06em; text-transform: uppercase;
    padding: 2px 6px; border-radius: 5px; line-height: 1.2; pointer-events: none;
  }
  /* "Updated" is the quieter sibling — an outlined amber pill, so a brand-new
     ZIM (solid amber) still reads louder than one that merely changed. */
  .new-badge.updated-badge {
    background: var(--amber-glow); color: var(--amber);
    box-shadow: inset 0 0 0 1px var(--amber-border);
  }
  /* Provenance type badge — what a Zimi-made ZIM IS (folder, page, site, video,
     bookmarks, import, alive), derived from its metadata and never from its
     title. Deliberately the quietest chip on the card: amber is spoken for by
     New/Updated, which are transient and want the eye; this one is permanent
     and must not. A Kiwix ZIM carries no badge at all, so the absence is the
     signal that this library did not make the file. */
  .prov-badge {
    display: inline-block; vertical-align: middle; margin-inline-start: 6px;
    font-size: 9px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
    padding: 2px 6px; border-radius: 5px; line-height: 1.2;
    background: var(--surface2); color: var(--text2);
    box-shadow: inset 0 0 0 1px var(--border);
  }
  /* A replay ZIM is the one kind that behaves differently once opened, so it is
     the one kind that gets an outline instead of blending in. */
  .prov-badge.prov-alive { color: var(--amber); box-shadow: inset 0 0 0 1px var(--amber-border); }

  /* ── Context menu ──
     Z-order contract: content layers (cards, their badges/stars) live at
     z-index 1–2; the menu layer sits at 9999, unconditionally above them. On
     iOS a card promoted to its own compositing layer (momentum-scroll ancestor)
     could paint over the menu for a frame — an icon flashing on top, then
     dropping behind. translateZ(0) on the open menu forces its own layer so it
     sorts above content deterministically, not just by paint order. */
  #zim-ctx-menu {
    position: fixed; z-index: 9999; min-width: 180px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-popup);
    padding: 4px 0; display: none; user-select: none;
  }
  #zim-ctx-menu.visible { display: block; transform: translateZ(0); }
  .ctx-item { padding: 8px 16px; font-size: 13px; color: var(--text); cursor: pointer; transition: background 0.1s; white-space: nowrap; position: relative; }
  .ctx-item.danger:hover { background: rgba(239,68,68,0.08); color: var(--error); }
  .ctx-sep { height: 1px; background: var(--border); margin: 4px 0; }
  /* left/top/width/max-height are overwritten inline by posMenu, which places
     each submenu against the viewport; these are the pre-measurement defaults.
     Move-to can list many categories, so it always scrolls inside its cap. */
  .ctx-sub {
    display: none; position: absolute; left: 100%; top: -4px;
    min-width: 170px; max-height: 60vh; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-popup);
    padding: 4px 0; z-index: 10000;
  }
  /* Too narrow for its labels (a phone pins it to the viewport): wrap them
     rather than let the text run out of the box. */
  .ctx-sub.fitted > .ctx-item { white-space: normal; }
  .ctx-item:hover > .ctx-sub, .ctx-item.kb-open > .ctx-sub { display: block; }
  /* Keyboard focus reads like hover (roving tabindex, ARIA menu pattern).
     Only the keyboard's: a menu opened by a tap or a click focuses its first
     item too, and lit up it read as already chosen (the Lists picker's first
     list looked ticked). */
  .ctx-item:focus { outline: none; }
  .ctx-item:focus-visible { background: var(--amber-glow); color: var(--amber); }
  .ctx-item.danger:focus-visible { background: rgba(239,68,68,0.08); color: var(--error); }

  /* ── Manage-row gear (opens the same Move to… menu) ── */
  .ci-gear {
    flex-shrink: 0; width: 26px; height: 26px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); color: var(--text2); cursor: pointer;
    font-size: 14px; line-height: 1; transition: color 0.1s, border-color 0.1s, background 0.1s;
  }
  .ci-gear:hover { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }

  /* ── Section reorder panel (#37) ── */
  /* Collections and categories share one draggable list; a per-type icon (layers
     vs tag) tells them apart, and collection rows carry a dashed border too. */
  .reorder-list { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
  .reorder-row {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 12px; background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); font-size: 13px; cursor: grab;
  }
  .reorder-row-col { border-style: dashed; }
  .reorder-row.dragging { opacity: 0.5; cursor: grabbing; border-color: var(--amber); }
  /* Grip is the drag affordance; the ▲▼ buttons remain the keyboard fallback.
     touch-action:none lets the touch drag own the gesture that starts on the
     grip (the list still scrolls when a drag starts anywhere else). */
  .reorder-grip { flex-shrink: 0; color: var(--text3); display: inline-flex; align-items: center; cursor: grab; touch-action: none; }
  /* Type icon: collections read amber (their signature), categories muted. */
  .reorder-type { flex-shrink: 0; display: inline-flex; align-items: center; color: var(--text2); }
  .reorder-row-col .reorder-type { color: var(--amber); }
  .reorder-label { flex: 1; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .reorder-btns { display: flex; gap: 4px; flex-shrink: 0; }
  .reorder-btn {
    width: 26px; height: 26px; padding: 0; display: inline-flex;
    align-items: center; justify-content: center; font-size: 11px;
    background: var(--surface2); border: 1px solid var(--border);
    border-radius: var(--radius); color: var(--text); cursor: pointer;
    transition: color 0.1s, border-color 0.1s, background 0.1s;
  }
  .reorder-btn:hover:not(:disabled) { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .reorder-btn:disabled { opacity: 0.3; cursor: default; }
  /* Other catch-all + declared-empty rows read quieter — they're structural, not
     content. Still fully AA (muted labels, not disabled). */
  .reorder-row-other, .reorder-row-empty { border-style: dashed; }
  .reorder-row-other .reorder-label, .reorder-row-empty .reorder-label { color: var(--text2); }
  /* ✕ to drop an empty declared section (only present before any ZIM lives there). */
  .reorder-remove {
    flex-shrink: 0; width: 22px; height: 22px; padding: 0; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center; font-size: 15px;
    background: transparent; border: none; border-radius: var(--radius);
    color: var(--text3); cursor: pointer; transition: color 0.1s, background 0.1s;
  }
  .reorder-remove:hover { color: var(--danger, #e5484d); background: var(--amber-glow); }
  /* Add-section pill: dashed amber, matches the reorder link-out pill. */
  .reorder-add {
    display: inline-flex; align-items: center; gap: 5px; margin-top: 10px;
    color: var(--amber); border-color: var(--amber-border); border-style: dashed;
    background: var(--amber-glow);
  }
  .reorder-add:hover { color: var(--amber); border-color: var(--amber); background: var(--amber-glow); border-style: dashed; }
  /* Inline "Add category" input row (replaces the old prompt() dialog). */
  .reorder-add-row {
    display: flex; align-items: center; gap: 6px; margin-top: 10px;
  }
  .reorder-add-input {
    flex: 1; min-width: 0; padding: 8px 12px; font-size: 13px;
    background: var(--surface); border: 1px solid var(--amber-border);
    border-radius: var(--radius); color: var(--text); outline: none;
  }
  .reorder-add-input:focus { border-color: var(--amber); }
  .reorder-add-ok, .reorder-add-cancel {
    flex-shrink: 0; width: 30px; height: 32px; padding: 0; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); cursor: pointer;
    transition: color 0.1s, border-color 0.1s, background 0.1s;
  }
  .reorder-add-ok { color: var(--amber); border-color: var(--amber-border); }
  .reorder-add-ok:hover { border-color: var(--amber); background: var(--amber-glow); }
  .reorder-add-cancel { color: var(--text3); font-size: 17px; }
  .reorder-add-cancel:hover { color: var(--text); border-color: var(--text3); }
  /* ── Collections manage ── */
  .coll-form { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
  .coll-form input {
    flex: 1; min-width: 120px; padding: 8px 12px; background: var(--surface);
    border: 1px solid var(--border); border-radius: var(--radius);
    color: var(--text); font-size: 13px; outline: none;
  }
  .coll-form input:focus { border-color: var(--amber-border); }
  .coll-form button { padding: 8px 16px; font-size: 13px; font-weight: 500; }
  .coll-item {
    display: flex; align-items: center; gap: 12px; padding: 12px 14px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    margin-bottom: 6px; font-size: 13px; cursor: pointer;
  }
  .coll-item:hover { border-color: var(--amber-border); }
  .coll-item .coll-name { font-weight: 600; color: var(--accent); }

  /* ── History panel ── */
  /* .library-btn base styles in shared .random-btn,.manage-btn,.newtab-btn,.library-btn rule above */
  .history-panel {
    display: none; position: fixed; top: var(--under-topbar); right: 0; bottom: 0; width: 360px; max-width: 100vw;
    background: var(--surface); border-left: 1px solid var(--border); z-index: 180;
    overflow-y: auto; box-shadow: -4px 0 24px rgba(0,0,0,0.4);
  }
  .history-panel.open { display: block; }
  /* Each panel's header: its name and its close, one short band. */
  .library-panel-header {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 6px 8px 6px 16px; border-bottom: 1px solid var(--border); position: sticky; top: 0;
    background: var(--surface); z-index: 1;
  }
  [dir="rtl"] .library-panel-header { padding: 6px 16px 6px 8px; }
  .library-panel-title { margin: 0; font-size: 15px; font-weight: 600; color: var(--text); }
  .library-panel-header .hp-clear { border-color: transparent; font-size: 14px; }
  /* Continue, at the head of History: its tree sits on the day groups' edge. */
  .bm-tree.hp-continue { padding: 6px 10px 0; }
  .hp-clear { background: none; border: 1px solid var(--border); border-radius: var(--radius); color: var(--text2); font-size: 12px; padding: 4px 10px; cursor: pointer; transition: all 0.15s; }
  .hp-clear:hover { color: var(--error); border-color: var(--error); }
  .hp-group { padding: 0 16px; }
  .hp-group-label { padding: 12px 0 6px; }
  /* First day heading and the Clear action share one row. */
  .hp-group-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .hp-group-head .hp-group-label { flex: 1; }
  .hp-item { display: flex; align-items: center; gap: 10px; padding: 8px 0; cursor: pointer; transition: opacity 0.15s; border-bottom: 1px solid var(--border); }
  .hp-item:last-child { border-bottom: none; }
  .hp-item:hover { opacity: 0.8; }
  .hp-icon { flex-shrink: 0; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 6px; background: var(--surface2); font-size: 14px; }
  .hp-icon img { width: 20px; height: 20px; border-radius: 3px; }
  .hp-icon svg { color: var(--text2); }
  .hp-detail { flex: 1; min-width: 0; }
  .hp-title { font-size: 13px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .hp-sub { font-size: 11px; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .hp-item-del {
    opacity: 0; background: none; border: none; color: var(--text2); font-size: 14px; cursor: pointer;
    padding: 2px 6px; border-radius: 4px; transition: all 0.15s; flex-shrink: 0; line-height: 1;
  }
  .hp-item:hover .hp-item-del { opacity: 0.4; }
  .hp-item-del:hover { opacity: 1 !important; color: var(--error); }
  .hp-empty { padding: 32px 16px; text-align: center; color: var(--text2); font-size: 13px; }

  /* ── Saved tab: Continue, Liked and the lists, then what is in no list ── */
  .bm-scope { display: flex; gap: 6px; padding: 10px 12px 0; flex-wrap: wrap; }
  .bm-continue .bm-ficon, .bm-folder[data-fid="liked"] .bm-ficon { color: var(--amber); }
  .bm-gear.bm-gear-gap { visibility: hidden; }
  /* A title keeps its own direction (an English one under Hebrew chrome still
     ends in its own question mark) and the list's alignment. */
  .bm-name, .bm-sub { unicode-bidi: plaintext; text-align: left; }
  [dir="rtl"] .bm-name, [dir="rtl"] .bm-sub { text-align: right; }
  /* Two quiet actions over the lists, in the tree's own voice: New list
     where a list's name starts, Export at the far end. Boxed, at a thumb's
     height each, they outweighed the lists they act on. */
  .bm-actions { justify-content: space-between; padding: 2px 10px 0; }
  .bm-actions .hp-action-btn { background: none; border: 0; color: var(--text2); font-size: 13px; padding: 6px 8px; display: inline-flex; align-items: center; gap: 8px; }
  .bm-actions .bm-new > span { width: 17px; text-align: center; font-size: 17px; line-height: 1; }
  /* Something to say above the lists: sync paused, 1.11's bookmarks offered. */
  /* A toast for something taken away carries its way back. */
  .toast-undo { display: flex; align-items: center; gap: 16px; width: max-content; max-width: calc(100vw - 32px); box-sizing: border-box; }
  .toast-undo button { border: 0; background: none; color: var(--amber); font: inherit; font-weight: 600; cursor: pointer; padding: 8px 4px; margin: -8px 0; white-space: nowrap; }
  .bm-note { margin: 10px 12px 0; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 13px; line-height: 1.45; color: var(--text2); }
  .bm-note.bm-warn { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); }
  .bm-note-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
  .bm-tree { padding: 6px 10px 16px; -webkit-user-select: none; user-select: none; touch-action: pan-y; }
  .bm-tree.bm-drop-root { background: var(--amber-glow); border-radius: 8px; }
  .bm-row {
    position: relative; display: flex; align-items: center; gap: 8px;
    padding-block: 7px; padding-inline: 6px 8px; border-radius: 7px; cursor: pointer; min-height: 34px;
  }
  .bm-row:hover { background: var(--surface2); }
  .bm-row:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
  .bm-folder .bm-name { font-weight: 600; }
  .bm-twist {
    flex-shrink: 0; width: 14px; text-align: center; color: var(--text2);
    font-size: 10px; transition: transform 0.15s; transform: rotate(0deg);
  }
  .bm-twist.open { transform: rotate(90deg); }
  /* Closed, it points the way the text runs. */
  [dir="rtl"] .bm-twist:not(.open) { transform: scaleX(-1); }
  /* Empty twist column on bookmark rows — keeps a bookmark indented past the
     folder that holds it instead of hanging left of it. */
  .bm-twist-gap { visibility: hidden; }
  .bm-ficon, .bm-bicon { flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
  .bm-ficon { width: 20px; color: var(--text2); }
  .bm-ficon svg { display: block; }
  /* Drop target: the folder about to receive the drag lights its glyph too. */
  .bm-row.bm-drop-into .bm-ficon { color: var(--amber); }
  .bm-bicon { width: 24px; height: 24px; border-radius: 5px; background: var(--surface2); font-size: 13px; color: var(--text2); }
  .bm-bicon img { width: 18px; height: 18px; border-radius: 3px; }
  /* Letter tile fallback for sources without an icon; unscoped it rendered as
     a bare floating character that read as a broken image. */
  .bm-bicon .rs-letter { font-size: 11px; font-weight: 600; color: var(--text2); line-height: 1; }
  .bm-detail { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  .bm-name { font-size: 13px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .bm-sub { font-size: 11px; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .bm-folder .bm-name { flex: 1; min-width: 0; }
  /* Source no longer installed — readable, not decorative: the row still has to
     be findable and movable, it just can't be opened. */
  .bm-missing .bm-bicon { opacity: 0.4; }
  .bm-missing .bm-name { color: var(--text2); font-style: italic; }
  .bm-missing .bm-sub { color: var(--error); }
  /* A highlight: its colour as a bar, its passage over two lines. */
  .bm-hl-bar { flex-shrink: 0; width: 3px; align-self: stretch; border-radius: 2px; margin-block: 2px; margin-inline: 0 2px; }
  .bm-hl .bm-name { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-height: 1.35; }
  .bm-hl-lost .bm-name { color: var(--text2); }
  .bm-hl-lost .bm-sub { color: var(--amber); }
  .bm-count {
    flex-shrink: 0; font-size: 11px; color: var(--text2); background: var(--surface2);
    border-radius: 10px; padding: 1px 7px; min-width: 20px; text-align: center;
  }
  .bm-gear {
    flex-shrink: 0; background: none; border: none; color: var(--text2); font-size: 16px;
    cursor: pointer; padding: 0 4px; opacity: 0; line-height: 1; border-radius: 5px;
  }
  .bm-row:hover .bm-gear, .bm-row:focus-within .bm-gear { opacity: 0.55; }
  .bm-gear:hover { opacity: 1 !important; color: var(--amber); background: var(--amber-glow); }
  /* Touch: no hover, so keep the gear always faintly visible. */
  @media (hover: none) { .bm-gear { opacity: 0.45; } }
  /* Inline new-folder / rename inputs */
  .bm-newfolder, .bm-row.bm-renaming { display: flex; align-items: center; gap: 8px; padding-block: 7px; padding-inline: 6px 8px; min-height: 34px; }
  .bm-newfolder-input, .bm-rename-input {
    flex: 1; min-width: 0; background: var(--surface); color: var(--text); font-size: 13px;
    border: 1px solid var(--amber-border); border-radius: 6px; padding: 4px 8px; outline: none;
  }
  .bm-rename-input { font-weight: 600; }
  /* A finger, not a pointer: a row, the ⋯ at its end and the panel's buttons
     are each a thumb's size. The ⋯ takes the row's full height and the room
     at its end, so its glyph stays where it was. */
  @media (pointer: coarse) {
    .bm-row, .bm-newfolder, .bm-row.bm-renaming { min-height: 44px; }
    .bm-gear { align-self: stretch; min-width: 44px; margin-block: -7px; margin-inline-end: -8px; }
    .bm-actions .hp-action-btn, .bm-note-actions .hp-action-btn,
    .bm-export-actions .hp-action-btn, .bm-exp-row { min-height: 44px; }
    .library-panel-header .hp-clear { min-width: 44px; min-height: 44px; }
    /* The targets stay a thumb's size; the room around them goes, so the
       header and the actions under it take one short band, not a third of
       the phone. The close is a bare mark, as the sheets' are. */
    .library-panel-header { padding: 0 0 0 16px; }
    [dir="rtl"] .library-panel-header { padding: 0 16px 0 0; }
    .library-panel-header .hp-clear { font-size: 15px; }
    .bm-actions { padding-top: 0; }
    .bm-actions + .bm-tree, .bm-actions ~ .bm-tree { padding-top: 0; }
  }
  /* Drag feedback */
  .bm-row.bm-dragging { opacity: 0.4; }
  .bm-row.bm-drop-into { background: var(--amber-glow); box-shadow: inset 0 0 0 2px var(--amber-border); }
  .bm-row.bm-drop-before::before, .bm-row.bm-drop-after::after {
    content: ''; position: absolute; left: 6px; right: 8px; height: 2px; background: var(--amber); border-radius: 2px;
  }
  .bm-row.bm-drop-before::before { top: -1px; }
  .bm-row.bm-drop-after::after { bottom: -1px; }
  .bm-drag-ghost {
    position: fixed; z-index: 9999; pointer-events: none; transform: translate(8px, 8px);
    background: var(--surface); color: var(--text); border: 1px solid var(--amber-border);
    border-radius: 7px; padding: 6px 12px; font-size: 13px; font-weight: 600;
    box-shadow: 0 6px 20px rgba(0,0,0,0.28); max-width: 220px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .ctx-note { padding: 8px 12px; font-size: 12px; color: var(--text2); border-bottom: 1px solid var(--border); max-width: 240px; white-space: normal; line-height: 1.4; }
  /* The tick beside each list an item is in (the Lists submenu). */
  .ctx-check { display: inline-block; width: 1.2em; color: var(--amber); font-weight: 700; }
  /* The list picker (savedPickLists): a new list's name typed in its row;
     each row a thumb's height on a touch screen. */
  .ctx-input { width: 100%; box-sizing: border-box; }
  @media (pointer: coarse) {
    .ctx-item[data-action="toggle-list"], .ctx-item[data-action="new-list"], .ctx-item:has(> .ctx-input) { min-height: 44px; display: flex; align-items: center; }
  }

  /* Export selector modal */
  .bm-export-overlay {
    position: fixed; inset: 0; z-index: 10000; display: flex; align-items: center; justify-content: center;
    background: rgba(0,0,0,0.5); padding: 20px;
  }
  .bm-export-modal {
    background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
    width: min(460px, 100%); max-height: 80vh; display: flex; flex-direction: column;
    box-shadow: 0 20px 60px rgba(0,0,0,0.4); overflow: hidden;
  }
  .bm-export-head { padding: 16px 18px 6px; font-size: 16px; font-weight: 600; color: var(--text); }
  .bm-export-desc { padding: 0 18px 12px; font-size: 12.5px; color: var(--text2); line-height: 1.45; }
  .bm-export-tree { overflow-y: auto; padding: 4px 10px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
  .bm-exp-row { display: flex; align-items: center; gap: 8px; padding: 7px 6px; cursor: pointer; border-radius: 6px; }
  .bm-exp-row:hover { background: var(--surface2); }
  .bm-exp-row input { accent-color: var(--amber); width: 15px; height: 15px; flex-shrink: 0; }
  .bm-exp-ico { flex-shrink: 0; display: flex; align-items: center; color: var(--text2); }
  .bm-exp-name { flex: 1; min-width: 0; font-size: 13px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .bm-exp-count { flex-shrink: 0; font-size: 11px; color: var(--text2); background: var(--surface2); border-radius: 10px; padding: 1px 7px; }
  .bm-exp-empty { padding: 24px; text-align: center; color: var(--text2); font-size: 13px; }
  .bm-export-name-row {
    display: flex; align-items: center; gap: 10px; padding: 12px 18px 0;
    font-size: 12.5px; color: var(--text2); white-space: nowrap;
  }
  .bm-export-name-row input {
    flex: 1; min-width: 0; background: var(--surface); color: var(--text); font-size: 13px;
    border: 1px solid var(--border); border-radius: 6px; padding: 5px 8px; outline: none;
  }
  .bm-export-name-row input:focus { border-color: var(--amber-border); }
  .bm-export-count { padding: 6px 18px 0; font-size: 12px; color: var(--text2); }
  .bm-export-status { padding: 8px 18px 0; font-size: 12.5px; color: var(--text2); min-height: 18px; }
  .bm-export-actions { display: flex; align-items: center; gap: 8px; padding: 12px 18px 16px; }
  .bm-export-actions .primary { background: var(--amber); color: var(--on-amber); border-color: transparent; }
  .bm-export-actions .primary:hover:not(:disabled) { color: var(--on-amber); filter: brightness(1.05); }

  /* ── About this ZIM ──
     A reading panel, not a form: no primary action, nothing to submit, one way
     out. It shares the export modal's overlay geometry (centred, capped at 80vh,
     scrolls inside itself) because a ZIM's metadata is exactly as long as it is
     and a 4000-character LongDescription must not push the close button off
     screen. Every colour is a token, so light theme needs no rules of its own. */
  .zi-overlay {
    position: fixed; inset: 0; z-index: 10000; display: flex;
    align-items: center; justify-content: center;
    background: rgba(0,0,0,0.5); padding: 20px;
    /* A modal that lets the page scroll underneath is not modal. `contain`
       stops a flick that reaches the panel's end from handing the rest of the
       gesture to the library behind it — the chaining a phone does by default,
       and the reason this read as "I can scroll while that thing is loading".
       The body itself is frozen by .zi-open below. */
    overscroll-behavior: contain;
  }
  /* Set on <html> while the panel is up. Frozen rather than hidden: the body
     is pinned where it was scrolled to (_ziLockPage sets its top) and put
     back after, so the reader keeps their place and iOS draws the sheet
     where it is, not where the page was. */
  html.zi-open, html.zi-open body { overflow: hidden; }
  html.zi-open body { position: fixed; left: 0; right: 0; }
  .zi-panel {
    background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
    width: min(520px, 100%); max-height: 84vh; display: flex; flex-direction: column;
    box-shadow: 0 20px 60px rgba(0,0,0,0.4); overflow: hidden;
  }
  .zi-head {
    display: flex; align-items: center; gap: 10px; padding: 14px 16px;
    border-bottom: 1px solid var(--border); flex-shrink: 0;
  }
  .zi-head-title { font-size: 15px; font-weight: 600; color: var(--text); flex: 1; }
  .zi-close {
    background: none; border: none; color: var(--text2); font-size: 15px; line-height: 1;
    cursor: pointer; padding: 4px 6px; border-radius: 6px;
  }
  .zi-close:hover { color: var(--text); background: var(--surface2); }
  .zi-body { overflow-y: auto; padding: 16px; }
  .zi-none { color: var(--text2); font-size: 12.5px; line-height: 1.5; padding: 4px 0; }
  .zi-warn {
    color: var(--text2); font-size: 12.5px; line-height: 1.5; margin-bottom: 12px;
    padding: 8px 10px; border-radius: 8px; background: var(--surface2);
    border: 1px solid var(--border);
  }
  .zi-id { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 14px; }
  .zi-icon img { border-radius: 8px; display: block; background: var(--surface2); }
  .zi-letter {
    display: flex; align-items: center; justify-content: center;
    width: 48px; height: 48px; border-radius: 8px; background: var(--surface2);
    color: var(--text2); font-size: 20px; font-weight: 600;
  }
  .zi-id-text { min-width: 0; flex: 1; }
  .zi-title { font-size: 16px; font-weight: 600; color: var(--text); line-height: 1.3; }
  .zi-desc { font-size: 12.5px; color: var(--text2); line-height: 1.45; margin-top: 3px; }
  .zi-long {
    font-size: 12.5px; color: var(--text2); line-height: 1.5; margin-bottom: 14px;
    white-space: pre-wrap;
  }
  /* Label/value pairs. The label column is fixed so the values line up, and it
     wraps rather than truncates — a translated label is often longer. */
  .zi-rows { display: flex; flex-direction: column; gap: 6px; }
  .zi-row { display: flex; gap: 10px; align-items: baseline; font-size: 12.5px; }
  .zi-k {
    flex: 0 0 92px; color: var(--text2); text-transform: uppercase;
    letter-spacing: 0.05em; font-size: 10px; font-weight: 600; padding-top: 2px;
  }
  /* overflow-wrap, not ellipsis: a Source URL or a Scraper string is the whole
     point of the row, so it wraps instead of losing its tail. */
  /* A raw metadata key is the FILE's spelling — never title-cased by the UI. */
  .zi-k-raw { text-transform: none; letter-spacing: 0; font-size: 11px; }
  .zi-v { flex: 1; min-width: 0; color: var(--text); overflow-wrap: anywhere; }
  .zi-v a { color: var(--amber); text-decoration: none; }
  .zi-v a:hover { text-decoration: underline; }
  .zi-tag {
    display: inline-block; margin: 0 4px 4px 0; padding: 2px 7px; border-radius: 10px;
    background: var(--surface2); border: 1px solid var(--border);
    color: var(--text2); font-size: 11px;
  }
  .zi-sec {
    margin: 18px 0 10px; font-size: 10px; font-weight: 600; color: var(--text2);
    text-transform: uppercase; letter-spacing: 0.06em;
  }
  /* "Estimated from 6,000 of 6,400,000 entries." A ZIM too large to weigh
     whole is sampled, and the panel says so under the bar rather than letting
     an estimate stand in the same voice as a measurement. */
  .zi-shape-note { margin-top: 8px; font-size: 11px; color: var(--text2); opacity: 0.85; }
  /* The provenance timeline: one entry per record, oldest first, joined by a
     rule down the left so a ZIM's life reads as a sequence rather than a list. */
  .zi-timeline { list-style: none; margin: 0; padding: 0 0 0 16px; border-inline-start: 1px solid var(--border); }
  .zi-ev { position: relative; padding-bottom: 14px; }
  .zi-ev:last-child { padding-bottom: 0; }
  .zi-ev::before {
    content: ''; position: absolute; inset-inline-start: -21px; top: 5px;
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--surface); box-shadow: inset 0 0 0 2px var(--amber);
  }
  .zi-ev-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
  .zi-ev-op { font-size: 13px; font-weight: 600; color: var(--text); }
  .zi-ev-mode {
    font-size: 9px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
    padding: 2px 6px; border-radius: 5px; background: var(--surface2); color: var(--text2);
    box-shadow: inset 0 0 0 1px var(--border);
  }
  .zi-ev-when { font-size: 11px; color: var(--text2); margin-inline-start: auto; }
  .zi-ev-detail { font-size: 12.5px; color: var(--text); line-height: 1.45; margin-top: 3px; }
  .zi-ev-fact { font-size: 11.5px; color: var(--text2); line-height: 1.45; margin-top: 2px; }
  .zi-ev-sub { color: var(--text3); }
  /* Settings > Voices: a row per engine (name, how many languages it says
     here and its version, Hear, its switch); Natural's Get or Remove for an
     admin; the downloads setting one quiet line under the card. Hear works
     (a turning ring) from the tap until the audio starts, then plays (a stop
     square, a soft pulse) until it ends. */
  .voice-engine { flex-wrap: wrap; }
  .voice-engine .voice-meta { color: var(--text3); font-size: 11.5px; }
  /* The words keep their width; the buttons wrap under them, not the words. */
  .voice-engine .share-row-text { flex: 1 1 10em; min-width: 10em; }
  /* Clear's Get and its ▾, one object; the ▾ opens the other languages. */
  .voice-split { display: inline-flex; }
  .voice-get-line { flex-basis: 100%; display: flex; }
  .voice-split > .set-btn[data-primary] { flex-direction: column; line-height: 1.2; padding-block: 6px; }
  .set-btn-sub { font-size: 11px; font-weight: 400; color: var(--text2); }
  .voice-split > .set-btn:first-child:not(:last-child) { border-start-end-radius: 0; border-end-end-radius: 0; }
  .voice-split-more { border-start-start-radius: 0; border-end-start-radius: 0; margin-inline-start: -1px; padding-inline: 12px; font-size: 15px; }
  .voice-pick { flex-basis: 100%; display: flex; flex-direction: column; border-top: 1px solid var(--border); margin-top: 4px; }
  .voice-pick-row {
    display: flex; justify-content: space-between; gap: 12px; padding: 10px 0; border: 0; border-bottom: 1px solid var(--border);
    background: none; color: var(--text); font: inherit; font-size: 13.5px; text-align: start; cursor: pointer;
  }
  .voice-pick-row:last-child { border-bottom: 0; }
  .voice-pick-row:hover { color: var(--amber); }
  .voice-pick-row:disabled { opacity: 0.5; cursor: default; }
  .voice-pick-all { color: var(--amber); font-weight: 600; }
  .voice-pick-mb { color: var(--text3); font-variant-numeric: tabular-nums; font-weight: 400; }
  /* Default voice: only the voices on here; scrolls sideways when many. */
  .voice-default { max-width: none; overflow-x: auto; margin-bottom: 12px; scrollbar-width: none; }
  .voice-default .app-theme-btn { flex: 1 0 auto; white-space: nowrap; }
  .voice-engine.voice-off .share-row-title, .voice-engine.voice-off .share-row-desc { opacity: 0.55; }
  .voice-engine .set-btn[data-primary] { color: var(--amber); border-color: var(--amber-border); }
  /* Natural's Remove, quiet in its small line: a word, not a button. */
  /* "N languages": its title lists them; a tap shows the list as a line. */
  .voice-count {
    padding: 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer;
    text-decoration: underline dotted; text-underline-offset: 2px;
  }
  .voice-count:hover, .voice-count[aria-expanded="true"] { color: var(--text2); }
  .voice-langs { flex-basis: 100%; margin-top: -6px; color: var(--text3); font-size: 11.5px; }
  .voice-meta .set-btn[data-quiet] {
    min-height: 0; padding: 0; border: 0; background: none; font-size: inherit; font-weight: 400;
    color: var(--text2); text-decoration: underline; text-underline-offset: 2px;
  }
  .voice-meta .set-btn[data-quiet].confirming { color: var(--error); }
  .voice-progress { color: var(--amber); }
  .voice-failed { color: var(--error); }
  .voice-engine .lang-dl { padding-inline-start: 0; }
  .voice-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; font-size: 12.5px; color: var(--text2); }
  .voice-hear { flex: none; }
  .voice-hear-icon { position: relative; display: inline-flex; width: 12px; height: 12px; }
  .voice-hear.working .voice-hear-icon svg, .voice-hear.playing .voice-hear-icon svg { visibility: hidden; }
  .voice-hear.working .voice-hear-icon::before {
    content: ''; position: absolute; inset: 0; border-radius: 50%;
    border: 2px solid var(--amber-border); border-top-color: var(--amber); animation: share-spin 0.7s linear infinite;
  }
  .voice-hear.playing { color: var(--amber); border-color: var(--amber-border); background: var(--amber-glow); animation: voice-pulse 1.4s ease-in-out infinite; }
  .voice-hear.playing .voice-hear-icon::before { content: ''; position: absolute; inset: 2px; border-radius: 2px; background: currentColor; }
  @keyframes voice-pulse { 50% { box-shadow: 0 0 0 4px var(--amber-glow); } }
  @media (prefers-reduced-motion: reduce) { .voice-hear.playing { animation: none; } }
  @media (max-width: 600px) {
    .zi-overlay { padding: 0; align-items: stretch; }
    .zi-panel { width: 100%; max-height: 100%; border-radius: 0; border: none; }
    /* The label column costs too much of a phone's width — stack instead. */
    .zi-row { flex-direction: column; gap: 1px; }
    .zi-k { flex: none; padding-top: 0; }
  }

  /* ── Discover homepage — horizontal scroll row ── */
  .discover-section { margin-bottom: 20px; }
  .discover-label {
    font-size: 13px; font-weight: 600; color: var(--text2); text-transform: uppercase;
    letter-spacing: 0.06em; margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between;
  }
  .dc-dismiss { background: none; border: none; color: var(--text2); font-size: 16px; cursor: pointer; padding: 0 4px; opacity: 0.4; transition: opacity 0.2s; line-height: 1; }
  .dc-dismiss:hover { opacity: 1; color: var(--text); }
  .discover-scroll { display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; padding: 8px 2px; margin: -8px -2px; will-change: transform; }
  /* The scrollbar stays. It was hidden once on a guess about which stray line
     was being complained about — the answer was the step rail on the create
     page — and on a desktop with no touchscreen that left the strip with no
     way to scroll it at all (#75). A row that can only be moved by swiping is
     not a row most people can move. */
  .discover-scroll::-webkit-scrollbar { height: 4px; }
  .discover-scroll::-webkit-scrollbar-track { background: transparent; }
  .discover-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
  .discover-card {
    flex: 0 0 260px; min-height: 200px; max-height: 320px; scroll-snap-align: start;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    cursor: pointer; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    overflow: hidden; display: flex; flex-direction: column;
  }
  .discover-card:hover { background: var(--surface2); border-color: var(--amber-border); box-shadow: 0 4px 24px var(--amber-glow), 0 0 0 1px var(--amber-glow); transform: translateY(-2px); }
  .discover-card.dc-no-click { cursor: default; }
  .discover-card.dc-no-click:hover { background: var(--surface); border-color: var(--border); box-shadow: none; transform: none; }
  .dc-thumb { width: 100%; min-height: 120px; height: 120px; object-fit: cover; display: block; background: var(--surface2); }
  .dc-thumb.dc-book-cover { object-fit: contain; background: #1a1a1e; }
  .dc-body { padding: 10px 12px; flex: 1; display: flex; flex-direction: column; gap: 2px; }
  .dc-source { font-size: 11px; color: var(--text2); display: flex; align-items: center; gap: 6px; }
  .dc-zim-icon { width: 16px; height: 16px; border-radius: 3px; background: #fff; flex-shrink: 0; }
  .dc-source span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .dc-date { font-size: 11px; color: var(--text2); margin-inline-start: auto; flex-shrink: 0; }
  .dc-title { font-size: 13px; color: var(--text); font-weight: 600; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.3; }
  .dc-blurb { font-size: 11px; color: var(--text2); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .dc-blurb.dc-quote { font-family: Georgia, "Times New Roman", serif; font-style: italic; color: var(--text); font-weight: 400; font-size: 14px; -webkit-line-clamp: 4; line-height: 1.5; flex: 1; letter-spacing: 0.01em; }
  .dc-quote-card { display: flex; flex-direction: column; }
  .dc-quote-card .dc-body { padding: 20px; gap: 0; flex: 1; justify-content: space-between; }
  .dc-quote-card .dc-header { display: flex; align-items: center; gap: 6px; font-size: 10px; color: var(--text2); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 12px; }
  .dc-quote-card .dc-quote-mark { font-family: Georgia, "Times New Roman", serif; font-size: 48px; line-height: 1; color: var(--amber); opacity: 0.4; margin-bottom: -8px; user-select: none; }
  .dc-quote-card .dc-attribution { font-size: 11px; color: var(--text2); margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; letter-spacing: 0.3px; }
  .dc-speaker { font-size: 11px; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .dc-word-card .dc-body { padding: 20px; }
  .dc-word-card .dc-header { margin-bottom: 10px; text-transform: uppercase; letter-spacing: 0.06em; font-size: 10px; }
  .dc-word-card .dc-headword { font-family: Georgia, "Times New Roman", serif; font-size: 24px; font-weight: 700; color: var(--text); margin: 0 0 4px; letter-spacing: -0.5px; line-height: 1.2; }
  .dc-word-card .dc-pos { display: inline-block; font-size: 11px; color: var(--amber); font-style: italic; font-family: Georgia, "Times New Roman", serif; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--border); width: 100%; }
  /* Say on a Discover card: a round speaker at the top right, level with
     the card's title, so the card keeps its height (Eric, 2026-10-06). */
  .dc-word-card .dc-body, .dc-quote-card .dc-body { position: relative; }
  .dc-word-card .dc-header, .dc-quote-card .dc-header { padding-inline-end: 44px; }
  .dc-say { position: absolute; top: 12px; inset-inline-end: 12px; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; margin: 0; border-radius: 50%; color: var(--amber); background: var(--amber-glow); border: 1px solid var(--amber-border); cursor: pointer; }
  .dc-say > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .dc-say:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
  .dc-say.on { animation: dcSayPulse 1s ease-in-out infinite; }
  @keyframes dcSayPulse { 50% { opacity: .55; } }
  @media (prefers-reduced-motion: reduce) { .dc-say.on { animation: none; } }
  .dc-word-card .dc-def { font-size: 13px; color: var(--text2); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
  .dc-icon-thumb { width: 100%; height: 120px; display: flex; align-items: center; justify-content: center; background: var(--surface2); }
  .dc-icon-thumb img { width: 48px; height: 48px; opacity: 0.6; }
  /* A source with no icon of its own: its letter, as everywhere else it is
     named, not an empty box. */
  .dc-icon-thumb .rs-letter { width: 56px; height: 56px; border-radius: 12px; background: var(--surface); display: flex; align-items: center; justify-content: center; font-size: 26px; font-weight: 600; color: var(--text2); }
  .dc-dice { font-size: 12px; flex-shrink: 0; opacity: 0.7; }
  /* Video ZIMs (TED/Khan): play badge centered over the 120px thumbnail so a
     "random" pick reads as "play a video", not "read an article". */
  .dc-video-card { position: relative; }
  .dc-play-badge {
    position: absolute; top: 60px; left: 50%; transform: translate(-50%, -50%);
    width: 46px; height: 46px; border-radius: 50%; background: rgba(0, 0, 0, 0.55);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4); pointer-events: none;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.25s, transform 0.25s;
  }
  .dc-play-badge::after {
    content: ''; width: 0; height: 0; margin-left: 3px;
    border-style: solid; border-width: 9px 0 9px 15px;
    border-color: transparent transparent transparent #fff;
  }
  .dc-video-card:hover .dc-play-badge { background: var(--amber); transform: translate(-50%, -50%) scale(1.08); }
  .dc-video-card:hover .dc-play-badge::after { border-left-color: #0a0a0b; }
  /* ── Today card — moon phase + star field ── */
  /* Maps card: a graticule over the surface, the map glyph in the middle.
     Same 120px as every other card's thumb, so the row keeps its rhythm. */
  .dc-today { width: 100%; height: 120px; position: relative; background: linear-gradient(135deg, #0a0e1a 0%, #141c2e 50%, #0d1220 100%); overflow: hidden; }
  .dc-star { position: absolute; width: 2px; height: 2px; background: #fff; border-radius: 50%; animation: dc-twinkle 3s ease-in-out infinite; }
  @keyframes dc-twinkle { 0%, 100% { opacity: 0.2; } 50% { opacity: 1; } }
  .dc-moon-wrap { position: absolute; top: 50%; left: 50%; width: 48px; height: 48px; }
  .dc-moon-sprite { display: block; width: 100%; height: 100%; }
  .dc-moon-glow { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 64px; height: 64px; border-radius: 50%; background: radial-gradient(circle, rgba(232,224,208,0.15) 0%, transparent 70%); }

  .pill.disabled { opacity: 0.35; cursor: not-allowed; }

  /* ── Discover loading skeletons ── */
  .dc-loading { pointer-events: none; }
  .dc-skel, .dc-skel-line { background: linear-gradient(90deg, var(--surface2) 25%, var(--border) 50%, var(--surface2) 75%); background-size: 200% 100%; animation: dc-shimmer 1.5s ease-in-out infinite; }
  .dc-skel-line { height: 12px; border-radius: 4px; margin-bottom: 6px; }
  @keyframes dc-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

  /* ── Reduced motion ── */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  }

  /* ── Touch-device input zoom guard ──
     iOS Safari auto-zooms the page when a focused text-entry control has a
     font-size below 16px. Many of our inputs use denser sizes (Add User 13px,
     search/port fields 11–13px) which triggered that zoom on tap. On coarse
     (touch) pointers, floor every text-entry control at 16px so focusing an
     input never zooms; desktop keeps the compact sizes. Checkbox/radio/range/
     color aren't font-sized and don't zoom, so they're excluded. */
  @media (pointer: coarse) {
    input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file]),
    select, textarea { font-size: 16px; }
  }

  /* ── Forced colors (Windows High Contrast, etc.) ──
     When the OS forces a color scheme, our custom CSS variables get
     overridden anyway. The wins here are: keep focus rings visible,
     ensure border-only buttons stay distinguishable, and don't force
     dark backgrounds that fight the user's chosen scheme. */
  @media (forced-colors: active) {
    :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
    button, .btn-primary, .manage-btn-action, .ci-add-btn {
      forced-color-adjust: none;
      background: ButtonFace; color: ButtonText;
      border: 1px solid ButtonBorder, ButtonText;
    }
    .skip-link { background: Highlight; color: HighlightText; }
    /* Pills need to retain their border so they're still grouped visually */
    .pill, .ci-peer-pill, .ci-hier-installed, .dl-source {
      border: 1px solid ButtonText;
    }
  }

/* ── Background-activity badge ────────────────────────────────────────
   Background work (downloads, indexing, seeding) surfaces as a compact
   amber badge on the topbar's Manage entry point — the gear on desktop,
   the ⋯ button on mobile (the gear is hidden there). Replaces the old
   full-width status bar: no layout shift, no ambient strip, and it's
   clickable — tapping it jumps straight to the downloads view. The count
   (in-flight + queued downloads) rides the gear; a hover/long-press title
   carries the full detail string. Zero downloads but other work → a bare
   dot. Matches the .dl-tab-badge amber-on-black convention. */
  .topbar-badge {
    position: absolute; top: -5px; right: -5px;
    min-width: 17px; height: 17px; padding: 0 4px; box-sizing: border-box;
    display: none; align-items: center; justify-content: center;
    background: var(--amber); color: var(--on-amber);
    border: 1.5px solid var(--bg); border-radius: 9px;
    font-size: 10px; font-weight: 700; line-height: 1;
    font-variant-numeric: tabular-nums; cursor: pointer;
  }
  /* Ambient-only work (no downloads): shrink to a plain dot, no number. */
  .topbar-badge.dot { min-width: 9px; width: 9px; height: 9px; padding: 0; top: -2px; right: -2px; }
  [dir="rtl"] .topbar-badge { right: auto; left: -5px; }
  [dir="rtl"] .topbar-badge.dot { right: auto; left: -2px; }
  .topbar-more { position: relative; }
  /* Download count shown on the ⋯-menu's Manage row (mobile). */
  .topbar-menu-item .tbm-count {
    margin-left: auto; padding: 1px 7px;
    background: var(--amber); color: var(--on-amber); border-radius: 10px;
    font-size: 11px; font-weight: 600; line-height: 1.4;
    font-variant-numeric: tabular-nums;
  }

  /* almanac.css loads after this file and pins .almanac-view to a literal 56px;
     the body prefix wins on specificity so the almanac clears the banner too. */
  body .almanac-view { top: var(--under-topbar); }

/* The site's mark beside its name while a capture runs. Fixed box so a
   tall or wide favicon cannot push the two text lines around, and the
   header stays one row on a phone.
   SCOPED to the run header on purpose. This was written as a bare
   `.create-head { align-items: center; gap: 12px }`, which is the same
   selector as the page header's rule far above — same specificity, later
   in the file, so it half-overwrote it and neither header ended up as
   either author had drawn it. The page header silently took a 12px gap
   it did not want; the run header kept `flex-direction: column` from the
   rule it was overriding and so laid its icon ABOVE the title and, with
   align-items:center, shrank the text block to the title's own width and
   centred it — leaving the mode line ("Web page · cnn.com") flush left
   under a centred title. That is what read as off-centre: two rules for
   two different components wearing one class name. */
.create-run .create-head { flex-direction: row; align-items: center; gap: 12px; }
.create-run .create-head-text { min-width: 0; flex: 1; }
.create-head-icon {
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: 6px; object-fit: contain;
  background: var(--panel, rgba(255,255,255,0.04));
}

/* The live page, while its capture runs. A full-page screenshot of a homepage
   is twenty thousand pixels tall, so this shows the TOP of it at the width it
   has — enough to recognise the page, never enough to push the progress strip
   off the screen. object-position keeps that crop at the top rather than the
   middle, which is where the interesting part of a page is. */
.create-live-shot {
  margin: 0 0 14px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--border);
  background: var(--panel, rgba(255,255,255,0.04));
}
.create-live-shot img {
  display: block; width: 100%; height: 180px;
  object-fit: cover; object-position: top center;
}
@media (max-width: 560px) { .create-live-shot img { height: 120px; } }


/* The two pictures a capture keeps, side by side on anything wide enough and
   stacked on a phone. Neither means much alone: the pair is the comparison. */
.zi-shots { display: grid; gap: 10px; margin: 0 0 14px; }
.zi-shots.pair { grid-template-columns: 1fr 1fr; }
@media (max-width: 560px) { .zi-shots.pair { grid-template-columns: 1fr; } }

/* The capture's picture of the live page, in About. Bounded height so a tall
   homepage does not push every fact off the panel; the whole thing opens full
   size in a tab on click. */
.zi-shot {
  display: block;
  margin: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg2);
  text-decoration: none;
}
.zi-shot img {
  display: block;
  width: 100%;
  max-height: 220px;
  object-fit: cover;
  object-position: top center;
}
.zi-shot-cap {
  display: block;
  padding: 6px 10px;
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text2);
  border-top: 1px solid var(--border);
}
.zi-shot:hover { border-color: var(--amber); }
.zi-shot:hover .zi-shot-cap { color: var(--amber); }


/* The two pictures on the Create done card: live beside packaged. Same
   bounded height as About's, side by side where there is room. */
.create-shots { display: grid; gap: 10px; margin: 12px 0 4px; }
.create-shots.pair { grid-template-columns: 1fr 1fr; }
@media (max-width: 560px) { .create-shots.pair { grid-template-columns: 1fr; } }
.create-shot {
  display: block; border: 1px solid var(--border); border-radius: 8px;
  overflow: hidden; background: var(--bg2); text-decoration: none;
}
.create-shot img { display: block; width: 100%; max-height: 260px; object-fit: cover; object-position: top center; }
.create-shot-cap {
  display: block; padding: 6px 10px; font-size: 11px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text2); border-top: 1px solid var(--border);
}
.create-shot:hover { border-color: var(--amber); }
.create-shot:hover .create-shot-cap { color: var(--amber); }

/* Why a control is disabled, beside the control rather than in a tooltip.
   A greyed-out switch whose reason is hidden reads as broken (#69, #70). */
/* The auto-update reason sits beside its control, right-aligned. The
   BitTorrent one sits under the row's own text, where a sentence belongs, so
   it reads left and takes the width the text column already has. */
.ms-hint.au-why {
  margin-top: 4px;
  max-width: 34ch;
  text-align: right;
  line-height: 1.35;
}
.ms-hint.bt-why {
  margin-top: 4px;
  line-height: 1.35;
  color: var(--amber);
}

/* The library order control, sharing the first section header's line with the
   view toggle. Quiet until touched: it is a preference, not an action. It
   reads as text the heading's size (a sort mark and the order it is in); the
   native select lies over it unseen and takes the tap. 16px on the select
   keeps iOS from zooming when it opens. */
.lib-sort-wrap {
  position: relative; display: inline-flex; align-items: center; gap: 5px;
  margin-inline-end: 6px; padding: 2px 4px; border-radius: 5px;
  font-size: 12px; font-weight: 500; letter-spacing: 0; text-transform: none;
  color: var(--text2); -webkit-text-fill-color: initial; cursor: pointer; white-space: nowrap;
}
.lib-sort-wrap:hover, .lib-sort-wrap:focus-within { color: var(--text); }
.lib-sort-wrap:focus-within { outline: 2px solid var(--amber); outline-offset: 1px; }
.lib-sort-wrap .lib-sort {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: pointer; font-size: 16px; border: 0;
}

/* On a touch screen the home page's and the search's small controls take a
   finger (44px): the order and view controls grow to it, the marks that
   close or clear keep their look and gain an invisible margin. */
@media (pointer: coarse) {
  .lib-sort-wrap { min-height: 44px; }
  .lib-view-btn { width: 44px; height: 42px; }
  /* No hover to reveal it on a phone: the × is there in full, finger-sized. */
  .sg-forget { width: 40px; height: 40px; opacity: 1; }
  .sg-recent-clear, .result-more { min-height: 40px; }
  .find-bar .find-step, .find-bar .find-close { width: 40px; height: 40px; }
  .dc-dismiss, .lang-banner-dismiss, .search-chip-x { position: relative; }
  .dc-dismiss::after, .lang-banner-dismiss::after, .search-chip-x::after, .topbar-search .search-help::after {
    content: ''; position: absolute; inset: -12px;
  }
}

/* Settings > Creator: an engine's version is a quiet link to what it is. */
.cr-ver { color: var(--text2); text-decoration: none; border-bottom: 1px dotted var(--border); font-variant-numeric: tabular-nums; }
.cr-ver:hover, .cr-ver:focus-visible { color: var(--amber); border-bottom-color: var(--amber); }
.cr-detail { margin: -4px 0 8px; font-size: 12px; text-align: end; }
