/* 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: 100vh; 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: 100vh; 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); }

/* ---------- 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; }

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);
}
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 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 {
  display: flex; gap: var(--space-1); border-bottom: 1px solid var(--rule); flex-wrap: wrap;
}
.unit-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 { color: var(--ink); }
.unit-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); }
.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; }
