/* Corpus for Thelemica -- shared stylesheet ("Ink & Vellum", Phase 17).
   Self-hosted, linked, and HTTP-cacheable. Every template extends
   base.html, so this one file is the whole app's visual system. */

/* ---------- self-hosted type (never a live Google Fonts CDN link --
   see Phase 15B's plan notes on the privacy/GDPR reasoning) ---------- */
@font-face {
  font-family: 'Fraunces';
  src: url('/static/fonts/Fraunces-Variable.woff2') format('woff2-variations');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Spectral';
  src: url('/static/fonts/Spectral-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Spectral';
  src: url('/static/fonts/Spectral-Italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Spectral';
  src: url('/static/fonts/Spectral-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('/static/fonts/IBMPlexMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('/static/fonts/IBMPlexMono-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

:root {
  --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Helvetica, Arial, sans-serif;
  --font-display: 'Fraunces', Georgia, serif;
  --font-serif: 'Spectral', Georgia, "Times New Roman", serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --text-xs: 0.75rem; --text-sm: 0.8125rem; --text-base: 0.9375rem;
  --text-lg: 1.0625rem; --text-xl: 1.375rem; --text-2xl: 1.8rem;
  /* Reserved for the dashboard's single hero figure -- not a general
     display size, one deliberate arrival-statement use only. */
  --text-3xl: 4rem;

  --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem;
  --space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem; --space-10: 2.5rem;

  --radius-sm: 6px; --radius-md: 8px; --radius-lg: 10px; --radius-full: 999px;
  --sidebar-w: 264px;

  /* "Ink & Vellum" -- a restrained paper/ink neutral base with a single
     indigo/violet-blue accent (evoking Nuit, the night-sky goddess who
     opens Liber AL, rather than fire -- a deliberate break from the
     "embers" palette this replaces). Two accent tones (bright for
     text/links, deeper for solid fills -- a single hex can't hit 4.5:1
     contrast as both at once), verdigris reserved for the
     validated/settled state, and a danger red fully decoupled from the
     accent hue. Every pairing below is WCAG-verified, both themes. */
  --bg: #f4f1ea; --panel: #ffffff; --panel-2: #ece8dd; --ink: #17171b; --ink-soft: #63636c;
  --rule: #ddd7c8; --accent: #322f6d; --accent-strong: #201d4c; --accent-rgb: 50, 47, 109;
  --draft: #4f4f58; --proofread: var(--accent); --validated: #2f6a5a; --danger: #a3271e;
  --shadow-sm: 0 1px 2px color-mix(in srgb, var(--ink) 10%, transparent);
  --shadow-md: 0 12px 32px -12px color-mix(in srgb, var(--ink) 26%, transparent);
  /* Tint used by .badge.proofread/.assigned/.flag/.rejected's background
     (color-mix against --accent/--danger). 14% clears WCAG AA 4.5:1 in
     this (light) theme, but the dark theme's brighter --accent/--danger
     values need a lower tint to still clear 4.5:1 against --panel --
     overridden per-theme below, same pattern as every other token here. */
  --badge-tint: 14%;

  /* Phase 20: per-annotation-layer token colors, requested directly
     ("we're not constrained by the theme itself... I like the 'boxed'
     feature of the WordPress version") -- a deliberate, explicit
     departure from Phase 19's original "no per-layer color" restraint,
     scoped to Annotation's word tokens only. Four hues rotated well away
     from --accent (indigo-violet), --validated (teal-green), and
     --danger (red) so none of the three already-meaningful colors get
     reused for something new: structural=blue, discourse=rose,
     editorial=olive/sage, linguistic=amber. */
  --layer-structural: #4a6fa5; --layer-discourse: #a54a6f;
  --layer-editorial: #6f7d4a; --layer-linguistic: #a5794a;
  /* Per-category coloring within the linguistic layer only -- requested
     directly, comparing against a WordPress-plugin screenshot that colored
     individual grammatical categories, not just one flat layer color.
     Scoped to linguistic because it's the only layer with a fixed, known
     vocabulary at real scale (the 17 seeded Universal Dependencies tags);
     structural/discourse/editorial categories are open-ended and user-
     created, so they keep their single per-layer color unchanged. 17 fully
     distinct hues would be unparseable at word-token size, so the 17 tags
     are grouped into 5 grammatical families (nominal/verbal/modifier/
     function/other -- see _LINGUISTIC_CATEGORY_COLOR_FAMILY in app.py),
     each rotated well clear of every hue already in use above. */
  --cat-nominal: #a29349; --cat-verbal: #49a250;
  --cat-modifier: #4990a2; --cat-function: #9349a2; --cat-other: #796b5d;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #131316; --panel: #1c1c22; --panel-2: #232329; --ink: #ece9e2; --ink-soft: #9d9da6;
    --rule: #303038; --accent: #8b87e6; --accent-strong: #6f6bd1; --accent-rgb: 139, 135, 230;
    --draft: #b7b7c0; --proofread: var(--accent); --validated: #5fbfa8; --danger: #e5675f;
    /* Lower than the light theme's 14% -- these brighter accent/danger
       values need less tint to stay under WCAG AA 4.5:1 against --panel;
       14% here measured 4.42:1/4.33:1 (fails), 10% measures ~4.6-4.7:1. */
    --badge-tint: 10%;
    --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
    --shadow-md: 0 16px 40px -14px rgba(0,0,0,0.6);
    --layer-structural: #7fa8d9; --layer-discourse: #d97fa0;
    --layer-editorial: #a3b87f; --layer-linguistic: #d9a86f;
    --cat-nominal: #d3c688; --cat-verbal: #88d38e;
    --cat-modifier: #88c4d3; --cat-function: #c688d3; --cat-other: #b4a89c;
  }
}
/* Manual override via the appearance control near the avatar menu --
   stamped as data-theme on <html> by base.html's inline head script, and
   must win over the prefers-color-scheme query in both directions (a
   light override on a dark OS, and vice versa). */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #131316; --panel: #1c1c22; --panel-2: #232329; --ink: #ece9e2; --ink-soft: #9d9da6;
  --rule: #303038; --accent: #8b87e6; --accent-strong: #6f6bd1; --accent-rgb: 139, 135, 230;
  --draft: #b7b7c0; --proofread: var(--accent); --validated: #5fbfa8; --danger: #e5675f;
  --badge-tint: 10%;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
  --shadow-md: 0 16px 40px -14px rgba(0,0,0,0.6);
  --layer-structural: #7fa8d9; --layer-discourse: #d97fa0;
  --layer-editorial: #a3b87f; --layer-linguistic: #d9a86f;
  --cat-nominal: #d3c688; --cat-verbal: #88d38e;
  --cat-modifier: #88c4d3; --cat-function: #c688d3; --cat-other: #b4a89c;
}
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f1ea; --panel: #ffffff; --panel-2: #ece8dd; --ink: #17171b; --ink-soft: #63636c;
  --rule: #ddd7c8; --accent: #322f6d; --accent-strong: #201d4c; --accent-rgb: 50, 47, 109;
  --draft: #4f4f58; --proofread: var(--accent); --validated: #2f6a5a; --danger: #a3271e;
  --shadow-sm: 0 1px 2px color-mix(in srgb, var(--ink) 10%, transparent);
  --shadow-md: 0 12px 32px -12px color-mix(in srgb, var(--ink) 26%, transparent);
  --layer-structural: #4a6fa5; --layer-discourse: #a54a6f;
  --layer-editorial: #6f7d4a; --layer-linguistic: #a5794a;
  /* Per-category coloring within the linguistic layer only -- requested
     directly, comparing against a WordPress-plugin screenshot that colored
     individual grammatical categories, not just one flat layer color.
     Scoped to linguistic because it's the only layer with a fixed, known
     vocabulary at real scale (the 17 seeded Universal Dependencies tags);
     structural/discourse/editorial categories are open-ended and user-
     created, so they keep their single per-layer color unchanged. 17 fully
     distinct hues would be unparseable at word-token size, so the 17 tags
     are grouped into 5 grammatical families (nominal/verbal/modifier/
     function/other -- see _LINGUISTIC_CATEGORY_COLOR_FAMILY in app.py),
     each rotated well clear of every hue already in use above. */
  --cat-nominal: #a29349; --cat-verbal: #49a250;
  --cat-modifier: #4990a2; --cat-function: #9349a2; --cat-other: #796b5d;
}

* { box-sizing: border-box; }
html { color-scheme: light dark; }
body {
  background: var(--panel); color: var(--ink); margin: 0;
  font-family: var(--font-ui); font-size: var(--text-base); line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- shell + sidebar ---------- */
.shell { display: flex; min-height: 100dvh; align-items: flex-start; }

.sidebar {
  width: var(--sidebar-w); flex-shrink: 0; background: var(--bg); border-right: 1px solid var(--rule);
  display: flex; flex-direction: column; padding: 22px 16px;
  position: sticky; top: 0; height: 100dvh; overflow-y: auto;
}
.brand {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 0 var(--space-2) 20px; margin-bottom: 18px; border-bottom: 1px solid var(--rule);
}
.brand:hover { text-decoration: none; }
.brand-mark { font-family: var(--font-display); font-size: 1.375rem; font-weight: 600; color: var(--ink); }
.brand-sub { font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }

.nav-group { margin-bottom: 4px; }
.nav-label {
  font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-soft); padding: 14px 10px 6px;
}
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: var(--space-2) 10px; border-radius: var(--radius-sm);
  font-size: 0.84375rem; font-weight: 500; color: var(--ink-soft); margin: 1px 0;
  transition: background-color 120ms ease, color 120ms ease;
}
.nav-item svg { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.8; }
.nav-item:hover { background: var(--panel-2); color: var(--ink); text-decoration: none; }
.nav-item.active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); font-weight: 650; }
.nav-item.active svg { opacity: 1; }
.nav-item .nav-badge {
  margin-left: auto; font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 700;
  background: color-mix(in srgb, var(--danger) 85%, black); color: #fff; border-radius: var(--radius-full);
  min-width: 15px; height: 15px; padding: 0 4px; display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}

.sidebar-foot {
  margin-top: auto; padding-top: 14px; border-top: 1px solid var(--rule);
  display: flex; align-items: center; gap: 10px; position: relative;
}
.acct-name { font-size: 0.78125rem; font-weight: 600; line-height: 1.25; }
.acct-role { font-size: 0.65625rem; color: var(--ink-soft); text-transform: capitalize; }

.account-menu { min-width: 0; }
.account-menu summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.account-menu summary::-webkit-details-marker { display: none; }
.avatar-btn { display: flex; align-items: center; gap: var(--space-2); min-width: 0; padding: var(--space-1); border-radius: var(--radius-sm); transition: background-color 120ms ease; flex: 1; }
.account-menu[open] .avatar-btn, .avatar-btn:hover { background: var(--panel-2); }
.avatar {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  background: var(--accent-strong); color: #fff; font-size: 0.72rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
}
.acct-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chevron { width: 13px; height: 13px; color: var(--ink-soft); transition: transform 150ms ease; flex-shrink: 0; }
.account-menu[open] .chevron { transform: rotate(180deg); }

.dropdown {
  position: absolute; bottom: calc(100% + var(--space-2)); left: 0; right: 0; min-width: 13rem;
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md); padding: var(--space-2); z-index: 30;
}
.dropdown-header { padding: var(--space-2) var(--space-3); font-size: var(--text-xs); color: var(--ink-soft); border-bottom: 1px solid var(--rule); margin-bottom: var(--space-1); }
.dropdown a, .dropdown button {
  display: block; width: 100%; text-align: left; font: inherit; font-size: var(--text-sm);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  background: none; border: none; color: var(--ink); cursor: pointer;
}
.dropdown a:hover, .dropdown button:hover { background: var(--panel-2); text-decoration: none; }
.dropdown form { margin: 0; }

.theme-switch {
  display: flex; gap: 2px; padding: 2px; margin: var(--space-1) var(--space-2) var(--space-2);
  background: var(--panel-2); border-radius: var(--radius-sm);
}
.theme-switch button {
  flex: 1; display: flex; align-items: center; justify-content: center;
  padding: var(--space-1) 0; border-radius: calc(var(--radius-sm) - 2px);
  color: var(--ink-soft); transition: background-color 120ms ease, color 120ms ease;
}
.theme-switch button:hover { background: color-mix(in srgb, var(--ink) 9%, transparent); }
.theme-switch button.active { background: var(--panel); color: var(--accent); box-shadow: var(--shadow-sm); }
.theme-switch svg { width: 15px; height: 15px; }

/* ---------- masthead ---------- */
main { flex: 1; min-width: 0; }
.masthead { padding: 38px 48px 0; }
.eyebrow {
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 6px;
}
.eyebrow a { color: inherit; }
.masthead h1 {
  font-family: var(--font-display); font-size: 2.5rem; font-weight: 600; letter-spacing: -0.015em;
  margin: 0 0 8px; text-wrap: balance;
}
.masthead .dek {
  font-family: var(--font-serif); font-size: 1.05rem; font-style: italic; color: var(--ink-soft);
  max-width: 46rem; margin: 0 0 20px;
}
.masthead-rule { border: none; border-top: 2px solid var(--ink); margin: 0 0 28px; }

/* Pipeline stepper (2026-07-30 full-audit UX fix) -- replaces the old
   prose dek on section.html/section_enrich.html/section_annotate.html.
   Deliberately reuses .eyebrow's own mono/uppercase/tracked treatment
   rather than inventing a new visual language for "here's where you are
   in the pipeline" -- it's the same kind of wayfinding the breadcrumb
   above it already does. */
.pipeline-stepper {
  font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
  margin: 0 0 20px;
}
.pipeline-stepper .step { color: var(--ink-soft); text-decoration: none; }
.pipeline-stepper .step:hover { color: var(--accent); }
.pipeline-stepper .step.active { color: var(--ink); font-weight: 600; }
.pipeline-stepper .step-sep { color: var(--rule); margin: 0 8px; }

.content { padding: 0 48px 60px; max-width: 78rem; }

h1 { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: 600; margin: 0 0 var(--space-1); letter-spacing: -0.01em; }
h2 { font-family: var(--font-display); font-size: 1.2rem; font-weight: 600; margin: 0; }
.muted { color: var(--ink-soft); }
.card {
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius-lg);
}

/* Hairline-divider stream, no box -- for lists that were boxed for no
   reason (activity feeds, assignment lists, corrections inbox). Rows
   inside keep their own hover-tint/divider styling; this just removes
   the redundant .card wrapper those streams don't need. */
.section-block { border-top: 1px solid var(--rule); padding-top: var(--space-4); }
.section-block + .section-block { margin-top: var(--space-6); }

/* ---------- dashboard ---------- */
.hero-row { display: flex; gap: 56px; align-items: flex-end; margin: var(--space-6) 0 var(--space-2); flex-wrap: wrap; }
.hero-stat { flex-shrink: 0; }
.hero-stat-num {
  font-family: var(--font-display); font-size: var(--text-3xl); font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.hero-stat-label {
  color: var(--ink-soft); font-size: 0.6875rem; margin-top: var(--space-2);
  font-family: var(--font-mono); letter-spacing: 0.04em; text-transform: uppercase;
}
.hero-row .card { flex: 1; min-width: 220px; max-width: 460px; padding: var(--space-4) var(--space-5); }

/* Flags -- a marginal note, not a boxed alert. */
.flags-strip {
  display: flex; align-items: baseline; gap: var(--space-4); flex-wrap: wrap;
  margin: var(--space-5) 0; padding: var(--space-3) 0;
  border-top: 1px solid var(--danger); border-bottom: 1px solid var(--rule);
}
.flags-strip > div:first-child { flex: 1; font-size: var(--text-sm); }
.flags-strip strong { font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--danger); font-weight: 700; }

/* The app's three "start work here" entry points -- ledger columns, not
   boxed cards: a border-top spanning the row, hairline dividers between
   columns, no shadow/elevation. */
.pipeline-row {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; margin: 8px 0 var(--space-8);
  border-top: 1px solid var(--ink);
}
@media (max-width: 900px) { .pipeline-row { grid-template-columns: 1fr; border-top: none; } }
.pipeline-card {
  padding: var(--space-5) var(--space-5) var(--space-6); border-right: 1px solid var(--rule);
}
@media (max-width: 900px) { .pipeline-card { border-right: none; border-top: 1px solid var(--ink); } }
.pipeline-row .pipeline-card:last-child { border-right: none; }
.pipeline-card h2 { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.pipeline-card.muted { opacity: 0.7; }

.stat-card { padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); }
.stat-figures { display: flex; gap: var(--space-6); flex-wrap: wrap; }
.stat-figure-num {
  font-family: var(--font-display); font-size: 1.8rem; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat-figure-lbl { font-size: var(--text-xs); color: var(--ink-soft); margin-top: var(--space-1); text-transform: uppercase; letter-spacing: 0.04em; }

.progress-bar {
  display: flex; height: 6px; width: 100%; border-radius: var(--radius-full);
  overflow: hidden; background: var(--rule);
}
.progress-bar .seg { height: 100%; border-right: 1px solid var(--bg); }
.progress-bar .seg:last-child { border-right: none; }
.progress-bar .seg.draft { background: var(--draft); }
.progress-bar .seg.proofread { background: var(--proofread); }
.progress-bar .seg.validated { background: var(--validated); }

.activity-row {
  display: flex; gap: var(--space-4); align-items: baseline; padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--rule); color: var(--ink); font-size: var(--text-sm);
}
.activity-row:last-child { border-bottom: none; }
.activity-row:hover { background: var(--panel-2); text-decoration: none; }

/* ---------- ledger tables (applies to every data table in the app) ---------- */
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--rule); vertical-align: top; }
th {
  font-family: var(--font-mono); font-size: 0.65625rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-soft); font-weight: 600; padding-bottom: 8px; border-bottom: 1.5px solid var(--ink);
}
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background-color 120ms ease; scroll-margin-top: var(--space-4); }
tbody tr:hover td { background: var(--panel-2); }
tbody tr.unit-focused td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tbody tr.unit-focused td { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.count { font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

/* ---------- status chips (flat tag, not a dot-badge) ---------- */
.badge {
  display: inline-block; font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 3px 9px; border-radius: 3px; white-space: nowrap; font-weight: 600;
}
.badge.draft { color: var(--draft); background: var(--panel-2); }
.badge.proofread { color: var(--proofread); background: color-mix(in srgb, var(--accent) var(--badge-tint), transparent); }
.badge.validated { color: var(--validated); background: color-mix(in srgb, var(--validated) 16%, transparent); }
.badge.assigned { color: var(--accent); background: color-mix(in srgb, var(--accent) var(--badge-tint), transparent); }
.badge.unassigned { color: var(--ink-soft); background: var(--panel-2); }
.badge.approved { color: var(--validated); background: color-mix(in srgb, var(--validated) 16%, transparent); }
.badge.rejected { color: var(--danger); background: color-mix(in srgb, var(--danger) var(--badge-tint), transparent); }

.badge.part { color: var(--ink-soft); background: var(--panel-2); }

/* A work published as part of another (m0046): the register lists it directly
   under its head, indented, so the relation is visible in the shape of the
   table rather than only in a label. The title cell, not the first: the
   register opens each row with its number. */
.part-of > td.work-title { padding-left: var(--space-6); position: relative; }
.part-of > td.work-title::before {
  content: ""; position: absolute; left: var(--space-3); top: 0; bottom: 50%;
  border-left: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  width: var(--space-2); border-bottom-left-radius: 3px;
}

/* ---------- A-Z quick-jump bar (glossary index) ---------- */
.alphabet-bar {
  display: flex; flex-wrap: wrap; gap: var(--space-1); margin: var(--space-3) 0 0;
  padding: var(--space-2) 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
}
.alphabet-bar a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; border-radius: var(--radius-sm);
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 600;
  color: var(--ink); text-decoration: none; transition: background-color 120ms ease;
}
.alphabet-bar a:hover { background: var(--panel-2); text-decoration: none; }
.alphabet-bar a.muted { color: var(--ink-soft); opacity: 0.55; }
.alphabet-bar a.active { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }

/* Flat fill, ink by default -- color is reserved for status/meaning, not
   spent on every action button. .btn.danger is the deliberate exception:
   an action that needs to read as urgent (e.g. "Review" from an open
   flags note). */
.btn {
  display: inline-flex; align-items: center; gap: var(--space-2); font: inherit; font-size: var(--text-sm); font-weight: 600;
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-md); border: 1px solid var(--ink);
  background: var(--ink); color: var(--bg); cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background-color 160ms ease, box-shadow 160ms ease, transform 120ms ease, opacity 160ms ease;
}
.btn:hover { opacity: 0.85; text-decoration: none; box-shadow: var(--shadow-md); }
.btn:active { transform: translateY(1px); }
.btn.secondary { background: none; border-color: var(--rule); color: var(--ink); box-shadow: none; }
.btn.secondary:hover { background: var(--panel-2); border-color: var(--rule); box-shadow: none; opacity: 1; }
.btn.small { padding: var(--space-1) var(--space-3); font-size: var(--text-xs); }
.btn.danger { border-color: var(--danger); background: var(--danger); color: #fff; }
/* Submit-in-flight state for the small set of actions that run a real
   corpus-wide scan server-side (import, tagging runs, bulk approve/reject,
   etc.) -- see base.html's delegated submit listener, which sets this via
   the `disabled` attribute itself rather than a class, so a slow request
   that never comes back still reads as "working" instead of silently
   reverting to clickable. */
.btn:disabled { opacity: 0.6; cursor: default; pointer-events: none; }

textarea, input[type=text], input[type=password], select {
  font: inherit; font-size: var(--text-sm); width: 100%; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--rule); border-radius: var(--radius-md); background: var(--panel-2); color: var(--ink);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
textarea:focus, input:focus, select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}
/* :user-invalid (not bare :invalid) so a required field only flags red once
   the visitor has actually interacted with it, not on first paint before
   they've had a chance to fill anything in. Same focus-ring treatment as
   above, just in --danger instead of --accent. */
textarea:user-invalid, input:user-invalid, select:user-invalid { border-color: var(--danger); }
textarea:user-invalid:focus, input:user-invalid:focus, select:user-invalid:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 20%, transparent);
}
textarea { resize: vertical; min-height: 2.6rem; font-family: var(--font-serif); font-size: var(--text-base); line-height: 1.5; }
.row-form { display: flex; gap: var(--space-2); align-items: flex-start; }
.row-form textarea { flex: 1; }
.toolbar { display: flex; gap: var(--space-3); align-items: center; margin: var(--space-5) 0; flex-wrap: wrap; }
.field { margin-bottom: var(--space-4); }
.field label { display: block; font-family: var(--font-ui); font-size: var(--text-xs); font-weight: 650; margin-bottom: var(--space-1); color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.03em; }
.field-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0 var(--space-4); margin-bottom: var(--space-2);
}
.field-grid .field { margin-bottom: var(--space-4); }
.field-group-label {
  grid-column: 1 / -1; font-family: var(--font-ui); font-size: var(--text-xs); font-weight: 650; color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: 0.04em;
  margin: var(--space-2) 0 var(--space-1); padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--rule);
}
.field-group-label:first-child { margin-top: 0; }
.bib-fields { margin-top: var(--space-1); }

/* One credited name as three aligned columns -- role, given, family --
   plus the remove control. A grid rather than flex so every row's columns
   line up with each other however long the chosen role's label is, and so
   a list of contributors reads as a table instead of a stack of blocks.
   Controls are shrunk to the compact scale here: a witness can easily have
   half a dozen credits, and at full form-control height the section
   dominated the panel. */
.contributor-row {
  display: grid; grid-template-columns: 10rem 1fr 1fr auto;
  gap: var(--space-2); align-items: center; margin-bottom: var(--space-1);
}
.contributor-row select,
.contributor-row input {
  min-width: 0; padding: var(--space-1) var(--space-2); font-size: var(--text-sm);
}
.contributor-row button { padding: var(--space-1) var(--space-2); line-height: 1; }
/* Only stack on a genuinely narrow viewport -- the whole point above is
   that these stay side by side at any ordinary width. */
@media (max-width: 620px) {
  .contributor-row { grid-template-columns: 1fr 1fr auto; }
  .contributor-row select { grid-column: 1 / -1; }
}
.required-mark { color: var(--accent); margin-left: 0.2em; }
.form-panel {
  margin-top: var(--space-3); padding: var(--space-5);
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius-lg);
}
.breadcrumb { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); margin-bottom: var(--space-2); }
.breadcrumb a { color: var(--ink-soft); }

/* Plain wrapping paragraph, not flex -- a flag-note's text routinely
   embeds an inline <a> (e.g. "...on the Proofing page, any..."), and
   flexbox wraps each text-node/element run as its OWN flex item, which
   breaks the sentence into separate boxes instead of letting it flow. */
.flag-note {
  margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3); font-size: var(--text-sm); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--draft) 12%, transparent);
  border-left: 3px solid var(--draft);
}
.correction-card {
  margin: var(--space-2) 0 0; padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--rule));
  background: color-mix(in srgb, var(--accent) 7%, var(--panel));
}
.correction-card .meta { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); margin-bottom: var(--space-2); }
/* Bare selector (not scoped to .correction-card .meta) so it's reusable
   anywhere an "automated: ..." or similar tag is needed outside a
   correction card too -- e.g. the Annotation review UI's origin
   indicator, a single-line list row rather than a full card. */
.qa-tag { color: var(--accent); font-weight: 650; text-transform: uppercase; letter-spacing: 0.02em; font-size: 0.7rem; }
.correction-card .proposed { font-family: var(--font-serif); font-size: var(--text-base); margin-bottom: var(--space-2); white-space: pre-line; }
.correction-card .reason { font-size: var(--text-sm); color: var(--ink-soft); margin-bottom: var(--space-3); }
.correction-card .actions { display: flex; gap: var(--space-2); }
.correction-card.documented-variant {
  border-color: color-mix(in srgb, var(--validated) 40%, var(--rule));
  background: color-mix(in srgb, var(--validated) 7%, var(--panel));
}
.correction-card.annotation-rejected {
  border-color: color-mix(in srgb, var(--danger) 40%, var(--rule));
  background: color-mix(in srgb, var(--danger) 7%, var(--panel));
}
/* section_annotate.html: a draft annotation card matches the color of its
   own word-tok in the verse text above (requested directly), using the
   same layer/category-family tokens and the same 40%/7% mix ratios as
   .correction-card's own accent/documented-variant/annotation-rejected
   rules -- approved/rejected cards keep those existing status-color
   overrides untouched, since those classes are mutually exclusive with
   the layer/cat classes below (see section_annotate.html's
   annotation_card macro). */
.correction-card.layer-structural { border-color: color-mix(in srgb, var(--layer-structural) 40%, var(--rule)); background: color-mix(in srgb, var(--layer-structural) 7%, var(--panel)); }
.correction-card.layer-discourse { border-color: color-mix(in srgb, var(--layer-discourse) 40%, var(--rule)); background: color-mix(in srgb, var(--layer-discourse) 7%, var(--panel)); }
.correction-card.layer-editorial { border-color: color-mix(in srgb, var(--layer-editorial) 40%, var(--rule)); background: color-mix(in srgb, var(--layer-editorial) 7%, var(--panel)); }
.correction-card.layer-linguistic { border-color: color-mix(in srgb, var(--layer-linguistic) 40%, var(--rule)); background: color-mix(in srgb, var(--layer-linguistic) 7%, var(--panel)); }
.correction-card.cat-nominal { border-color: color-mix(in srgb, var(--cat-nominal) 40%, var(--rule)); background: color-mix(in srgb, var(--cat-nominal) 7%, var(--panel)); }
.correction-card.cat-verbal { border-color: color-mix(in srgb, var(--cat-verbal) 40%, var(--rule)); background: color-mix(in srgb, var(--cat-verbal) 7%, var(--panel)); }
.correction-card.cat-modifier { border-color: color-mix(in srgb, var(--cat-modifier) 40%, var(--rule)); background: color-mix(in srgb, var(--cat-modifier) 7%, var(--panel)); }
.correction-card.cat-function { border-color: color-mix(in srgb, var(--cat-function) 40%, var(--rule)); background: color-mix(in srgb, var(--cat-function) 7%, var(--panel)); }
.correction-card.cat-other { border-color: color-mix(in srgb, var(--cat-other) 40%, var(--rule)); background: color-mix(in srgb, var(--cat-other) 7%, var(--panel)); }
.qa-tag.documented-tag { color: var(--validated); }
.reviewer-line { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); margin-top: var(--space-2); }
.history-entry { padding: var(--space-2) 0; border-bottom: 1px dashed var(--rule); }
.history-entry:last-child { border-bottom: none; }
.history-entry blockquote {
  margin: var(--space-1) 0 0; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  border-left: 3px solid var(--rule); background: color-mix(in srgb, var(--ink) 4%, var(--panel));
  font-size: var(--text-sm); font-style: italic;
}
mark { background: color-mix(in srgb, var(--accent) 35%, transparent); color: var(--ink); border-radius: 3px; padding: 0 2px; }
.xref-card {
  margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  border-left: 3px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, var(--panel)); font-size: var(--text-sm);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
}
.xref-card .direction {
  display: inline-flex; align-items: center; font-size: 0.7rem; font-weight: 650;
  text-transform: uppercase; letter-spacing: 0.02em; padding: 0.15rem 0.55rem; border-radius: var(--radius-full);
  color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); margin-right: var(--space-2);
}
/* A footnote the source prints against this unit (m0039, first filled by
   Liber 418's 837 of them). Status is carried by the LEFT RULE as well as by
   the word, because a page of the 10th Aethyr shows a dozen at once and
   "which of these still need me" has to read at a glance rather than by
   reading each label. The rule carries STATUS and nothing else -- whether a
   note is placed is a separate fact, and the meta line says it in words. */
.footnote-card {
  margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); font-size: var(--text-sm);
  border-left: 3px solid var(--draft);
  background: color-mix(in srgb, var(--draft) 6%, var(--panel));
}
.footnote-card.confirmed {
  border-left-color: var(--validated);
  background: color-mix(in srgb, var(--validated) 6%, var(--panel));
}
.footnote-card.rejected {
  border-left-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 6%, var(--panel));
  opacity: 0.72;
}
.footnote-card .meta {
  font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft);
  margin-bottom: var(--space-1); display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 0 var(--space-1);
}
.footnote-card .marker {
  font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums;
  padding: 0.1rem 0.45rem; border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}
.footnote-card .unplaced { font-style: italic; }
.footnote-card .note { font-family: var(--font-serif); white-space: pre-line; }
.footnote-card .actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-top: var(--space-2);
}
.footnote-card .actions form { display: flex; align-items: center; gap: var(--space-1); margin: 0; }

/* A unit's attached supplementary images (Enrichment tab) -- a tarot
   trump, a diagram, or a chapter whose original content is entirely an
   image. Thumbnails, not full-size: these are reference material inside
   a review row, not the page's main content. */
.unit-image-card {
  display: inline-block; vertical-align: top; margin: 0 var(--space-3) var(--space-3) 0;
  padding: var(--space-2); border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background: var(--panel-2); max-width: 12rem;
}
.unit-image-card img { display: block; max-width: 100%; max-height: 10rem; border-radius: var(--radius-sm); }

/* An anchored image spliced directly into a unit's own text (see
   app.py's _render_text_with_images and section_enrich.html's main text
   display) -- unlike .unit-image-card, this is the image at real size,
   scaled only to fit the text column, never cropped or height-capped,
   so its true aspect ratio is always preserved. */
.inline-unit-image { display: block; max-width: 100%; height: auto; margin: var(--space-3) auto; border-radius: var(--radius-sm); }

details.add-xref { margin-top: var(--space-2); font-size: var(--text-sm); }
details.add-xref summary { cursor: pointer; color: var(--ink-soft); font-size: var(--text-xs); }
details.add-xref summary:hover { color: var(--ink); }
details.add-xref .field { margin: var(--space-3) 0 var(--space-3); }
/* Per-row action group (the witnesses table's Edit/Remove pair). Edit is a
   <details> because it reveals an inline edit row, but a bare summary next
   to a bordered <button> reads as two unrelated controls -- so the summary
   takes .btn chrome and the marker is suppressed. The selector has to
   out-specify details.add-xref summary above, which sets its own muted
   colour and size. */
.row-actions { display: flex; gap: var(--space-2); align-items: center; }
.row-actions details, .row-actions form { margin: 0; }
.row-actions summary.btn {
  list-style: none; display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--text-xs); color: var(--ink);
  /* Fixed to fit its widest label ("Close") so swapping Edit <-> Close on
     toggle doesn't shift the Remove button beside it. */
  min-width: 7.5ch;
}
.row-actions summary.btn::-webkit-details-marker { display: none; }
.row-actions summary.btn:hover { color: var(--ink); }

.xref-form-row { display: flex; gap: var(--space-2); }
.xref-form-row > * { flex: 1; }
/* A submit button is sometimes a direct sibling in this row (glossary
   "Link", users "Set password", the correction-card "Add witness" form)
   -- without this, the row's default align-items:stretch plus flex:1
   above stretch the button to match the tallest field's height AND
   width, making it look like an oversized blank box instead of a button. */
.xref-form-row > button { flex: 0 0 auto; align-self: flex-end; }

/* ---------- diff-against-existing-text preview (Phase 10B) ---------- */
.diff-pair {
  margin: var(--space-2) 0 0; padding: var(--space-3); display: flex; gap: var(--space-3); align-items: flex-start;
  border-radius: var(--radius-md); border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--rule));
  background: color-mix(in srgb, var(--accent) 7%, var(--panel));
}
.diff-pair input[type=checkbox] { width: auto; margin-top: 0.3rem; flex-shrink: 0; }
.diff-pair .diff-cols { flex: 1; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); min-width: 0; }
.diff-col-label { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: var(--space-1); }
.diff-text { font-family: var(--font-serif); font-size: var(--text-base); white-space: pre-line; overflow-wrap: break-word; }
.diff-bucket { margin-top: var(--space-8); }
.diff-readonly-card {
  margin: var(--space-2) 0 0; padding: var(--space-3); border-radius: var(--radius-md); font-size: var(--text-sm);
  border: 1px solid var(--rule); background: color-mix(in srgb, var(--ink) 3%, var(--panel));
}
@media (max-width: 700px) { .diff-pair .diff-cols { grid-template-columns: 1fr; } }

/* ---------- per-unit action tabs (section.html / section_enrich.html) ---------- */
.unit-actions { margin-top: var(--space-3); }
.unit-tabs, .page-tabs {
  display: flex; gap: var(--space-1); border-bottom: 1px solid var(--rule); flex-wrap: wrap;
}
.unit-tab, .page-tab {
  font: inherit; font-family: var(--font-ui); font-size: var(--text-xs); font-weight: 600; color: var(--ink-soft);
  background: none; border: none; border-bottom: 2px solid transparent;
  padding: var(--space-2) var(--space-2) var(--space-1); margin-bottom: -1px; cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease;
}
.unit-tab:hover, .page-tab:hover { color: var(--ink); }
.unit-tab.active, .page-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.unit-panel { display: none; padding-top: var(--space-3); font-size: var(--text-sm); }
.unit-panel.active { display: block; }
.unit-panel .field { margin-bottom: var(--space-3); }
/* ---------- page-level tabs and the works register (works.html) ----------
   The same tab look as .unit-tabs, one size up. Twelve genres wrap on all but
   wide screens, so each tab carries its own rule rather than the bar: a
   selected tab's underline then always sits on a rule, whichever line it is
   on. The register's tabs only hide rows of one table; its script renumbers
   the rows left showing. */
.works-controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4) var(--space-3); margin-top: var(--space-5); }
.page-tabs { flex: 1 1 100%; border-bottom: none; column-gap: 0; row-gap: var(--space-2); }
.page-tab {
  font-size: var(--text-sm); margin-bottom: 0;
  padding: var(--space-2) var(--space-3) calc(var(--space-2) + 1px); border-bottom: 1px solid var(--rule);
}
.page-tab.active { padding-bottom: var(--space-2); border-bottom: 2px solid var(--accent); }
.page-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.page-tab .tab-count {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 500;
  color: var(--ink-soft); margin-left: var(--space-1);
}
.page-tab.active .tab-count { color: inherit; }
.works-search { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); flex: 0 1 34rem; }
.works-filter-label {
  font-family: var(--font-ui); font-size: var(--text-xs); font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft);
}
/* The house input, with a boundary that reads against the page in both
   themes: --rule alone is under 3:1 there. */
.works-filter {
  font: inherit; font-family: var(--font-ui); font-size: var(--text-sm); color: var(--ink);
  background: var(--panel-2); border: 1px solid var(--ink-soft); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3); flex: 1 1 14rem; min-width: 0;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.works-filter::placeholder { color: var(--ink-soft); opacity: 1; }
.works-shown { font-size: var(--text-xs); white-space: nowrap; }
.table-scroll { overflow-x: auto; margin-top: var(--space-4); }
.works-table .row-num { width: 3rem; font-variant-numeric: tabular-nums; }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.structure-block + .structure-block { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px dashed var(--rule); }

/* ---------- Annotation: one verse visible at a time (Phase 20) ----------
   Same display:none/.active mechanism as .unit-tabs/.unit-panel above,
   applied at a new structural level (which of up to ~174 units is shown,
   not which of 4 fixed tabs) -- a real trade-off, accepted directly:
   native browser find-in-page no longer searches a whole section's text,
   since only the active .unit-block is ever in the visible DOM. */
.annotate-layout { display: grid; grid-template-columns: 220px 1fr; gap: var(--space-5); margin-top: var(--space-5); align-items: start; }
@media (max-width: 820px) { .annotate-layout { grid-template-columns: 1fr; } }
.annotate-nav {
  display: flex; flex-direction: column; gap: 1px; position: sticky; top: var(--space-4);
  max-height: calc(100vh - var(--space-8)); overflow-y: auto;
  border: 1px solid var(--rule); border-radius: var(--radius-md); background: var(--panel); padding: var(--space-1);
}
.unit-nav-item {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2);
  font: inherit; font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft);
  background: none; border: none; border-radius: var(--radius-sm); text-align: left; cursor: pointer;
  padding: var(--space-2); transition: background-color 120ms ease, color 120ms ease;
}
.unit-nav-item:hover { background: var(--panel-2); color: var(--ink); }
.unit-nav-item.active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); font-weight: 650; }
.unit-nav-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.unit-block { display: none; }
.unit-block.active { display: block; }
.unit-block-label { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--ink-soft); margin: 0 0 var(--space-3); }

.selectable-text {
  font-family: var(--font-serif); font-size: var(--text-base); white-space: pre-line;
  user-select: none; cursor: default; margin-bottom: var(--space-2);
}
.selectable-text .word-tok {
  position: relative; cursor: pointer; border-radius: 4px; padding: 0 1px;
  border: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
}
/* A small filled dot marks a word whose covering tag is confirmed/approved
   -- requested directly: "we have no visual indicator that a word has
   been approved." Reuses --validated (this app's existing "settled/
   complete" color, the same one the Proofing pipeline's own validated
   stage uses) rather than a new hue -- draft/suggested stays the unmarked
   default. Shared verbatim between Annotation (an approved span_annotations
   row) and Enrichment's Glossary tab (a confirmed glossary_links row) --
   same "this one's settled" signal, same dot, not two competing ones. */
.selectable-text .word-tok.tag-approved::after {
  content: ""; position: absolute; top: -3px; right: -3px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--validated); border: 1px solid var(--panel);
}
.selectable-text .word-tok:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.selectable-text .word-tok.selected { background: color-mix(in srgb, var(--accent) 35%, transparent); color: var(--ink); }
/* Already linked to a glossary term -- boxed background-fill, requested
   directly ("we still don't have any way of seeing already linked
   words... color those... like we do in the annotation section").
   Previously underline-only AND confirmed-only, on the reasoning that
   Enrichment's Glossary tab has no "layer" concept to color-code by --
   true, but that's an argument against per-category colors, not against
   a box at all, and confirmed-only meant a real, live suggested link
   (e.g. "Pan") rendered completely unmarked until someone confirmed it --
   invisible in the exact view meant to surface it. Now covers any
   non-rejected link (mirrors section_annotate's own "any non-rejected
   span colors the word" rule -- see glossary_link_ranges_for_units);
   .tag-approved above is the only thing distinguishing confirmed from
   merely-suggested, matching how Annotation already distinguishes
   approved from draft. One color (--accent, this app's own single
   deliberate "active work" hue) either way, matching the .layer-*/.cat-*
   boxes below exactly rather than inventing a second visual language for
   the same "already tagged" idea. */
.selectable-text .word-tok.linked { text-decoration: none; background: color-mix(in srgb, var(--accent) 20%, var(--panel)); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
/* Already covered by a span annotation with no resolved layer color (a
   defensive fallback -- shouldn't normally happen, every span_annotations
   row has a non-null layer). Real layer distinction is the four
   .layer-* rules below -- Phase 20 deliberately reverses Phase 19's own
   "no per-layer color" restraint here, at the user's explicit direction
   ("we're not constrained by the theme itself... I like the 'boxed'
   feature of the WordPress version"). */
.selectable-text .word-tok.annotated { text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 1.5px; text-underline-offset: 3px; }
.selectable-text .word-tok.layer-structural,
.selectable-text .word-tok.layer-discourse,
.selectable-text .word-tok.layer-editorial,
.selectable-text .word-tok.layer-linguistic { text-decoration: none; }
.selectable-text .word-tok.layer-structural { background: color-mix(in srgb, var(--layer-structural) 20%, var(--panel)); border-color: color-mix(in srgb, var(--layer-structural) 45%, transparent); }
.selectable-text .word-tok.layer-discourse { background: color-mix(in srgb, var(--layer-discourse) 20%, var(--panel)); border-color: color-mix(in srgb, var(--layer-discourse) 45%, transparent); }
.selectable-text .word-tok.layer-editorial { background: color-mix(in srgb, var(--layer-editorial) 20%, var(--panel)); border-color: color-mix(in srgb, var(--layer-editorial) 45%, transparent); }
.selectable-text .word-tok.layer-linguistic { background: color-mix(in srgb, var(--layer-linguistic) 20%, var(--panel)); border-color: color-mix(in srgb, var(--layer-linguistic) 45%, transparent); }
/* Per-category coloring, linguistic layer only -- 17 seeded UD tags
   grouped into 5 grammatical families (see _LINGUISTIC_CATEGORY_COLOR_FAMILY
   in app.py). Declared after .layer-linguistic above so a word carrying
   BOTH classes (every automatically-tagged word does) resolves to its
   category color, not the flatter layer color -- .layer-linguistic stays
   the fallback for an uncategorized linguistic-layer row. */
.selectable-text .word-tok.cat-nominal,
.selectable-text .word-tok.cat-verbal,
.selectable-text .word-tok.cat-modifier,
.selectable-text .word-tok.cat-function,
.selectable-text .word-tok.cat-other { text-decoration: none; }
.selectable-text .word-tok.cat-nominal { background: color-mix(in srgb, var(--cat-nominal) 20%, var(--panel)); border-color: color-mix(in srgb, var(--cat-nominal) 45%, transparent); }
.selectable-text .word-tok.cat-verbal { background: color-mix(in srgb, var(--cat-verbal) 20%, var(--panel)); border-color: color-mix(in srgb, var(--cat-verbal) 45%, transparent); }
.selectable-text .word-tok.cat-modifier { background: color-mix(in srgb, var(--cat-modifier) 20%, var(--panel)); border-color: color-mix(in srgb, var(--cat-modifier) 45%, transparent); }
.selectable-text .word-tok.cat-function { background: color-mix(in srgb, var(--cat-function) 20%, var(--panel)); border-color: color-mix(in srgb, var(--cat-function) 45%, transparent); }
.selectable-text .word-tok.cat-other { background: color-mix(in srgb, var(--cat-other) 20%, var(--panel)); border-color: color-mix(in srgb, var(--cat-other) 45%, transparent); }
.selection-tools {
  margin: 0 0 var(--space-3); padding: var(--space-3);
  border: 1px solid var(--rule); border-radius: var(--radius-md); background: var(--panel-2);
}

@media (max-width: 860px) {
  .sidebar { position: static; height: auto; }
  .shell { flex-direction: column; }
  .masthead, .content { padding-left: 24px; padding-right: 24px; }
}

::selection { background: color-mix(in srgb, var(--accent) 30%, transparent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The browser's own `[hidden] { display: none }` is a plain UA-origin rule,
   so ANY author-origin `display` beats it -- compare_preview's pager set
   .hidden on .diff-pair elements that carry `display: flex` here, and the
   rows simply stayed visible with the pager reporting otherwise. This is
   the one place a global !important is the correct tool: `hidden` means
   hidden, and every element that opts into the attribute (selection tools,
   witness edit rows, the diff buckets) should get it without each having to
   remember to out-specify its own layout rule. Placed last so it wins on
   order too, not only on !important. */
[hidden] { display: none !important; }

/* ---------------------------------------------------------- facsimile regions
   The plate and its boxes are positioned entirely in percentages, because the
   geometry is stored as fractions of the page. Nothing here knows the scan's
   pixel size, which is what lets a hi-res rescan drop in without moving a box. */
.facsimile-layout {
  display: grid;
  grid-template-columns: minmax(320px, 1fr) minmax(280px, 26rem);
  gap: var(--space-5);
  align-items: start;
  margin-top: var(--space-4);
}
@media (max-width: 900px) { .facsimile-layout { grid-template-columns: 1fr; } }

.facsimile-plate {
  position: relative;
  line-height: 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--panel);
  touch-action: none;               /* pointer drags, not page scrolling */
}
.facsimile-plate img { width: 100%; height: auto; display: block; user-select: none; }

.facsimile-region {
  position: absolute;
  border: 2px solid var(--proofread);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  cursor: move;
}
.facsimile-region.confirmed { border-color: var(--validated);
  background: color-mix(in srgb, var(--validated) 10%, transparent); }
.facsimile-region.rejected { border-color: var(--rule); border-style: dotted;
  background: none; opacity: 0.5; }
/* A region with no box yet is drawn at a default position and must not look
   like a placement -- it is an invitation to drag, not a claim. */
.facsimile-region.unplaced { border-style: dashed; background: none; opacity: 0.55; }
.facsimile-region.is-selected { box-shadow: 0 0 0 2px var(--accent); z-index: 3; }

.facsimile-tag {
  position: absolute; top: 0; left: 0;
  font-size: 10px; line-height: 1.4;
  padding: 0 4px;
  background: var(--proofread); color: var(--bg);
  border-radius: 0 0 3px 0;
  white-space: nowrap;
}
.facsimile-region.confirmed .facsimile-tag { background: var(--validated); }

.facsimile-region .grip { position: absolute; background: transparent; }
.facsimile-region .grip.n { top: -4px; left: 0; right: 0; height: 9px; cursor: ns-resize; }
.facsimile-region .grip.s { bottom: -4px; left: 0; right: 0; height: 9px; cursor: ns-resize; }
.facsimile-region .grip.w { left: -4px; top: 0; bottom: 0; width: 9px; cursor: ew-resize; }
.facsimile-region .grip.e { right: -4px; top: 0; bottom: 0; width: 9px; cursor: ew-resize; }

.facsimile-list { display: flex; flex-direction: column; gap: var(--space-3); }
.facsimile-card { padding: var(--space-3); cursor: pointer; }
.facsimile-card.is-selected { border-color: var(--accent); }

/* Subdivision editor. The division sites sit between every pair of words, so
   they have to be reachable without being visible: an almost-invisible sliver
   that only resolves on hover or keyboard focus. A visible marker at every gap
   would make the verse unreadable, which is the one thing this page must not
   do -- the editor is reading for clause boundaries. */
.subdiv-unit {
  padding: var(--space-5); margin-bottom: var(--space-4);
}
.subdiv-unit:last-child { margin-bottom: 0; }
.subdiv-unit .toolbar { margin: 0 0 var(--space-3); }
.subdiv-text { line-height: 2.1; font-size: var(--text-md); }
.subdiv-point {
  display: inline-block; width: 0.55rem; margin: 0 -0.275rem; padding: 0;
  border: 0; background: none; color: var(--accent); cursor: pointer;
  font: inherit; line-height: 1; vertical-align: baseline; opacity: 0;
  transition: opacity 90ms ease;
}
.subdiv-point::before { content: "\21B5"; font-size: 0.8em; }
.subdiv-text:hover .subdiv-point { opacity: 0.28; }
.subdiv-point:hover, .subdiv-point:focus-visible { opacity: 1; outline: none; }
.subdiv-point:focus-visible { text-decoration: underline; }

.subdiv-mark {
  display: inline-block; vertical-align: super; font-size: 0.68em;
  font-weight: 600; letter-spacing: 0.02em; padding: 0 0.1rem;
  color: var(--accent);
}
.subdiv-mark.is-suggested { color: var(--muted); font-style: italic; }
.subdiv-mark.is-rejected { color: var(--muted); text-decoration: line-through; }

.subdiv-controls { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.subdiv-chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 0.15rem 0.45rem; border: 1px solid var(--rule); border-radius: var(--radius-sm);
  font-size: var(--text-xs);
}
.subdiv-chip .letter { font-weight: 700; }

/* A unit's letters, shown as row-number + letter pairs rather than a run of
   Hebrew characters -- see m0042: three independent letters in one string get a
   reading direction from bidi that they do not actually have. Each pair is its
   own inline-block so no two letters ever sit adjacent in the same run. */
.unit-letter {
  display: inline-block; margin-right: var(--space-2);
  font-size: var(--text-md); line-height: 1;
}
.unit-letter .row { color: var(--muted); font-size: 0.72em; margin-right: 0.15em; }

.letter-grid td, .letter-grid th { text-align: center; }
.letter-grid td.letter-cell { font-size: var(--text-lg); }
