/* ============================================================
   DocAI — Clean Minimal White Theme
   ============================================================ */

/* Hiding wins over layout, everywhere.
   The UA rule for [hidden] is `display: none` with no weight of its own, so any
   author rule that sets display — `display: grid` on a panel, `display: flex` on
   a row — silently beats it and the "hidden" element paints anyway. The same
   goes for the .hidden utility against an id or a more specific selector. That
   has now shipped to production twice and cost seven bugs, because the failure
   is invisible until the empty state renders.
   Declaring it once, with !important, removes the trap for every element: a new
   JS-toggled block no longer needs its own matching guard. Nothing may show a
   hidden element by assigning an inline style.display — remove the attribute or
   the class instead (no code does otherwise; see tests/test_hidden_utility.py).
   <details hidden> is covered too: `open` governs expansion, not visibility. */
[hidden],
.hidden {
  display: none !important;
}

:root {
  /* Interface sans — Instrument Sans everywhere, headings included (the
     Source Serif 4 → sans migration ended with the 2026-08 DocAI Rework pass
     by retiring the serif). Stated once; use the token. */
  --font-ui: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* Data mono — extracted values, amounts, confidence figures, IDs, code.
     A value set in mono reads as machine output, which is the product's
     honesty cue: extracted ≠ prose. */
  --font-mono: "IBM Plex Mono", "JetBrains Mono", "Consolas", monospace;

  /* --- Light theme (default) ---
     2026-08 DocAI Rework palette. Cooler, blue-tinted neutrals replace the
     old warm greys; the accent moves #4f46e5 → #5b45e0. Every replacement
     was contrast-checked against the surfaces it is used over — the pinned
     ratios are noted per token. */
  --bg: #f6f7fb;
  --surface: #ffffff;
  --surface-soft: #f2f4f9;
  /* Hover background for elements that rest on --surface-soft (secondary
     buttons, auth links, etc.). A uniform ~10/255 per-channel step darker in
     light mode / lighter in dark mode — clearly perceptible without reading
     as a state change. Never use --border-light as a hover background:
     it gives a near-invisible delta against --surface-soft.
     See also the note at .step-eyebrow::after. */
  --surface-soft-hover: #e8ebf3;
  --ink: #10131c;
  /* Secondary text. Clears 4.5:1 on all light backgrounds it sits over:
     6.15 on white, 5.59 on --surface-soft, 5.39 on --border-light, 5.74 on
     --bg, 5.37 on --accent-light-over-white — while still reading as
     secondary against --ink. */
  --ink-soft: #5a6178;
  --accent: #5b45e0;
  --accent-strong: #4938c7;
  --accent-light: rgba(91, 69, 224, 0.09);
  /* Accent border for selected/active outlines (mock --accentLine): stronger
     than --border, quieter than --accent solid. */
  --accent-border: rgba(91, 69, 224, 0.4);
  /* Accent split into a FILL pair (white text sits on it) and a TEXT value
     (it sits on a surface). Light mode needs no split — #5b45e0 clears 4.5:1
     both ways (6.23 filled, 5.66 as text on --surface-soft) — so these alias
     --accent here and are re-pointed under dark, where the brighter #7b68f0
     fails as a fill (4.13:1 with white). */
  --accent-cta: var(--accent);
  --accent-cta-hover: var(--accent-strong);
  --accent-text: var(--accent);
  --accent-warm: #d97706;
  --border: #e4e7f0;
  --border-light: #edf0f7;
  /* Stronger hairline for controls that must read as interactive against
     --border-filled containers (chips, segmented controls, phone frames). */
  --border-strong: #d3d8e6;
  --danger: #dc2626;
  --success: #0f9d63;
  /* Status TEXT tokens. --success/--accent-warm are tuned as fills/dots and
     FAIL 4.5:1 as small text on white and on tinted status backgrounds
     (#0f9d63 measures 3.49 on white). Use these darker variants for status
     TEXT only: the darker variants below retain AA contrast after translucent
     status tints are composited over the page background. */
  --success-text: #066b40;
  --warn-text: #81500f;
  --danger-text: #b91c1c; /* red-700: 6.5:1 on white, 5.8:1 on the danger tint */
  /* Success FILL for controls that carry white text, mirroring --accent-cta.
     --success is a dot/bar hue (3.49:1 with white) and --success-text follows
     the theme (bright in dark, where white on it is 2.0:1), so neither is safe
     under white. This one is deliberately theme-INDEPENDENT: white on #0b7a4c
     is 5.38:1 in both themes. */
  --success-cta: #0b7a4c;
  /* Status tint backgrounds (mock --okSoft/--warnSoft): pair with the status
     TEXT tokens above, never with the fill values. */
  --success-soft: rgba(15, 157, 99, 0.1);
  --warn-soft: rgba(169, 105, 26, 0.1);
  --danger-soft: rgba(220, 38, 38, 0.08);
  --radius: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --shadow: 0 1px 3px rgba(16,19,28,0.06), 0 1px 2px rgba(16,19,28,0.04);
  --shadow-lg: 0 20px 50px -24px rgba(16,19,28,0.22);

  /* Spacing scale — 4px base. Use for padding/margin/gap; off-scale values
     (6px, 10px, 14px…) are legacy and should migrate to a step when touched. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;

  /* Type scale — the only allowed font sizes (hero clamp() excepted).
     Steps sit on the dominant pre-scale values so the visual delta of the
     2026-07 consolidation stays under ~5% everywhere. */
  --text-2xs: 0.7rem;   /* micro badges, chips */
  --text-xs: 0.75rem;   /* captions, table meta */
  --text-sm: 0.82rem;   /* secondary UI text, buttons */
  --text-md: 0.875rem;  /* default control/table text */
  --text-base: 0.9rem;  /* body copy */
  --text-lg: 1rem;      /* emphasized body, small headings */
  --text-xl: 1.1rem;    /* section headings */
  --text-2xl: 1.25rem;  /* page headings */
  --text-3xl: 1.5rem;   /* display headings */
  --text-4xl: 1.75rem;  /* page H1 titles */
  --text-display: 2rem; /* key metrics, pricing */

  /* Aliases used by some legacy selectors */
  --text: var(--ink);
  --muted: var(--ink-soft);
  /* Used by per-page <style> blocks (workspace, team_share, invite). Defined
     once: --ink-soft is itself re-themed, and var() resolves at use time, so
     this alias tracks light/dark automatically. Previously undefined, it fell
     back to a hardcoded #666 that ignored dark mode. */
  --text-muted: var(--ink-soft);

  /* Finer-grained tokens (sidebar vs. card surface, secondary vs. disabled
     text, input fill) — no-op aliases in light mode so light theme is
     unchanged; re-themed to distinct values under [data-theme="dark"]. */
  --surface-sidebar: var(--surface);
  --surface-input: var(--surface);
  --ink-secondary: var(--ink-soft);
  --ink-disabled: #9ca3af;
}

/* --- Dark mode via explicit attribute --- */
/* 2026-08 DocAI Rework palette — the design's hero theme. Deeper, bluer
   ground (#0a0c12) with solid hairline borders instead of the old alpha
   borders, so stacked panels read as one continuous surface system. */
[data-theme="dark"] {
  --bg: #0a0c12;
  --surface: #141826;
  --surface-soft: #1a1f30;
  --surface-soft-hover: #242a3e; /* +10/channel vs --surface-soft; mirrors :root */
  --ink: #edeff6;
  /* 7.50 on --bg, 6.78 on --surface, 6.29 on --surface-soft — all AA. */
  --ink-soft: #9aa0b4;
  --accent: #7b68f0;
  --accent-strong: #8f7ef3;
  --accent-light: rgba(123, 104, 240, 0.15);
  --accent-border: rgba(123, 104, 240, 0.45);
  /* WCAG AA: white on #7b68f0 is only 4.13:1 and #7b68f0 as text on --surface
     is below 4.5 — the split restores both without moving --accent itself
     (borders, rings and icon tints stay put): white on --accent-cta #6450e3
     is 5.53, --accent-text #a296f6 is 6.93 on --surface / 6.42 on
     --surface-soft. */
  --accent-cta: #6450e3;
  --accent-cta-hover: #5843d6;
  --accent-text: #a296f6;
  --accent-warm: #f0b429;
  --border: #232839;
  --border-light: #1b2030;
  --border-strong: #2e3448;
  --danger: #ef4444;
  --success: #3ecf8e;
  /* On dark surfaces the bright success/warm hues already clear 4.5:1 as text
     (success 8.85, warm 9.48 on --surface). --danger does NOT: #ef4444 on the
     --danger-soft tint over --surface is 4.04:1, so the text token stays on
     red-400, which measures 5.49 on that tint and 6.39 on --surface itself. */
  --success-text: var(--success);
  --warn-text: var(--accent-warm);
  --danger-text: #f87171;
  --success-soft: rgba(62, 207, 142, 0.14);
  --warn-soft: rgba(240, 180, 41, 0.14);
  --danger-soft: rgba(239, 68, 68, 0.15);
  /* Theme-independent by design — see the light block. */
  --success-cta: #0b7a4c;
  --shadow: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2);
  --shadow-lg: 0 24px 60px -20px rgba(0,0,0,0.65);
  --text: var(--ink);
  --muted: var(--ink-soft);

  --surface-sidebar: #0e111a;
  --surface-input: #10141f;
  --ink-secondary: #c9cedd;
  --ink-disabled: #6b7186;
}

/* --- Dark mode via OS preference (when no explicit preference set) --- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0a0c12;
    --surface: #141826;
    --surface-soft: #1a1f30;
    --surface-soft-hover: #242a3e; /* +10/channel vs --surface-soft; mirrors [data-theme="dark"] */
    --ink: #edeff6;
    --ink-soft: #9aa0b4;
    --accent: #7b68f0;
    --accent-strong: #8f7ef3;
    --accent-light: rgba(123, 104, 240, 0.15);
    --accent-border: rgba(123, 104, 240, 0.45);
    /* Mirrors the [data-theme="dark"] block above — see the note there. */
    --accent-cta: #6450e3;
    --accent-cta-hover: #5843d6;
    --accent-text: #a296f6;
    --accent-warm: #f0b429;
    --border: #232839;
    --border-light: #1b2030;
    --border-strong: #2e3448;
    --danger: #ef4444;
    --success: #3ecf8e;
    --success-text: var(--success);
    --warn-text: var(--accent-warm);
    --danger-text: #f87171;
    --success-soft: rgba(62, 207, 142, 0.14);
    --warn-soft: rgba(240, 180, 41, 0.14);
    --danger-soft: rgba(239, 68, 68, 0.15);
    --success-cta: #0b7a4c;
    --shadow: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2);
    --shadow-lg: 0 24px 60px -20px rgba(0,0,0,0.65);
    --text: var(--ink);
    --muted: var(--ink-soft);

    --surface-sidebar: #0e111a;
    --surface-input: #10141f;
    --ink-secondary: #c9cedd;
    --ink-disabled: #6b7186;
  }
}

* { box-sizing: border-box; }

/* ============================================================
   Accessibility — keyboard focus
   A single brand-colored focus ring for every keyboard-focusable
   control. :focus-visible keeps the ring off mouse clicks while
   guaranteeing it for keyboard/AT users. Explicit selectors below
   reinforce custom-styled controls (buttons, pills, the dropzone)
   that otherwise inherited only the inconsistent UA outline.
   ============================================================ */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Custom controls — keep the offset tight so the ring hugs the shape */
.primary-btn:focus-visible,
.secondary-btn:focus-visible,
.ghost-btn:focus-visible,
.tiny-btn:focus-visible,
.cancel-btn:focus-visible,
.nav-btn:focus-visible,
.nav-link:focus-visible,
.nav-link-btn:focus-visible,
.sample-chip:focus-visible,
.review-tab:focus-visible,
.export-menu-btn:focus-visible,
.step-summary-action:focus-visible,
.nav-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* The dropzone's real control is the visually-hidden (1px) but focusable file
   input, whose own ring would be invisible — surface an inset ring on the hero
   when that input takes keyboard focus. (:focus-visible kept for safety.) */
.dropzone-hero:focus-visible,
.dropzone-hero:has(> input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: -3px;
}
/* sidebar-search suppresses the :focus outline (border-color cue); restore
   a real ring for keyboard users so it matches every other control. */
.sidebar-search:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Skip link (WCAG 2.4.1) — the first tab stop on every page. Visually hidden
   until focused, then pinned top-left so keyboard/AT users can jump past the
   nav straight to <main id="main">. */
.skip-link {
  position: absolute;
  left: 8px;
  top: -80px;
  z-index: 1000;
  padding: 10px 16px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-lg);
  font-weight: 600;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus {
  top: 8px;
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-ui);
  color: var(--ink);
  background: var(--bg);
  /* Sticky footer: keep the footer at the viewport bottom on short pages
     (developer/history/workspace/billing) instead of floating mid-screen with
     an empty band below it. The footer's margin-top:auto (below) absorbs the
     free space regardless of which wrapper class a page uses for its content. */
  display: flex;
  flex-direction: column;
}
body > .site-footer { flex-shrink: 0; }

/* Headings share the interface sans with tighter tracking — the 2026-08
   rework ended the Source Serif 4 migration by retiring the serif, so the
   per-screen override rules that carried the transition are gone. Legends
   are included: they are headings in every form the app renders. */
h1, h2, h3, legend {
  margin: 0;
  font-family: var(--font-ui);
  letter-spacing: -0.02em;
}

/* ---- Top Navigation Bar ---- */
.top-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px var(--space-6);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 100;
}

.logo {
  font-family: var(--font-ui);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  letter-spacing: -0.02em;
}

/* Brand mark — the accent "D" tile that precedes the wordmark (2026-08
   rework). --accent-cta, not --accent: white glyph needs the AA-safe fill.

   The tile carries its own spacing and alignment rather than the anchor
   flexing: `display:flex; gap` on the <a> would make the bare "Doc" text run
   an ANONYMOUS flex item, so the gap lands between "Doc" and "AI" too and the
   wordmark reads "Doc AI". Kept inline, the wordmark stays one text run
   (letter-spacing and kerning intact) and both logos — sidebar and marketing
   top nav — get the mark spaced identically from this one rule. */
.logo-mark {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 7px;
  background: var(--accent-cta);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0;
  vertical-align: middle;
  margin-right: 9px;
}

/* vertical-align: middle aligns the "AI" suffix to the same x-height midpoint
   as the .logo-mark tile (which is inline-grid + vertical-align:middle),
   closing the ~2px gap that appears when the span sits at bare baseline. */
.logo-accent { color: var(--accent); vertical-align: middle; }

.top-bar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.top-bar-nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* Hamburger is desktop-hidden; the mobile media query reveals it. */
.nav-toggle { display: none; }

.top-bar-auth { display: flex; gap: var(--space-2); align-items: center; }

.nav-link {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: var(--text-md);
  font-weight: 500;
  padding: 6px var(--space-3);
  border-radius: 8px;
  transition: color 0.15s, background 0.15s;
}

.nav-link:hover { color: var(--ink); background: var(--surface-soft); }

.nav-link--active { color: var(--ink); font-weight: 600; }

.nav-btn {
  display: inline-flex;
  align-items: center;
  padding: 7px var(--space-4);
  background: var(--accent-cta);
  color: #fff;
  font-size: var(--text-md);
  font-weight: 600;
  border-radius: 8px;
  text-decoration: none;
  transition: background 0.15s;
}

.nav-btn:hover { background: var(--accent-cta-hover); }

.nav-user {
  font-size: var(--text-md);
  color: var(--ink-soft);
  font-weight: 500;
}

.nav-link-btn {
  background: none;
  border: none;
  color: var(--ink-soft);
  font-size: var(--text-md);
  font-weight: 500;
  padding: 6px var(--space-3);
  border-radius: 8px;
  cursor: pointer;
  margin-top: 0;
}

.nav-link-btn:hover { color: var(--ink); background: var(--surface-soft); }

.inline-auth-form { margin: 0; }

.clerk-user-meta { display: flex; align-items: center; min-width: 0; }
.clerk-user-meta[hidden] { display: none !important; }
.clerk-user-name {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink);
  margin: 0;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
#clerk-user-button { min-height: 32px; }

.top-bar-auth[hidden],
#clerk-user-button[hidden],
#clerk-auth-cta[hidden] { display: none !important; }

/* ---- Shell / Main Layout ---- */
/* ── App Layout (main + sidebar) ── */
.app-layout {
  display: flex;
  max-width: 1200px;
  margin: 0 auto;
  gap: 0;
  min-height: calc(100vh - 60px);
}

.shell {
  flex: 1;
  min-width: 0;
  width: min(800px, 92vw);
  margin: 0 auto;
  /* 40px workspace padding on desktop → 720px workflow column within 800px. */
  padding: var(--space-10) var(--space-10) 80px;
  display: grid;
  gap: var(--space-6);
}

/* Workspace pages (e.g. History) fill the remaining width next to the fixed
   left sidebar, centered within that remaining space on wide viewports. */
.workspace-shell {
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
  padding: var(--space-10) var(--space-12) 72px;
}
@media (max-width: 900px) {
  .workspace-shell { padding: 28px var(--space-6) 64px; }
}
@media (max-width: 640px) {
  .workspace-shell { padding: var(--space-5) var(--space-4) 56px; }
}

/* ============================================================
   App Shell — persistent left sidebar (desktop) that collapses
   into a slide-out drawer on mobile. Replaces the old top nav.
   The right-hand "history-sidebar" is a separate component.
   ============================================================ */
:root {
  --sidebar-w: 248px;
  --topbar-h: 56px;
  /* Workspace/batch caret column: the toggle button's width + its left margin.
     The nested-doc connector line derives its indent from these so it always
     sits under the caret; change the caret size here and the line follows. */
  --appnav-caret-w: 24px; /* ≥24px so the caret toggle + its alignment spacer meet WCAG 2.5.8 */
  --appnav-caret-ml: 2px;
}

/* Reserve the sidebar gutter only on pages that render the shell
   (admin / share pages don't include _nav.html). */
@media (min-width: 901px) {
  body:has(#app-sidebar) { padding-left: var(--sidebar-w); }
  /* Reserve top-right clearance for the fixed language/theme pill (#lang-fab,
     bottom edge = 24px top + 36px height = 60px). 68px gives 8px breathing room
     so the first card always starts below the pill regardless of which page is
     rendered. Applies to both the upload/analyze column (.shell) and the
     full-width workspace layouts (.workspace-shell). The pill floats in the
     blank 68px band above the content. */
  body:has(#app-sidebar) .shell { padding-top: 68px; }
  body:has(#app-sidebar) .workspace-shell { padding-top: 68px; }
  /* The .shell/.billing-shell width formulas use vw, which is viewport-relative
     and knows nothing about the 248px sidebar gutter; 100% of the body
     content-box (padding already excluded) does. Without these, .shell (800px)
     overflowed 901–1047px viewports by up to 24px and .billing-shell (94vw)
     scrolled sideways on every viewport under 1328px. .workspace-shell already
     uses width:100% and never had the bug — and it and .billing-shell must be
     excluded from the .shell rule: the scoped selector's specificity would
     otherwise beat both of their own width rules (capping History/Analytics
     at 800px, and billing at 800px instead of 1080px).

     Two flex subtleties, both measured the hard way:
     - On the home page .shell sits inside .app-layout, whose auto cross-axis
       margins block the column-flex stretch — its width was indefinite, so a
       percentage on .shell resolved to 0 in the intrinsic pass and the shell
       froze at its 533px min-content. Giving the wrapper width:100% (of the
       definite, gutter-adjusted body content box) restores definiteness.
     - .shell is a flex:1 item there, so in the main axis `width` loses to flex
       resolution; only max-width actually caps it. width:100% + max-width is
       equivalent to the min() on the direct-child pages and correct on both. */
  body:has(#app-sidebar) .app-layout { width: 100%; }
  body:has(#app-sidebar)
    .shell:not(.workspace-shell):not(.billing-shell) {
    width: 100%;
    max-width: 800px;
  }
  body:has(#app-sidebar) .billing-shell { width: min(1080px, 100%); }
}

.app-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--sidebar-w);
  display: flex;
  flex-direction: column;
  /* Vertical rhythm is owned by each block's own margins (8px system):
     logo →16→ New analysis →16→ Search →20→ Workspaces →24→ Recent →24→
     Resources. gap:0 keeps the math exact. */
  gap: 0;
  padding: var(--space-4) var(--space-3) var(--space-4);
  background: var(--surface-sidebar);
  border-right: 1px solid var(--border);
  z-index: 100;
  /* The rail is a fixed-width column: nothing inside it may ever scroll
     sideways. This is the backstop — individual rows still truncate their own
     text, but a future untruncated label (or a long localized one) can no
     longer produce a horizontal scrollbar or paint past the border.
     overflow-y is `auto`, not `hidden`, purely as a degenerate-height escape
     hatch: below ~250px of viewport (extreme zoom / tiny window) the pinned
     head + CTA + search + account block alone exceed the rail, and clipping
     them would lose the profile row outright. In every normal case
     .appnav-scroll absorbs the height and this never engages. */
  overflow-x: hidden;
  overflow-y: auto;
}

.appnav-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 2px 6px 0;
}
.appnav-logo { font-size: var(--text-2xl); }

.appnav-close {
  display: none; /* desktop: persistent sidebar has no close control */
  width: 38px;
  height: 38px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  margin-top: 0;
}
/* Same SVG-in-flex-button trap as .mobile-menu-btn svg (see the note there):
   the icon is sized by width/height ATTRIBUTES, which Chrome resolves to a 0
   flex base size, so flex-shrink collapsed the X to 0px wide — measured, not
   theorised. The drawer's only close control was rendering as an empty box. */
.appnav-close svg { flex: 0 0 auto; }
.appnav-close:hover { background: var(--surface-soft); color: var(--ink); transform: none; }

.appnav-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  margin: var(--space-4) 0 0;
  padding: 10px var(--space-3);
  min-height: 44px; /* primary action — full touch/click target */
  border-radius: 10px;
  background: var(--accent-cta);
  color: #fff;
  font-size: var(--text-base);
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s;
}
.appnav-cta:hover { background: var(--accent-cta-hover); }

.appnav-nav { display: flex; flex-direction: column; gap: 2px; }
/* Resources groups API/Docs under a subtle header; 24px above it separates it
   from Recent (same section rhythm as Workspaces → Recent). */
.appnav-resources { display: flex; flex-direction: column; margin: var(--space-6) 0 var(--space-2); }

.appnav-link {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 36px;
  padding: 8px 11px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--ink-soft);
  font-size: var(--text-base);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  margin-top: 0;
  transition: background 0.12s, color 0.12s;
}
.appnav-link:hover { background: var(--surface-soft); color: var(--ink); transform: none; }
/* Active row: tinted fill plus the rework's inset accent bar on the left
   edge; text goes full ink so the row reads as "you are here", not merely
   hovered. */
.appnav-link--active {
  background: var(--accent-light);
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--accent);
}
.appnav-link.hidden { display: none; }
.appnav-icon { width: 18px; height: 18px; flex-shrink: 0; }

/* ---- Label truncation safety net ----
   Every text label in the rail is a flex child, and a flex item's default
   `min-width: auto` refuses to shrink below its min-content width — so a long
   localized label pushes the row wider than the 248px rail instead of
   ellipsing. `min-width: 0` restores shrinkability and the ellipsis triple does
   the rest. Document/workspace names carry their own copy of this (they also
   ship a title= tooltip from the render JS); these are the static labels that
   were missing it: nav rows, section headers, and the credits line. Keep the
   accessible name intact when adding truncation — the visible text is the
   accessible name here, so nothing is lost until a translation exceeds the row,
   at which point the row's own title= must carry the full string. */
/* .appnav-coins-text is deliberately NOT in this list: in the usage-meter card
   it is the right-hand item of a space-between row, so joining the net would
   let the balance itself ellipsize. Its row-mate .appnav-coins-label absorbs
   the shrink instead — see the meter block below. */
.appnav-link > span,
.appnav-recents-title,
.cmdk-trigger > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Section headers pair a truncating title with a fixed-size "+" action. */
.appnav-recents-head { min-width: 0; }

/* ---- Preferences: language + appearance rows ----
   §9 canonical location. These controls were never duplicated (the corner pill
   and this section swap by context), but WHERE they lived changed from page to
   page — sidebar while reviewing, corner pill everywhere else. Inside the
   authenticated AppShell the sidebar is now the single home for both, on every
   page and at every width; the corner pill is reserved for screens that have no
   sidebar (public/unauthenticated), where it is the only option.
   Verify with: exactly one visible theme control and one language control per
   page — see tests/test_index_template.py and the browser check. */
.appnav-prefs { display: none; }
body:has(#app-sidebar) .appnav-prefs {
  display: flex;
  flex-direction: column;
  margin: var(--space-6) 0 0;
}
@media (max-width: 900px) {
  .appnav-prefs { display: flex; flex-direction: column; margin: var(--space-6) 0 0; }
}
/* The corner pill is the fallback for sidebar-less pages only. */
body:has(#app-sidebar) .lang-fab { display: none; }
/* Preferences rows are 44px controls, not 36px links, so the section gets a
   little more air than the link sections above it: 8px under the heading and 4px
   between the two rows. Without it the heading sat 4px off the Language row and
   the two rows read as one block. */
.appnav-prefs .appnav-recents-head { padding-bottom: var(--space-2); }
.appnav-prefs .appnav-nav { gap: var(--space-1); }
.appnav-pref-row { min-height: 44px; }
/* Value + caret travel together as one unit, which is what makes the column a
   property of the markup instead of a number to keep in sync. Both rows carry
   this same pair, so both value texts end on the same pixel however long the
   locale's word is. */
.appnav-pref-picker {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  margin-left: auto;
}
/* The value takes the label's metrics — `font: inherit` — because it used to
   take its own: --text-md/600 in --ink beside a label of --text-base/500 in
   --ink-soft, so it was bolder AND darker AND half a pixel smaller than the
   setting it names. Three signals of emphasis on the answer, none on the
   question. Only colour separates them now: muted label, full-strength value. */
.appnav-pref-select {
  /* The UA arrow rides INSIDE the select's right edge, so the text stopped
     ~16px short of the box that contained it and no two rows could agree on a
     right edge. .appnav-pref-caret replaces it: same glyph, but a real sibling
     element, which means it inherits currentColor and follows the row's theme
     and hover instead of being a fixed-hex background image.
     `padding: 0` also undoes the bare `input, select` rule (~L2524), which
     hands every select 10px/12px of form-control padding; that alone made the
     Language row 55px tall against Appearance's 44px. */
  appearance: none;
  -webkit-appearance: none;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: right;
  text-overflow: ellipsis;
  cursor: pointer;
}
.appnav-pref-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.appnav-pref-select option { color: initial; }
.appnav-pref-caret {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  opacity: 0.7;
  /* Clicks belong to the select underneath, not to its decoration. */
  pointer-events: none;
}

/* ---- Rail layout contract: pinned header · scrolling middle · pinned footer ----
   Header (logo + New analysis + Search) and the account footer are `flex: 0 0
   auto` so they never shrink; .appnav-scroll is the ONLY flexible child, so it
   is the only thing that gives up height. Listing all four here (rather than
   letting the middle three inherit the default `flex: 0 1 auto`) is the fix for
   two real failures: the Search trigger was shrinking toward its 36px
   min-height on short viewports, and the footer had no explicit floor at all —
   which is what let it close in on Preferences instead of the scroll area
   absorbing the squeeze. */
.app-sidebar > .appnav-head,
.app-sidebar > .appnav-cta,
.app-sidebar > .cmdk-trigger,
.app-sidebar > .appnav-account { flex: 0 0 auto; }

.appnav-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Paired with overflow-y:auto this must be explicit: a `visible` cross-axis
     computes to `auto` next to a scrolling axis, so the middle region grew its
     own horizontal scrollbar as soon as any child overflowed 224px. */
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 0; /* sections own their vertical rhythm via margins */
  margin: 0 -4px;
  /* 16px of bottom clearance so the last section (Preferences → AI badge) keeps
     visible air above the account block's divider instead of merging into it. */
  padding: 0 var(--space-1) var(--space-4);
}

/* ── Recents ── */
.appnav-recents { display: flex; flex-direction: column; margin: var(--space-6) 0 0; }
/* Recent rows breathe a little more than workspace rows — each row is a
   two-line title + metadata block. */
.appnav-recents .appnav-recents-list { gap: var(--space-2); }
.appnav-recents-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 2px 11px var(--space-1);
}
.appnav-recents-title {
  font-size: var(--text-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--muted, #667085);
}
/* Compact "+" action pinned to the right of a section header (e.g. Workspaces).
   Replaces the old full-width "Create workspace" row so it doesn't compete with
   the section's items. Quiet by default, accent on hover/focus. */
.appnav-section-add {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin: -3px -4px -3px 0;
  border-radius: 7px;
  color: var(--muted, #667085);
  text-decoration: none;
  transition: background 0.12s, color 0.12s;
}
.appnav-section-add svg { flex: 0 0 auto; }
.appnav-section-add:hover,
.appnav-section-add:focus-visible { background: var(--surface-soft); color: var(--accent-strong); }
.appnav-recents-list { display: flex; flex-direction: column; gap: 1px; }
.appnav-recents-empty {
  margin: 0;
  padding: 5px 11px 7px;
  font-size: var(--text-sm);
  line-height: 1.35;
  color: var(--muted, #667085);
}
/* Second line of the signed-out empty state ("Sign in to access your history.")
   — tighter to the line above and one step quieter. */
.appnav-recents-empty-sub {
  padding-top: 0;
  font-size: var(--text-xs);
}
.appnav-recent {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  padding: 7px 11px;
  border-radius: 9px;
  text-decoration: none;
  color: var(--ink-soft);
  transition: background 0.12s;
}
.appnav-recent:hover { background: var(--surface-soft); }
/* Selected row — recent, workspace, and nested doc share one treatment: accent
   surface + inset accent bar, so selection never rests on the purple text alone.
   (Per-type heavier-title rules stay separate since their child class differs.) */
.appnav-recent.is-active,
.appnav-workspace.is-active,
.appnav-wsdoc.is-active {
  background: var(--accent-light);
  box-shadow: inset 2px 0 0 var(--accent);
}
.appnav-recent.is-active .appnav-recent-title { color: var(--accent-strong); font-weight: 600; }
.appnav-recent-title {
  font-size: var(--text-sm); /* document titles: ~13px, matches workspace docs */
  font-weight: 500;
  line-height: 1.25;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* Secondary line: "document type · relative time", quiet and single-line. */
.appnav-recent-sub {
  font-size: var(--text-xs);
  line-height: 1.25;
  /* --ink-soft, not the old `var(--muted, #667085)`: the hardcoded fallback
     ignored the dark theme entirely (the same bug the --text-muted alias was
     added to fix), leaving this metadata at a fixed grey on a dark surface. */
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* Batch status rows keep the same neutral metadata colour as every other row.
   A failed batch used to paint this whole line danger-red and bold, which made
   one dead upload the loudest thing in the rail; the state is carried by the
   red dot plus the leading "Failed" word in the text, so it survives both a
   colour-blind reader and a screen reader without shouting. */
/* Recents row wraps the title link + an overflow (⋯) action menu button. The
   button is quiet (revealed on hover/focus) on desktop and always shown in the
   mobile drawer where there is no hover. */
.appnav-recent-row {
  position: relative;
  display: flex;
  align-items: stretch;
  border-radius: 9px;
}
.appnav-recent-row:hover { background: var(--surface-soft); }
.appnav-recent-row .appnav-recent { flex: 1 1 auto; min-width: 0; }
.appnav-recent-row:hover .appnav-recent:not(.is-active) { background: transparent; }
.appnav-recent-pin { font-size: var(--text-2xs); }
.appnav-recent-menu-btn {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  align-self: center;
  margin-right: var(--space-1);
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted, #667085);
  border-radius: 6px;
  cursor: pointer;
  font-size: var(--text-sm);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.appnav-recent-row:hover .appnav-recent-menu-btn,
.appnav-recent-row:focus-within .appnav-recent-menu-btn,
.appnav-recent-menu-btn:focus,
.appnav-recent-menu-btn[aria-expanded="true"] { opacity: 1; }
.appnav-recent-menu-btn:hover { background: var(--surface); color: var(--ink); }
/* No hover on touch / in the mobile drawer: keep the affordance visible. */
@media (max-width: 900px) {
  .appnav-recent-menu-btn { opacity: 1; }
}
.appnav-recent-dismiss {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  align-self: center;
  margin-right: var(--space-1);
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted, #667085);
  border-radius: 6px;
  cursor: pointer;
  font-size: var(--text-lg);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.appnav-recent-row:hover .appnav-recent-dismiss,
.appnav-recent-row:focus-within .appnav-recent-dismiss { opacity: 1; }
.appnav-recent-dismiss:hover { background: var(--surface); color: var(--ink); }
@media (max-width: 900px) {
  .appnav-recent-dismiss { opacity: 1; }
}

/* Batch "container" expand: caret toggle + inline child-document list. */
.appnav-recent-expand {
  flex: 0 0 auto;
  width: var(--appnav-caret-w);
  min-height: 24px; /* WCAG 2.5.8 target size; width comes from --appnav-caret-w (24px) */
  align-self: center;
  margin-left: var(--appnav-caret-ml);
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted, #667085);
  cursor: pointer;
  font-size: var(--text-2xs);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.12s, color 0.12s;
}
.appnav-recent-expand:hover { color: var(--ink); }
.appnav-recent-expand.is-open { transform: rotate(90deg); }
.appnav-batch-children {
  list-style: none;
  margin: 0 0 2px 0;
  padding: 0 0 0 22px;
}
.appnav-batch-child {
  position: relative;
  display: flex;
  align-items: stretch;
  font-size: var(--text-sm);
  border-radius: 7px;
}
/* Inert muted rows (queued/processing/failed) have no link/menu — no hover. */
.appnav-batch-child:not(.is-muted):hover { background: var(--surface-soft); }
.appnav-batch-child a {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-1) var(--space-2);
  color: var(--ink);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-radius: 7px;
}
/* Hover surface lives on the row now, so the link itself stays transparent. */
.appnav-batch-child:not(.is-muted):hover a { background: transparent; }
/* The "⋯" menu button reveals on row hover/focus, like .appnav-recent-row. */
.appnav-batch-child .appnav-recent-menu-btn { opacity: 0; }
.appnav-batch-child:hover .appnav-recent-menu-btn,
.appnav-batch-child:focus-within .appnav-recent-menu-btn,
.appnav-batch-child .appnav-recent-menu-btn:focus,
.appnav-batch-child .appnav-recent-menu-btn[aria-expanded="true"] { opacity: 1; }
@media (max-width: 900px) {
  .appnav-batch-child .appnav-recent-menu-btn { opacity: 1; }
}
/* Muted children (queued/processing/failed) stay a plain block — no flex row,
   no link, no menu button. */
.appnav-batch-child.is-muted {
  display: block;
  padding: var(--space-1) var(--space-2);
  color: var(--muted, #667085);
}
.appnav-batch-child.is-muted span {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Body-level floating menu (fixed positioning set in JS) so it is never clipped
   by the sidebar's scroll container and can flip above the button near the
   viewport edge. max-width guards against horizontal overflow at 390px. */
.appnav-recent-menu {
  position: fixed;
  /* Above the mobile nav drawer (z-index 4001) so the menu is usable inside the
     open drawer; harmless on desktop where the sidebar sits much lower. */
  z-index: 4100;
  min-width: 160px;
  max-width: calc(100vw - 24px);
  padding: var(--space-1);
  background: var(--surface, #fff);
  border: 1px solid var(--border);
  border-radius: 9px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08), 0 4px 16px rgba(0,0,0,0.10);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
[data-theme="dark"] .appnav-recent-menu {
  box-shadow: 0 2px 10px rgba(0,0,0,0.4), 0 4px 20px rgba(0,0,0,0.3);
}
.appnav-recent-menu[hidden] { display: none; }
.appnav-recent-menu button {
  text-align: left;
  padding: 6px 10px;
  border: 0;
  border-radius: 7px;
  background: none;
  font-size: var(--text-sm);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.appnav-recent-menu button:hover { background: var(--surface-soft); }
.appnav-recent-menu button.danger { color: var(--danger); }
.appnav-recent-menu button.danger:hover { background: #fef2f2; }
[data-theme="dark"] .appnav-recent-menu button.danger:hover { background: rgba(239,68,68,0.12); }
.appnav-recent-menu-sep { height: 1px; margin: 3px 2px; background: var(--border); }

.appnav-recents-viewall {
  display: flex;
  align-items: center;
  min-height: 36px;
  margin-top: 2px;
  padding: 6px 11px;
  border-radius: 9px;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--accent-strong);
  text-decoration: none;
  transition: background 0.12s;
}
.appnav-recents-viewall:hover { background: var(--surface-soft); }
.appnav-recents-viewall.is-active { background: var(--accent-light); font-weight: 600; }
/* The author display:block above would otherwise defeat the [hidden] attribute
   (View all history is hidden while signed out — history isn't reachable). */
.appnav-recents-viewall[hidden] { display: none; }

/* ── Workspaces (sidebar project list) ── */
.appnav-workspaces { display: flex; flex-direction: column; margin: var(--space-5) 0 0; }
/* Workspace name rows sit closer together than Recent rows — they're
   single-line folders, not two-line document entries. */
.appnav-workspaces .appnav-recents-list { gap: var(--space-1); }
.appnav-workspace {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  min-height: 36px;
  padding: 7px 11px;
  border-radius: 9px;
  text-decoration: none;
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 500;
  transition: background 0.12s;
}
.appnav-workspace:hover { background: var(--surface-soft); }
/* Active-surface treatment is grouped with .appnav-recent.is-active above. */
.appnav-workspace.is-active .appnav-workspace-name { color: var(--accent-strong); font-weight: 600; }
.appnav-workspace-icon { flex: 0 0 auto; width: 16px; height: 16px; color: var(--muted, #667085); }
.appnav-workspace.is-active .appnav-workspace-icon { color: var(--accent-strong); }
.appnav-workspace-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Quiet right-aligned total document count on a workspace row. */
.appnav-ws-count {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--muted, #667085);
}

/* Workspace group: workspace name row + nested document rows beneath it */
.appnav-ws-group { display: flex; flex-direction: column; }
/* Row that pairs the collapse/expand caret with the workspace link so the
   whole row shares one hover surface (mirrors .appnav-recent-row). */
.appnav-ws-row { display: flex; align-items: stretch; border-radius: 9px; }
.appnav-ws-row:hover { background: var(--surface-soft); }
.appnav-ws-row .appnav-workspace { flex: 1 1 auto; min-width: 0; }
.appnav-ws-row:hover .appnav-workspace:not(.is-active) { background: transparent; }
/* Keeps workspaces with no docs (no caret) aligned with those that have one. */
.appnav-ws-toggle-spacer { flex: 0 0 auto; width: var(--appnav-caret-w); margin-left: var(--appnav-caret-ml); }
/* Nested docs container: a subtle connector line centered under the caret column
   (indent = caret left margin + half its width), so hierarchy reads without heavy
   borders. 8px separates the workspace row from its first nested row. */
.appnav-wsdocs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: var(--space-2) 0 var(--space-1) calc(var(--appnav-caret-ml) + var(--appnav-caret-w) / 2);
  padding-left: 9px;
  border-left: 1px solid var(--border-light);
}
/* `display: flex` above is an author rule that beats the UA `[hidden]` rule, so
   the collapsed state needs an explicit override to actually hide the docs. */
.appnav-wsdocs[hidden] { display: none; }
/* Nested workspace doc row — mirrors .appnav-recent-row */
.appnav-wsdoc-row {
  position: relative;
  display: flex;
  align-items: stretch;
  border-radius: 9px;
}
.appnav-wsdoc-row:hover { background: var(--surface-soft); }
.appnav-wsdoc-row .appnav-wsdoc { flex: 1 1 auto; min-width: 0; }
.appnav-wsdoc-row:hover .appnav-wsdoc:not(.is-active) { background: transparent; }
/* The doc link — quieter than the workspace row, similar to .appnav-recent */
.appnav-wsdoc {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 5px var(--space-2) 5px 10px;
  border-radius: 9px;
  text-decoration: none;
  color: var(--ink-soft);
  transition: background 0.12s;
}
.appnav-wsdoc:hover { background: var(--surface-soft); }
/* Active-surface treatment is grouped with .appnav-recent.is-active above. */
.appnav-wsdoc.is-active .appnav-wsdoc-title { color: var(--accent-strong); font-weight: 600; }
.appnav-wsdoc-title {
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.25;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.appnav-wsdoc-sub {
  font-size: var(--text-xs);
  line-height: 1.2;
  color: var(--muted, #667085);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
/* Compact jump to the workspace's full document list — shown as the last row
   of an expanded workspace when more documents exist than the sidebar shows. */
.appnav-wsdoc-viewall {
  display: flex;
  align-items: center;
  min-height: 28px;
  padding: 2px var(--space-2) 2px 10px;
  border-radius: 7px;
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--accent-strong);
  text-decoration: none;
  transition: background 0.12s;
}
.appnav-wsdoc-viewall:hover { background: var(--surface-soft); }

/* Full-width sidebar rows get an inset focus ring — the global 2px-offset ring
   would clip against the sidebar edges and neighbouring rows. */
.appnav-cta:focus-visible,
.appnav-link:focus-visible,
.appnav-workspace:focus-visible,
.appnav-wsdoc:focus-visible,
.appnav-wsdoc-viewall:focus-visible,
.appnav-recent:focus-visible,
.appnav-recents-viewall:focus-visible,
.cmdk-trigger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Disabled menu item (e.g. "No workspaces available") */
.appnav-recent-menu button:disabled {
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}

/* Fixed bottom account block: a grounded two-row unit (identity row + action
   toolbar), separated from the scroll area by a single top border. flex:0 0 auto
   keeps it pinned however long the Recents list grows; splitting it across two
   rows means controls are never crammed onto one line. */
.appnav-account {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Also asserted in the rail layout contract next to .appnav-scroll — kept
     here so the block is self-describing. */
  flex: 0 0 auto;
  min-width: 0;
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  /* Opaque, matching the rail: in the degenerate short-viewport case where
     .app-sidebar itself scrolls, nothing from the middle shows through the
     footer as it passes. */
  background: var(--surface-sidebar);
}

/* Usage meter — bordered card above the identity row; links to /pricing.
   Populated by coin-meter.js. The explicit [hidden] rule is required: the
   display:flex below would otherwise win over the UA's [hidden]{display:none}
   and the card would flash for signed-out users. */
.appnav-coins {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-bottom: var(--space-2);
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font-size: var(--text-xs);
  color: var(--text-muted);
  text-decoration: none;
  transition: border-color 0.12s ease;
}
.appnav-coins[hidden] { display: none; }
.appnav-coins:hover { border-color: var(--accent-border); color: var(--ink); }
.appnav-coins-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 0;
}
.appnav-coins-label {
  color: var(--ink-soft);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The balance never shrinks or ellipsizes — it is the only information the
   card exists to convey. A long label ("Verbleibende Seiten") truncates
   instead; the full text stays available from the card's title=. */
.appnav-coins-text {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}
.appnav-coins-bar {
  height: 4px;
  border-radius: 2px;
  background: var(--border-strong);
  overflow: hidden;
}
.appnav-coins-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: var(--success);
  transition: width 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
  .appnav-coins-fill { transition: none; }
}
/* Low balance must survive greyscale and colour-blindness (WCAG 1.4.1). The
   hue swap alone is not a signal, and the bar is aria-hidden, so it is paired
   with a heavier number and a hatched fill — two cues that carry no colour.
   The weight restores what the pre-rework markup got from `strong`. */
.appnav-coins.is-low .appnav-coins-fill {
  background: var(--danger);
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(255, 255, 255, 0.5) 0 3px,
    transparent 3px 6px
  );
}
.appnav-coins.is-low .appnav-coins-text {
  color: var(--danger-text);
  font-weight: 700;
}

/* Account block — one control. position: relative makes it the anchor the
   account menu opens from. */
.appnav-profile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* No :has() hide rule any more: signed out there is no account control to keep
   available beside a hidden identity row — clerk.js simply leaves
   #appnav-account-trigger hidden, and #clerk-auth-cta takes the space. */

/* Fallback avatar for the non-Clerk (admin/local) identity branch; Clerk renders
   its own avatar inside #clerk-user-button. */
.appnav-avatar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--accent-light);
  color: var(--accent-strong);
  font-size: var(--text-sm);
  font-weight: 700;
}


/* Language switcher — compact 32px ghost pill, so it shares a baseline and a
   hover treatment with the theme toggle beside it. (It used to say "three
   toolbar controls" and reference .appnav-icon-btn's height; the settings
   trigger was the third and is now a full-width labelled row instead.) */
.appnav-lang {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 32px;
  padding: 0 var(--space-2);
  border-radius: 8px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.appnav-lang:hover { background: var(--surface-soft); color: var(--ink); }
/* The native select holds focus; lift the whole pill so the focus cue reads as
   one control rather than a ring floating on the inner <select>. */
.appnav-lang:focus-within { background: var(--surface-soft); color: var(--ink); }
.appnav-lang-icon { font-size: var(--text-md); line-height: 1; }
.appnav-lang-select {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  padding: 0 2px;
  max-width: 96px;
  cursor: pointer;
}
.appnav-lang-select:focus-visible { outline: 2px solid var(--accent, #5b45e0); outline-offset: 2px; border-radius: 4px; }
.appnav-lang-select option { color: initial; }

/* ── Global language switcher (top-right corner pill) ──────────────────────────
   Pinned to the viewport's top-right on every shell page. z-index sits above
   page content and the sticky mobile top bar (95), but below the mobile drawer
   (4001) and its backdrop (4000), so opening the drawer cleanly covers it. */
.lang-fab {
  position: fixed;
  /* Clear of the viewport edges (24px) but visually quiet — these controls must
     not compete with the upload action. */
  top: var(--space-6);
  right: var(--space-6);
  z-index: 200;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Theme toggle pill sitting next to the language switcher in #lang-fab.
   Matches lang-fab-control's border/shadow/height so the two read as one group. */
.lang-fab-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  /* One quiet hairline shadow — these controls belong to the app shell, they
     shouldn't float above it. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  font-size: var(--text-lg);
  line-height: 1;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.lang-fab-btn:hover { background: var(--surface-soft); color: var(--ink); border-color: var(--ink-soft); transform: none; }
.lang-fab-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
[data-theme="dark"] .lang-fab-btn {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.lang-fab-control {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 36px; /* matches .lang-fab-btn — one consistent control height */
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.lang-fab-control:hover { background: var(--surface-soft); color: var(--ink); border-color: var(--ink-soft); }
/* The native <select> holds focus; lift the whole pill so the cue reads as one
   control rather than a ring around the inner <select>. */
.lang-fab-control:focus-within {
  color: var(--ink);
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-light), 0 1px 2px rgba(0, 0, 0, 0.05);
}
[data-theme="dark"] .lang-fab-control {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.lang-fab-icon { font-size: var(--text-base); line-height: 1; }
.lang-fab-caret { flex: 0 0 auto; opacity: 0.7; }
.lang-fab-select {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  padding: 0;
  max-width: 108px;
  cursor: pointer;
}
.lang-fab-select:focus { outline: none; }
.lang-fab-select option { color: initial; }

/* The pill is pinned top-right at a higher z-index than the right-hand history
   drawer (70) and the mobile nav drawer's content — so hide it whenever either
   drawer is open, otherwise it would float over the drawer's own header. */
.nav-drawer-open .lang-fab,
body:has(.history-sidebar.is-open) .lang-fab { display: none; }

/* Mobile: the header carries only menu + logo — theme and language live in
   the drawer's Preferences section instead of a floating corner pill. */
@media (max-width: 900px) {
  .lang-fab { display: none; }
}

/* Signed-out account area: one quiet full-width Sign in button and nothing
   else. The dominant auth action and the create-account path live in the
   upload dropzone, so the sidebar never repeats them. */
.appnav-auth { display: flex; flex-direction: column; gap: var(--space-2); width: 100%; margin-top: var(--space-2); }
.appnav-auth-link {
  /* 44px, matching the labelled Settings trigger above it. */
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border-radius: 9px;
  font-size: var(--text-md);
  font-weight: 600;
  cursor: pointer;
  margin-top: 0;
  background: var(--surface-soft);
  border: 1px solid var(--border);
  color: var(--ink);
}
.appnav-auth-link:hover { background: var(--surface-soft-hover); transform: none; }
/* Create account carries the accent, Sign in stays quiet: signed out, this rail
   is the whole of the navigation, and the two actions serve opposite visitors —
   one already has an account, the other is deciding whether to want one. Same
   44px target as the button above it. */
.appnav-auth-signup {
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border-radius: 9px;
  font-size: var(--text-md);
  font-weight: 600;
  cursor: pointer;
  background: var(--accent-cta);
  border: 1px solid var(--accent-cta);
  color: #fff;
}
.appnav-auth-signup:hover {
  background: var(--accent-cta-hover);
  border-color: var(--accent-cta-hover);
  transform: none;
}

/* ---- Account control ----
   ONE control, signed in only. Avatar + name + chevron, opening a menu that
   holds every account-scoped action including Clerk's own Manage account and
   Sign out. It replaced a pair: Clerk's mountUserButton (avatar, its own menu)
   sitting next to a DocAI "Account settings" row with a second menu — two
   account controls in a 248px rail, and no way to tell which owned what.

   Signed out this whole block is absent: there is no account, so there is no
   account control. Cookie settings moved to Preferences and the legal links to
   .appnav-legal, so nothing that a signed-out visitor needs went with it. */
.appnav-account-trigger {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  /* 44px: past the 24px WCAG 2.5.8 minimum and matched to the Sign in button
     that replaces it when signed out, so the footer keeps one rhythm. */
  min-height: 44px;
  padding: var(--space-1) var(--space-2);
  border: 0;
  border-radius: 10px;
  background: none;
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.appnav-account-trigger:hover { background: var(--surface-soft); transform: none; }
.appnav-account-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.appnav-account-trigger[aria-expanded="true"] { background: var(--surface-soft); }
/* The name owns the flexible middle, so it — not the avatar or the chevron —
   is what truncates on a long name or a long-label locale. */
.appnav-account-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Chevron DOWN while collapsed, UP while open — the state of the disclosure,
   not the direction the panel happens to fly out in. (It opens upward, being
   pinned to the bottom of the rail; pointing the chevron up while CLOSED to
   advertise that was backwards, and made the arrow contradict aria-expanded.) */
.appnav-account-chevron {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--ink-soft);
  transition: transform 0.15s;
}
.appnav-account-trigger[aria-expanded="true"] .appnav-account-chevron { transform: rotate(180deg); }
.appnav-account-trigger .appnav-avatar { width: 26px; height: 26px; font-size: var(--text-xs); }
.appnav-account-trigger .appnav-avatar img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  display: block;
}

/* Opens upward from the trigger it is anchored to (position: relative on
   .appnav-profile) and spans its full width via left:0/right:0, so it stays
   inside the rail. Capped height + internal scroll keep it usable in a short
   mobile drawer. */
.appnav-account-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 4100;
  max-height: min(60vh, 360px);
  overflow-y: auto;
  padding: var(--space-1);
  background: var(--surface, #fff);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08), 0 6px 20px rgba(0,0,0,0.12);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
[data-theme="dark"] .appnav-account-menu {
  box-shadow: 0 2px 10px rgba(0,0,0,0.4), 0 6px 24px rgba(0,0,0,0.32);
}
.appnav-account-menu[hidden] { display: none; }

.appnav-account-item {
  display: block;
  width: 100%;
  padding: var(--space-2) 10px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ink-soft);
  font-size: var(--text-md);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.appnav-account-item:hover { background: var(--surface-soft); color: var(--ink); }
.appnav-account-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.appnav-account-item.is-active { background: var(--accent-light); color: var(--accent-strong); font-weight: 600; }
.appnav-account-sep { height: 1px; margin: var(--space-1) 2px; background: var(--border); }

/* Legal links — a quiet row, not account settings. Present in BOTH auth states
   because .site-footer (which carries the same three) is display:none wherever
   this sidebar is. */
.appnav-legal {
  /* display: block so children flow as inline elements — this is intentional.
     Flex with flex-wrap put "Cookie settings" alone on a second line and made
     it look detached from the three links above.  Inline flow wraps all four
     items as a continuous run (like a text paragraph), so they read as one
     coherent group regardless of where the wrap falls.  Children keep their
     display: inline-flex so alignment still works; margin-right replaces the
     flex column-gap. */
  display: block;
  padding: var(--space-2) var(--space-2) 0;
  font-size: var(--text-2xs);
  line-height: 1.8;
}
/* Cookie settings is a <button> among links — it opens a dialog rather than
   navigating — so the two are styled together and the button is stripped of its
   control chrome to sit level with them.
   The 24px floor is WCAG 2.2 AA 2.5.8: at --text-2xs these run about 16px tall,
   and the spacing exception does not save them either, because 8px between
   neighbours puts their 24px circles through each other. Padding is the fix
   that does not touch the type. */
.appnav-legal a,
.appnav-legal button {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  /* margin-right provides spacing in inline flow (replaces the old flex gap). */
  margin-right: var(--space-2);
  padding: 0;
  border: 0;
  /* radius + transition finish cancelling the bare `button` rule (~L2718) —
     invisible today with no border or background, but left inherited they
     resurface the moment that rule grows a box-shadow or a descendant
     restores a border. :focus-visible below still wins its 3px radius. */
  border-radius: 0;
  background: none;
  color: var(--ink-soft);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: none;
}
/* `transform: none` cancels the bare `button:hover` lift (~L2726) — without it
   Cookie settings hops a pixel while the three links beside it hold still. */
.appnav-legal a:hover,
.appnav-legal button:hover { color: var(--ink); text-decoration: underline; transform: none; }
.appnav-legal a:focus-visible,
.appnav-legal button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* Mobile top bar + backdrop are desktop-hidden. */
.mobile-topbar { display: none; }
.mobile-topbar-spacer { width: 22px; }
.mobile-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  margin-top: 0;
}
/* The button is inline-flex, so the inline SVG (sized via width/height attrs,
   not CSS) would otherwise be shrunk by flex-shrink to a sliver — collapsing
   the three hamburger lines into a single dot. Pin it like the repo's other
   SVG-in-flex-button cases (.share-btn svg, .appnav-section-add svg). */
.mobile-menu-btn svg { flex: 0 0 auto; }
.mobile-menu-btn:hover { background: var(--surface-soft); transform: none; }
.appnav-backdrop { display: none; }

/* The old floating Guide pill now lives inside the sidebar (#appnav-utility),
   so hide the fixed pill on shell pages — nothing floats over the work area.
   Non-shell surfaces (e.g. /admin) keep theirs.
   The Privacy pill is NOT listed here: consent.js renders it only where the
   page carries no static [data-cookie-settings] of its own. A shape-matching
   CSS rule was the wrong owner — it stopped matching the day the marketing
   landing shed the sidebar, and the pill appeared over the front door. */
body:has(#app-sidebar) #tour-guide-btn,
body:has(#app-sidebar) #tour-menu { display: none !important; }

/* The app shell already carries legal links (Terms / Security / Data retention
   in the sidebar bottom, Privacy in the account nav), so the page footer is
   duplicate navigation inside the authenticated shell — hide it there. Non-shell
   surfaces (admin / share) keep their footer. */
body:has(#app-sidebar) .site-footer { display: none; }

/* ── Mobile: persistent sidebar becomes a slide-out drawer ── */
@media (max-width: 900px) {
  .mobile-topbar {
    display: flex;
    align-items: center;
    gap: 10px;
    position: sticky;
    top: 0;
    z-index: 95;
    height: var(--topbar-h);
    padding: 0 14px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
  }
  .mobile-topbar-logo { font-size: var(--text-xl); }

  .app-sidebar {
    width: min(300px, 84vw);
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    z-index: 4001;
    box-shadow: none;
  }
  .nav-drawer-open .app-sidebar { transform: translateX(0); box-shadow: var(--shadow-lg); }
  .appnav-close { display: inline-flex; }

  .appnav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: 4000;
  }
  .appnav-backdrop[hidden] { display: none; }

  /* Lock background scroll while the drawer is open. */
  .nav-drawer-open,
  .nav-drawer-open body { overflow: hidden; }
}

/* ── History Sidebar ── */
.history-sidebar {
  width: 280px;
  flex-shrink: 0;
  border-left: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  height: calc(100vh - 60px);
  position: sticky;
  top: 60px;
}

.sidebar-header {
  padding: var(--space-5) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--border);
}

.sidebar-title {
  /* Used only by the analyze screen's history drawer, which is migrated. */
  font-family: var(--font-ui);
  font-size: var(--text-lg);
  font-weight: 700;
  margin: 0 0 10px;
  color: var(--text);
}

.sidebar-search {
  width: 100%;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: var(--text-sm);
  background: var(--bg);
  transition: border-color 0.2s;
}
.sidebar-search:focus {
  border-color: var(--accent);
}
.sidebar-search:focus:not(:focus-visible) {
  outline: none;
}

.sidebar-list {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2) 0;
}

.sidebar-item {
  display: block;
  padding: 10px var(--space-4);
  border-bottom: 1px solid var(--border-light, #f0f0f0);
  cursor: pointer;
  transition: background 0.15s;
  text-decoration: none;
  color: inherit;
}
.sidebar-item:hover {
  background: var(--accent-light);
}
.sidebar-item.active {
  background: var(--accent-light);
  border-left: 3px solid var(--accent);
  padding-left: 13px;
}

/* Per-record ⋯ menu (shared RecordActions) in the right drawer: revealed on
   hover/focus so the compact rows stay clean, and kept visible while its menu is
   open (the floating list lives in <body>, so :focus-within alone would drop). */
.sidebar-item { position: relative; }
.sidebar-item .ra-menu { position: absolute; top: 6px; right: 6px; opacity: 0; transition: opacity 0.12s; }
.sidebar-item:hover .ra-menu,
.sidebar-item:focus-within .ra-menu,
.sidebar-item:has(.ra-menu-btn[aria-expanded="true"]) .ra-menu { opacity: 1; }
.sidebar-item .ra-menu-btn { width: 26px; height: 26px; }

.sidebar-item-name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0 0 2px;
  /* Reserve room for the hover-revealed ⋯ menu (absolute, top-right) so it never
     overlaps the (truncating) title. */
  padding-right: 28px;
}

.sidebar-item-meta {
  font-size: var(--text-2xs);
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 6px;
}

.sidebar-item-status {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.sidebar-item-status.ok { background: #16a34a; }
.sidebar-item-status.warn { background: #f59e0b; }
.sidebar-item-status.error { background: #ef4444; }

.sidebar-status-badge {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: var(--text-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  flex-shrink: 0;
}
.sb-confirmed { background: #dcfce7; color: #166534; }
.sb-ok { background: #dcfce7; color: #166534; }
.sb-draft { background: #e0e7ff; color: #3730a3; }
.sb-incomplete { background: #fee2e2; color: #991b1b; }
.sb-review { background: #fef9c3; color: #854d0e; }

.sidebar-empty {
  text-align: center;
  padding: 2rem 1rem;
  color: var(--muted);
}
.sidebar-empty-icon { font-size: var(--text-3xl); margin: 0 0 0.25rem; }
.sidebar-empty-text { font-size: var(--text-sm); margin: 0; }

.sidebar-footer {
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border);
}

.sidebar-view-all {
  font-size: var(--text-sm);
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}
.sidebar-view-all:hover { text-decoration: underline; }

/* ---- Flash Banner ---- */
.flash-banner { margin: 0; }

.flash-message {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid #bbf7d0;
  background: #f0fdf4;
  color: var(--success-text);
  font-size: var(--text-base);
}

.flash-error {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border: 1px solid #fecaca;
  background: #fef2f2;
  color: var(--danger);
}

/* ---- Hero Upload Section ---- */
/* One left alignment line for title, description, upload area, sample action
   and Advanced options; only the dropzone's own content stays centered. */
.hero-upload {
  text-align: left;
  display: grid;
  /* Heading → description: 12px. */
  gap: var(--space-3);
  justify-items: start;
}

.hero-kicker {
  margin: 0;
  color: var(--accent);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}

.hero-upload :is(h1, .hero-h1) {
  /* Font now comes from the page-level .analyze-app heading rule; only the
     hero's own weight/size/tracking are set here. .hero-h1 is the demo-page
     variant: same visual role, demoted to h2 because the demo strip above it
     owns the page's h1. */
  font-weight: 700;
  letter-spacing: -0.02em;
  font-size: clamp(1.75rem, 3vw, 2.25rem);
  line-height: 1.2;
  color: var(--ink);
  max-width: 720px;
}

.hero-subtitle {
  margin: 0;
  color: var(--ink-soft);
  font-size: var(--text-lg);
  line-height: 1.5;
  max-width: 640px;
}
/* Supported-documents line: part of the same intro group, so pull it to 8px
   below the subtitle (grid gap 12 − 4) and set it one step smaller. */
.hero-subtitle-secondary {
  margin-top: calc(-1 * var(--space-1));
  font-size: var(--text-md);
}
/* EU AI Act Art. 50(1) interaction disclosure — deliberately its own class
   (not hero-subtitle-secondary) so it reads as an informational notice, not
   marketing copy, while staying legible (ink-soft, not faint/hidden). */
.ai-disclosure-note {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  max-width: 640px;
}
.ai-disclosure-note a { color: var(--accent); font-weight: 600; }
.ai-disclosure-note a:hover { color: var(--accent-strong); text-decoration: underline; }
/* The 4-item .hero-capabilities list was replaced by the 3-column
   .hero-benefits row (see "Hero benefits" further down), which keeps that
   block's original rule — no marketing icons, workspace-toned glyphs only. */

/* Analyze-in-workspace context chip below the hero title. */
.ws-context-banner {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 var(--space-3);
  padding: 6px var(--space-3);
  border-radius: 999px;
  background: var(--accent-light);
  border: 1px solid var(--border);
  color: var(--accent-strong);
  font-size: var(--text-md);
  font-weight: 500;
}
.ws-context-banner[hidden] { display: none; }
.ws-context-banner svg { flex: 0 0 auto; }
.ws-context-banner strong { font-weight: 700; }
.ws-context-clear {
  margin-left: var(--space-1);
  padding-left: var(--space-2);
  border-left: 1px solid var(--border);
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 500;
}
.ws-context-clear:hover { color: var(--ink); }

.hero-guest-note {
  margin: 0;
  color: var(--accent);
  font-size: var(--text-md);
  font-weight: 500;
}

/* ---- Analyze form ---- */
#analyze-form {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 720px;
  /* Intro group → upload area: 28px (grid gap 12 + 16). */
  margin-top: var(--space-4);
}

/* ---- Dropzone (Hero) ---- */
.dropzone-hero {
  display: grid;
  place-items: center;
  text-align: center;
  gap: var(--space-3);
  width: 100%;
  max-width: 720px;
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  /* Compact but comfortable drag-and-drop target: 32px all around keeps the
     card near ~300px tall on desktop without crowding its content. */
  padding: var(--space-8);
  background: var(--surface);
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, transform 0.2s;
}

.dropzone-hero:hover,
.dropzone-hero.drag-over {
  border-color: var(--accent);
  background: var(--accent-light);
  transform: translateY(-2px);
}

.dropzone-hero input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Icon tile (2026-08 rework): rounded accent-tinted square, the design's
   recurring "action origin" motif — same construction as the pillar numbers. */
.dropzone-icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 13px;
  background: var(--accent-light);
  color: var(--accent-text);
}
.dropzone-icon svg { display: block; width: 24px; height: 24px; }

.dropzone-title {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--ink);
}

.dropzone-sub {
  color: var(--ink-soft);
  font-size: var(--text-md);
  /* Heading → description: 8px (grid gap 12 − 4). */
  margin-top: calc(-1 * var(--space-1));
}

/* One dominant action in the zone: filled Browse files, outlined Use camera.
   Buttons are interactive content, so the surrounding <label> doesn't forward
   their clicks — app.js wires them to the pickers explicitly. */
.dropzone-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Primary button → secondary action: 16px. */
  gap: var(--space-4);
  width: 100%;
  max-width: 340px;
  /* Description → primary button: 20px (grid gap 12 + 8). */
  margin-top: var(--space-2);
}

@media (min-width: 641px) {
  .dropzone-actions-auth {
    flex-direction: row;
    gap: var(--space-3);
    max-width: 440px;
  }
  .dropzone-actions-auth > button { flex: 1; min-width: 0; }
}

/* Auth-state action swap: signed out, the same zone offers Sign in + a
   create-account line instead of Browse/camera (is-anon set by the server
   render and kept in sync by applyAuthState). Explicit display rules on both
   blocks — no [hidden]-vs-author-display ambiguity. */
.dropzone-hero .dropzone-actions-anon { display: none; }
.dropzone-hero.is-anon .dropzone-actions-auth { display: none; }
.dropzone-hero.is-anon .dropzone-actions-anon { display: flex; }

.dropzone-signup-line {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  color: var(--ink-soft);
  font-size: var(--text-md);
}
.dropzone-signup-link {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  color: var(--accent-text);
  font-size: inherit;
  font-weight: 600;
  cursor: pointer;
}
.dropzone-signup-link:hover { text-decoration: underline; }
.dropzone-anon-note {
  color: var(--ink-soft);
  font-size: var(--text-sm);
}
.dropzone-anon-note[hidden] { display: none; }

.dropzone-browse {
  min-height: 48px;
  margin-top: 0;
  border: none;
  border-radius: 10px;
  background: var(--accent-cta);
  color: #fff;
  font-size: var(--text-base);
  font-weight: 700;
  cursor: pointer;
}
.dropzone-browse:hover { background: var(--accent-cta-hover); }

.dropzone-camera {
  /* box-sizing: border-box makes min-height include the 1px top+bottom border,
     so the rendered height equals 48px — the same as .dropzone-browse which
     has no border.  Without it the camera button renders 2px shorter (46px). */
  box-sizing: border-box;
  min-height: 48px;
  margin-top: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--text-base);
  font-weight: 600;
  cursor: pointer;
}
.dropzone-camera:hover { border-color: var(--accent); color: var(--accent); }
.dropzone-camera svg { flex: 0 0 auto; }

.dropzone-formats {
  color: var(--ink-soft);
  /* Format names are machine vocabulary — mono per the rework's data-vs-prose
     split; one step smaller to keep the line from growing. */
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  /* Secondary action → supported-file line: 20px (grid gap 12 + 8). */
  margin-top: var(--space-2);
}

/* Batch processing opt-in — sits on its own line above the Analyze button. */
.batch-toggle {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  margin: 0;
  cursor: pointer;
  text-align: left;
}
.batch-toggle input {
  width: 16px;
  height: 16px;
  margin-top: 2px;
  flex-shrink: 0;
  cursor: pointer;
}
.batch-toggle-text {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.batch-toggle-label {
  font-weight: 600;
  font-size: var(--text-base);
  color: var(--ink);
}
.batch-toggle-hint {
  color: var(--ink-soft);
  font-size: var(--text-sm);
  line-height: 1.35;
}

.ocr-lang-label {
  flex: 1;
  display: grid;
  gap: 6px;
  font-size: var(--text-md);
  color: var(--ink-soft);
  text-align: left;
}

/* ---- Selected-file card (two-step upload) ----
   Replaces the dropzone once files are picked; "Analyze document" is the only
   dominant action in this state. #file-meta keeps its id — existing JS toggles
   its [hidden] attribute. */
/* Replaces the dropzone in place, so it takes the dropzone's position exactly:
   no top margin of its own, the "1 Upload document" eyebrow owns that gap. */
.file-selected {
  width: 100%;
  max-width: 720px;
  display: grid;
  justify-items: center;
  gap: var(--space-1);
  padding: var(--space-6) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  text-align: center;
}
.file-selected[hidden] { display: none !important; }

.file-selected-check {
  width: 48px;
  height: 48px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  background: var(--surface-soft);
  color: var(--success-text);
  margin-bottom: var(--space-1);
}

.file-selected #file-name {
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.file-selected #file-size {
  color: var(--ink-soft);
  font-size: var(--text-sm);
}

/* Scoped under .file-selected so this outranks the later .primary-btn rule
   (equal single-class specificity would lose on source order). */
.file-selected .file-selected-analyze {
  width: 100%;
  max-width: 340px;
  margin-top: var(--space-4);
  font-size: var(--text-lg);
  padding: 14px var(--space-6);
}

/* While a file is selected, the dropzone and the example link give way to the
   card; "Change file" is the way back. */
#analyze-form.is-file-selected .dropzone-hero,
#analyze-form.is-file-selected .try-example-link,
#analyze-form.is-file-selected .sample-docs {
  display: none;
}
/* With the example link gone, Advanced settings becomes the first block in
   #mode-panel-auto, and its own 24px top margin — which exists to separate it
   from the sample chips — stacks on the step-2 eyebrow's 12px. Same one-owner
   rule as .try-example-link: the eyebrow owns the gap to its content. */
#analyze-form.is-file-selected #advanced-options { margin-top: 0; }

/* ---- Pre-upload image quality note ----------------------------------------
   Amber inline note shown below the file card when blur or glare is detected.
   Warn-only: analysis always proceeds regardless. Hidden until JS reveals it. */
.file-quality-note {
  width: 100%;
  max-width: 720px;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding: var(--space-3) 14px;
  background: #fffbeb;
  border-left: 4px solid var(--accent-warm);
  border-radius: 8px;
  font-size: var(--text-sm);
  color: var(--ink);
  text-align: left;
}
.file-quality-note[hidden] { display: none !important; }
.file-quality-note-text { flex: 1; }
.file-quality-note-dismiss {
  flex-shrink: 0;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink-soft);
  padding: 0;
  min-height: 0;
}
.file-quality-note-dismiss:hover { color: var(--ink); }
[data-theme="dark"] .file-quality-note {
  background: rgba(245, 158, 11, 0.1);
  border-left-color: var(--accent-warm);
}

/* ---- Tertiary text-link buttons (Change file / Try an example) ---- */
.text-link-btn {
  margin-top: 0;
  background: none;
  border: none;
  color: var(--accent);
  font-size: var(--text-md);
  font-weight: 600;
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  min-height: 44px;
}
.text-link-btn:hover { text-decoration: underline; }
/* Sample-document entry point: a full-width secondary row (label left, arrow
   right) matching the column width so it reads as part of the same component
   family — but visually subordinate to the dropzone's primary action
   (transparent background, plain border). No top margin: since the three-mode
   redesign this is the first block inside #mode-panel-auto, so the "2 Configure
   extraction" eyebrow above already owns the gap, and a margin here would put
   step 2's label 32px from its content while step 1's sits at 12px. */
/* Reads as a secondary ACTION, not a collapsible section header: the accent-
   tinted icon and two-line label give it the weight of an offer, while the
   transparent fill keeps it clearly below the filled "Sign in to upload" CTA.
   The chevron is deliberate — this control expands the sample chips in place,
   so an arrow (which promises navigation) would misdescribe it. */
.try-example-link {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 64px;
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.try-example-link:hover { border-color: var(--accent); background: var(--surface-soft); }
.try-example-link:hover .try-example-label > svg { background: var(--accent-cta); color: #fff; }
.try-example-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.try-example-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}
/* Icon sits in an accent chip so the row announces itself at a glance — the
   same device the mode selector uses for its selected option. */
.try-example-label > svg {
  flex: 0 0 auto;
  box-sizing: content-box;
  width: 18px;
  height: 18px;
  padding: 7px;
  border-radius: 9px;
  background: var(--accent-light);
  color: var(--accent-text);
  transition: background 0.15s, color 0.15s;
}
.try-example-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.try-example-title { font-size: var(--text-md); font-weight: 600; color: var(--ink); }
.try-example-hint { font-size: var(--text-xs); font-weight: 400; color: var(--ink-soft); line-height: 1.35; }
.try-example-arrow { display: inline-flex; flex: 0 0 auto; color: var(--ink-soft); }
.try-example-arrow svg { transition: transform 0.15s; }
.try-example-link[aria-expanded="true"] .try-example-arrow svg { transform: rotate(180deg); }

/* ---- Advanced options disclosure ----
   Collapsed by default: document type, region, template, custom filename and
   batch processing stay one tap away without crowding the first screen.
   An independent bordered component — never flush against the card above. */
.advanced-options {
  width: 100%;
  max-width: 720px;
  margin-top: var(--space-6);
  text-align: left;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  /* Clips the summary's hover surface to the rounded corners. */
  overflow: hidden;
}
.advanced-options-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: 48px;
  padding: var(--space-2) var(--space-5);
  cursor: pointer;
  list-style: none;
  color: var(--ink);
  font-size: var(--text-base);
  font-weight: 600;
}
.advanced-options-summary::-webkit-details-marker { display: none; }
.advanced-options-summary:hover { background: var(--surface-soft); }
.advanced-options-summary:hover .advanced-options-chevron { color: var(--ink); }
.advanced-options-summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Sliders icon marks the row as a settings control (not a disabled input);
   it stays muted so the row keeps its low visual priority. */
.advanced-options-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.advanced-options-label svg { flex: 0 0 auto; color: var(--ink-soft); }
.advanced-options-chevron { flex: 0 0 auto; color: var(--ink-soft); transition: transform 0.15s, color 0.15s; }
.advanced-options[open] > .advanced-options-summary {
  border-bottom: 1px solid var(--border-light);
}
.advanced-options[open] > .advanced-options-summary .advanced-options-chevron {
  color: var(--ink);
  transform: rotate(180deg);
}

/* Automatic mode's four settings in one desktop row — document type, region,
   custom filename, batch — collapsing to two columns on narrow desktops and one
   below 700px. The batch note spans the full width beneath them. */
.advanced-options-body {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-5);
  padding: var(--space-5) var(--space-5) var(--space-4);
}
.advanced-options-body > .analyze-opt-note {
  grid-column: 1 / -1;
}
@media (max-width: 1100px) {
  .advanced-options-body { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.advanced-options .analyze-opt-note {
  text-align: left;
  margin: calc(-1 * var(--space-3)) 0 0;
}
/* Batch processing opens the panel's last group: the 20px grid gap alone. */
.advanced-options .batch-toggle { margin-top: 0; }

.filename-override {
  display: grid;
  gap: var(--space-2);
  justify-items: start;
  align-content: start;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  text-align: left;
}

.filename-override input {
  width: 100%;
  /* Match .doc-type-select metrics so every control in the panel reads as one
     family and rows align across both grid columns. */
  min-height: 44px;
  padding: 10px var(--space-3);
  border-radius: 10px;
  font-size: var(--text-base);
}

/* ---- Step Summary Bar ---- */
.step-summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-base);
  /* On narrow screens the filename + action buttons must wrap instead of
     forcing the page wider (was causing horizontal scroll on mobile). */
  flex-wrap: wrap;
}

.step-summary-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #dcfce7;
  color: var(--success-text);
  font-size: var(--text-xs);
  font-weight: 700;
  flex-shrink: 0;
}

.step-summary-text {
  flex: 1;
  color: var(--ink-soft);
}

.step-summary-text strong { color: var(--ink); }

.step-summary-change {
  margin-top: 0;
  padding: 5px var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  background: var(--surface-soft);
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
}

.step-summary-change:hover { background: var(--accent-light); }

/* ---- Step Header ---- */
.step-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.step-number {
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  background: var(--accent-light);
  padding: var(--space-1) 10px;
  border-radius: 6px;
}

.step-header h2 {
  font-size: var(--text-3xl);
}

/* ---- Guest Nudge ---- */
/* ── Modal overlay ── */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(2px);
}
.modal-overlay.hidden { display: none; }
/* The author `display: flex` above beats the UA `[hidden] { display: none }`,
   so overlays toggled via the `hidden` ATTRIBUTE (rather than the .hidden
   class) need this rule or they render permanently. */
.modal-overlay[hidden] { display: none; }
.modal-card {
  position: relative;
  background: #fff;
  border-radius: var(--radius);
  padding: 2rem 2.5rem;
  max-width: 420px;
  width: 90%;
  box-shadow: 0 20px 60px rgba(0,0,0,.15);
  animation: modalIn 0.25s ease-out;
}
@keyframes modalIn {
  from { opacity: 0; transform: translateY(12px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  background: none;
  border: none;
  font-size: var(--text-3xl);
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 0.25rem;
  transition: color 0.15s;
}
.modal-close:hover { color: var(--text); }

/* ── Batch file result popup (native <dialog>) ───────────────────────────── */
/* A read-only peek at a finished batch document, shown over the (still-polling)
   batch panel. showModal() gives top-layer stacking, an inert background, a
   ::backdrop, and Escape/focus handling for free. Internally scrollable so long
   field lists stay usable; becomes a full-screen sheet on phones. Mirrors the
   .share-modal <dialog> pattern. */
/* Leave-document confirmation — a small decision box, not a content modal.
   Sized to the sentence it asks so it reads as a question rather than a page. */
.leave-doc-dialog {
  border: none;
  padding: 0;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  background: var(--surface);
  color: var(--ink);
  width: min(420px, 92vw);
  animation: modalIn 0.2s ease-out;
}
.leave-doc-dialog::backdrop {
  background: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(2px);
}
.leave-doc-card {
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.leave-doc-title {
  margin: 0;
  font-size: var(--text-lg);
}
.leave-doc-text {
  margin: 0;
  color: var(--ink-soft);
  line-height: 1.5;
}
.leave-doc-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
/* Stacked on narrow screens so neither button gets squeezed under the other's
   label; the confirming action stays last in the DOM and reads first here. */
@media (max-width: 420px) {
  .leave-doc-actions { flex-direction: column-reverse; }
  .leave-doc-actions > button { width: 100%; }
}

.batch-preview-dialog {
  border: none;
  padding: 0;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  background: var(--surface);
  color: var(--ink);
  width: min(640px, 96vw);
  overflow: hidden; /* the body scrolls, not the dialog */
  animation: modalIn 0.2s ease-out;
}
.batch-preview-dialog::backdrop {
  background: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(2px);
}
.batch-preview-card {
  position: relative; /* anchor the absolute close button */
  max-height: 85vh;
  padding: 1.5rem 1.75rem;
  display: flex;
  flex-direction: column;
}
.batch-preview-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem 0.75rem;
  padding-right: 2rem; /* clear the close button */
}
.batch-preview-title {
  margin: 0;
  font-size: var(--text-lg);
  word-break: break-word;
}
.batch-preview-type {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--muted);
  background: var(--surface-soft);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  text-transform: capitalize;
}
.batch-preview-body {
  flex: 1 1 auto;
  min-height: 0; /* let the flex child scroll instead of growing the card */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  margin-top: 0.75rem;
  overscroll-behavior: contain;
}
.batch-preview-summary {
  margin: 0 0 0.5rem;
  font-size: var(--text-base);
  color: var(--muted);
}
.batch-preview-fields {
  margin: 0.25rem 0 0;
}
.batch-preview-field {
  display: grid;
  grid-template-columns: minmax(120px, 38%) 1fr;
  gap: 0.5rem 1rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--border, #e5e7eb);
}
.batch-preview-label {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--muted);
  text-transform: capitalize;
}
.batch-preview-value {
  margin: 0;
  font-size: var(--text-base);
  color: var(--text);
  word-break: break-word;
}
.batch-preview-empty { color: var(--muted); }
.batch-preview-empty-note,
.batch-preview-loading,
.batch-preview-error {
  margin: 0.75rem 0;
  font-size: var(--text-base);
  color: var(--muted);
}
.batch-preview-error { color: #ef4444; }
.batch-preview-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: middle;
}
.batch-preview-dot--mid { background: #eab308; }
.batch-preview-dot--low { background: #ef4444; }
.batch-preview-warnings {
  margin-top: 0.9rem;
  padding: 0.6rem 0.8rem;
  background: rgba(234, 179, 8, 0.12);
  border-radius: 8px;
  font-size: var(--text-md);
}
.batch-preview-warnings-title {
  margin: 0 0 0.3rem;
  font-weight: 600;
}
.batch-preview-warnings ul { margin: 0; padding-left: 1.1rem; }
.batch-preview-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}

@media (max-width: 640px) {
  /* Full-screen sheet: comfortable on phones, no cramped centred card. The
     dialog fills the viewport; the card's flex column keeps the actions row at
     the bottom while the body scrolls (no sticky footer needed). */
  .batch-preview-dialog {
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    border-radius: 0;
  }
  .batch-preview-card {
    max-height: none;
    height: 100%;
    padding: 1.25rem 1.15rem calc(1rem + env(safe-area-inset-bottom, 0px));
  }
  .batch-preview-field {
    grid-template-columns: 1fr;
    gap: 0.1rem;
    padding: 0.55rem 0;
  }
  .batch-preview-actions .primary-btn,
  .batch-preview-actions .ghost-btn {
    flex: 1 1 auto;
    text-align: center;
  }
}

.guest-nudge-card { text-align: center; }

.guest-nudge-text {
  margin: 0 0 1.25rem;
  color: var(--text);
  font-size: var(--text-lg);
  font-weight: 500;
  line-height: 1.5;
}

.guest-nudge-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: center;
}

.ghost-btn {
  margin-top: 0;
  padding: var(--space-2) var(--space-4);
  background: transparent;
  border: 1px solid #c7d2fe;
  border-radius: 8px;
  color: var(--accent-strong);
  font-size: var(--text-md);
  font-weight: 600;
  cursor: pointer;
}

.ghost-btn:hover { background: #e0e7ff; }

/* ---- Panels ---- */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: var(--space-6);
}

/* A review is one workbench. Its source and fields own the surfaces; an
   additional outer card only spends space and duplicates their boundaries. */
.panel.results {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* ---- Buttons ---- */
/* textarea included: without it a textarea falls back to the browser's
   monospace default, which is how a control ends up in a font nobody chose. */
input, select, button, textarea { font: inherit; }

input, select {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px var(--space-3);
  background: var(--surface-input);
  color: var(--ink);
  font-size: var(--text-base);
}

input:focus, select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

label {
  display: grid;
  gap: 6px;
  font-size: var(--text-md);
  color: var(--ink-soft);
}

button {
  margin-top: 0;
  border: 0;
  border-radius: 8px;
  padding: 10px 18px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, transform 0.15s;
}

button:hover { transform: translateY(-1px); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button:disabled { cursor: not-allowed; opacity: 0.6; transform: none; }

/* Both button classes are worn by <a> as often as by <button>, but everything
   below used to come from the bare `button {}` rule above — which an anchor
   never matches. .secondary-btn was the visible casualty: it declares no
   padding and no radius of its own, so the landing's signed-in
   <a class="secondary-btn">Open DocAI</a> rendered as a square-cornered box
   hugging its own text, and profile.html carried an inline-style workaround.
   Declared here, the anchors get a real button box.

   Values match what `button {}` already supplies, so every <button> instance
   computes exactly as before. inline-BLOCK, not inline-flex, is deliberate:
   icon+label buttons across this file rely on default inline whitespace for
   the gap between glyph and text (see .has-ai-icon), and flex would collapse
   that whitespace and split the label into an anonymous flex item. */
.primary-btn,
.secondary-btn {
  display: inline-block;
  text-align: center;
  cursor: pointer;
  transition: background 0.15s, transform 0.15s;
}

.primary-btn:hover,
.secondary-btn:hover { transform: translateY(-1px); }

/* Must follow the hover rule, and must be class-level. `button:disabled` (0,1,1)
   used to beat `button:hover` (0,1,1) on source order alone; the class hover
   above is (0,2,0) and outranks it, which would let a disabled #submit-btn lift
   under the cursor while still showing `not-allowed`. */
.primary-btn:disabled,
.secondary-btn:disabled { transform: none; }

.primary-btn {
  background: var(--accent-cta);
  color: #fff;
  font-weight: 700;
  padding: var(--space-3) var(--space-6);
  border-radius: 10px;
  font-size: var(--text-base);
  /* Without this the anchor variants keep the UA underline inside a filled
     button. */
  text-decoration: none;
}

.primary-btn:hover { background: var(--accent-cta-hover); }

.primary-btn.is-loading,
.primary-btn.is-saving {
  opacity: 0.8;
  cursor: not-allowed;
}
.primary-btn.is-loading::before,
.primary-btn.is-saving::before {
  content: '';
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid rgba(255,255,255,0.35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: btn-spin 0.7s linear infinite;
  vertical-align: middle;
  margin-right: var(--space-2);
  margin-bottom: 1px;
}
@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

.secondary-btn {
  margin-top: 0;
  background: var(--surface-soft);
  color: var(--ink);
  border: 1px solid var(--border);
  font-weight: 600;
  text-decoration: none;
  /* Same box `button {}` hands the <button> instances — repeated so the <a>
     instances get it too. Keep in sync with that rule. */
  padding: 10px 18px;
  border-radius: 8px;
}

.secondary-btn:hover { background: var(--surface-soft-hover); }

.primary-action-btn {
  margin-top: 0;
  background: var(--accent-cta);
  color: #fff;
  font-weight: 700;
  padding: var(--space-3) 18px;
  border-radius: 10px;
}

.primary-action-btn:hover { background: var(--accent-cta-hover); }

.tiny-btn {
  margin-top: 0;
  padding: 6px 10px;
  border-radius: 8px;
  font-size: var(--text-xs);
  font-weight: 600;
  background: var(--surface-soft);
  color: var(--ink);
  border: 1px solid var(--border);
}

.tiny-btn:hover { background: var(--surface-soft-hover); }

/* ---- Progress ---- */
/* In-place analyzing state — occupies the workspace where results will appear,
   so the user clearly sees work is ongoing without the page jumping. */
.analyzing-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-height: 320px;
  padding: var(--space-8) var(--space-4);
  text-align: center;
}

.analyzing-hint {
  margin: 0;
  max-width: 460px;
  font-size: var(--text-base);
  color: var(--ink-soft);
}

.progress-container {
  margin: 0 auto;
  display: grid;
  gap: 11px;
  width: 100%;
  max-width: 600px;
  padding: var(--space-4) 18px;
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
}

.progress-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.progress-spinner {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid var(--accent-light);
  border-top-color: var(--accent);
  animation: progress-spin 0.7s linear infinite;
}

@keyframes progress-spin {
  to { transform: rotate(360deg); }
}

.progress-title {
  flex: 1;
  font-weight: 600;
  font-size: var(--text-base);
  color: var(--ink);
}

.progress-percent {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: var(--text-base);
  color: var(--accent);
}

.progress-bar {
  height: 8px;
  background: var(--border);
  border-radius: 4px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  border-radius: 3px;
  transition: width 0.3s ease;
  animation: progress-pulse 1.5s ease-in-out infinite;
}

@keyframes progress-pulse {
  0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; }
}

.progress-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.progress-status {
  margin: 0;
  font-size: var(--text-md);
  color: var(--ink-soft);
  flex: 1;
}

.cancel-btn {
  margin-top: 0;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  font-weight: 600;
  background: transparent;
  color: var(--ink-soft);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
  flex-shrink: 0;
}
.cancel-btn:hover {
  color: var(--danger);
  border-color: var(--danger);
  background: var(--surface-soft);
  transform: none;
}

/* ---- Status Messages ---- */
.status {
  margin: var(--space-3) 0 0;
  min-height: 1.2em;
  color: var(--ink-soft);
  text-align: center;
}

.status.success { color: var(--success-text); }
.status.error { color: var(--danger); }

/* ---- Results ---- */
.results.hidden, .hidden { display: none; }

.results-top {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  align-items: flex-end;
}

.results-label {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
}

.confidence {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--accent);
}

.confidence-high { color: var(--success-text); }
.confidence-mid  { color: var(--accent-warm); }
.confidence-low  { color: var(--danger); }

.results-hint {
  margin: 6px 0 var(--space-3);
  color: var(--ink-soft);
  font-size: var(--text-base);
}

/* ---- Validation Toolbar ---- */
.validation-toolbar {
  margin: var(--space-4) 0;
  display: grid;
  grid-template-columns: minmax(240px, 1fr) auto;
  gap: var(--space-3) 14px;
  align-items: start;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.validation-status-wrap { display: grid; gap: var(--space-1); }

.validation-primary-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.toolbar-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

.meta-toggle {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--ink-soft);
  font-size: var(--text-sm);
}

.meta-toggle input { width: 16px; height: 16px; }

.financial-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 10px;
}

/* ---- Extraction Source Chip ---- */
.extraction-chip {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: var(--space-1) 10px;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
}
/* The generic .hidden utility sits EARLIER in this file, so this rule's
   display:inline-flex silently beat it (same specificity, later source order)
   — the 5th occurrence of the hide-mechanism trap. The compound guard makes
   `classList.add("hidden")` actually hide every chip in the family. */
.extraction-chip.hidden { display: none; }

.extraction-chip-demo {
  background: #e0e7ff;
  color: #3730a3;
}

.extraction-chip-ai {
  background: #dcfce7;
  color: #166534;
}

.extraction-chip-fallback {
  background: #fef3c7;
  color: #92400e;
}

/* Structured e-invoice data — the highest-trust result the product can show. */
.extraction-chip-exact {
  background: #cffafe;
  color: #155e75;
}

[data-theme="dark"] .extraction-chip-demo {
  background: rgba(99, 102, 241, 0.2);
  color: #a5b4fc;
}

[data-theme="dark"] .extraction-chip-ai {
  background: rgba(34, 197, 94, 0.15);
  color: #4ade80;
}

[data-theme="dark"] .extraction-chip-fallback {
  background: rgba(245, 158, 11, 0.15);
  color: #fbbf24;
}

[data-theme="dark"] .extraction-chip-exact {
  background: rgba(6, 182, 212, 0.15);
  color: #67e8f9;
}

/* ---- Status Badges ---- */
.status-badge {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 5px 10px;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
}

.status-badge.status-ok { background: #dcfce7; color: #166534; }
.status-badge.status-medium { background: #fef3c7; color: #92400e; }
.status-badge.status-high { background: #fecaca; color: #991b1b; }
.status-badge.status-neutral { background: var(--surface-soft); color: var(--ink-soft); }

/* ---- Warnings ---- */
.warnings {
  border-left: 4px solid var(--accent-warm);
  background: #fffbeb;
  border-radius: 8px;
  padding: var(--space-3) 14px;
  margin-bottom: 14px;
}

.warnings ul { margin: 6px 0 0; padding-left: 18px; }

/* Compact variant (renderWarnings): a single short warning shrinks to one
   inline "⚠ …" line instead of the full-width labeled block. The global
   .hidden !important rule still wins over this display value. */
.warnings.is-compact {
  display: inline-block;
  padding: 6px 12px;
  font-size: var(--text-sm);
}
.warnings.is-compact .results-label { display: none; }
.warnings.is-compact ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
.warnings.is-compact li::before { content: "⚠ "; }

/* A long single warning stays on the compact row: clamped to one line, with a
   "Details" toggle that reveals the rest in place. Severity (the ⚠ glyph and
   the panel's amber treatment) is unaffected. */
.warnings.is-compact.is-clamped {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  max-width: 100%;
}
.warnings.is-compact.is-clamped ul { min-width: 0; }
.warnings.is-compact.is-clamped li {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.warnings.is-compact.is-clamped.is-expanded li {
  white-space: normal;
  overflow: visible;
}
.warning-details-toggle {
  flex: 0 0 auto;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}
.warning-details-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Compact List ---- */
.compact-list {
  margin: 6px 0 10px;
  padding-left: 18px;
  color: var(--ink-soft);
  font-size: var(--text-base);
}

.compact-list li { margin-bottom: var(--space-1); }

.missing-field-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
}

.missing-field-label { overflow-wrap: anywhere; }

.list-action-btn { margin-top: 0; padding: 5px 9px; }
.list-inline-btn { margin-top: 0; margin-bottom: 6px; padding: 5px 9px; }

/* ---- Validation Layout ---- */
.validation-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(320px, 0.95fr);
  gap: var(--space-4);
  margin-bottom: var(--space-3);
  align-items: start;
}

.viewer-card, .editor-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  background: var(--surface);
  min-width: 0;
}

/* ---- Collapsible card panel (Field Validation + Document Preview) ---- */
.card-panel {
  padding: 0; /* override default card padding — content goes inside summary + body */
}

.card-panel-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 13px var(--space-4);
  cursor: pointer;
  user-select: none;
  border-radius: var(--radius);
  list-style: none;
  transition: background 0.15s;
}
.card-panel-summary::-webkit-details-marker { display: none; }
.card-panel-summary::marker { display: none; }
.card-panel-summary:hover { background: var(--surface-soft); }

.card-panel[open] > .card-panel-summary {
  border-bottom: 1px solid var(--border-light);
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.card-panel-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.card-panel-title {
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.2;
}

.card-panel-desc {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  line-height: 1.3;
}

.card-panel-chevron {
  color: var(--ink-soft);
  flex-shrink: 0;
  transition: transform 0.2s ease;
}
.card-panel[open] > .card-panel-summary .card-panel-chevron {
  transform: rotate(180deg);
}

.card-panel-body {
  padding: 14px var(--space-4) var(--space-4);
}

/* ---- Line Items ---- */
/* #line-items-panel is now a thin wrapper around a .card-panel <details>;
   visual presentation (border, radius, background) comes from .card-panel. */
.line-items-panel {
  margin: 0 0 var(--space-3);
  min-width: 0;
}

.line-items-editor { display: grid; gap: var(--space-3); min-width: 0; }

.line-items-standalone {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 14px;
  min-width: 0;
}

.line-items-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: var(--space-2);
  flex-wrap: wrap;
}

.line-items-title {
  margin: 0;
  color: var(--accent);
  font-weight: 700;
}

/* The enclosing .line-items-panel > .card-panel-summary already names this
   section, but only with the hardcoded "Line items" label — this title
   carries the FIELD's own label, which a custom schema renames
   ("Positionen", "Charges") and which is the only thing distinguishing two
   stacked tables. So the duplicate-heading class is set by the renderer, and
   only for the one case where the two truly say the same thing: a single
   table whose own label is that same generic word. Kept in the DOM either
   way so assistive tech still gets a heading for this block. */
.line-items-title.is-duplicate-heading {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.line-items-subtitle {
  margin: var(--space-1) 0 0;
  color: var(--ink-soft);
  font-size: var(--text-sm);
}

/* ---- PDF Preview ---- */
#pdf-preview {
  display: block;
  width: 100%;
  min-height: 620px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-soft);
}
/* The id selector above outranks the generic `.hidden { display:none }`, so the
   raw PDF <object> never actually hid (it kept rendering — as a black box on
   browsers without an inline PDF viewer). Restore hide-ability. */
#pdf-preview.hidden { display: none; }
/* Mobile browsers don't embed PDFs inline via <object> (it renders as an empty
   black area), so on small screens suppress the raw embed and surface the
   open/download hint instead — but ONLY when that <object> is the active preview
   (data-preview-mode="object"). Image and pdf.js-canvas previews render fine on
   mobile, so a PNG/JPG (or the interactive #doc-preview) keeps its inline view
   and must never trip the "inline PDF unsupported" hint. The attribute-scoped
   selector (specificity 2,1,0) also outranks the JS-applied `.hidden` (0,1,0),
   so the hint still shows reliably when the <object> embed is the active mode. */
@media (max-width: 700px) {
  #pdf-section[data-preview-mode="object"] #pdf-preview { display: none; }
  #pdf-section[data-preview-mode="object"] #pdf-fallback { display: block; }
}

/* ---- Fields Editor ---- */
.fields-editor {
  display: grid;
  gap: 10px;
  overflow-x: hidden;
  padding-right: 2px;
}

/* "+ Add field" trigger + inline composer below the field list. */
.add-field-trigger { margin-top: var(--space-2); }

.add-field-composer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: var(--space-2);
}
/* `.hidden` is defined earlier, so the flex rule above would win on source order;
   this compound selector outranks it so .hidden actually hides the composer. */
.add-field-composer.hidden { display: none; }

.add-field-name-input {
  flex: 1 1 200px;
  min-width: 0;
  padding: 6px 9px;
  font: inherit;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}

.field-group {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--space-3);
}

.field-group-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
  border-bottom: 1px solid var(--border-light);
  padding-bottom: var(--space-2);
  margin-bottom: 10px;
}

.field-group-title {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
}

.field-group-subtitle {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.field-group-count {
  margin-top: 1px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
  background: var(--surface-soft);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
}
/* Group-header status variants (groupReviewStatus in app.js). Only the amber
   "needs review" badge is emphasized — verified counts stay readable but quiet
   (plain green text, no pill) so confirmed fields never visually dominate.
   The base rule above styles .is-partial ("C of N reviewed") as neutral. */
.field-group-count.is-reviewed {
  color: var(--success-text);
  background: transparent;
  border-color: transparent;
}
.field-group-count.is-flag {
  color: var(--warn-text);
  background: var(--warn-soft);
  border-color: rgba(217, 119, 6, 0.35);
}
.field-group-count.is-neutral {
  color: var(--ink-soft);
  background: var(--surface-soft);
  border-color: var(--border);
}

.field-group-body { display: grid; gap: 10px; }

.field-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--space-3);
  background: var(--surface);
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  transition: border-left-color 0.2s, background 0.2s;
}

.field-card.is-confirmed,
.line-items-standalone.is-confirmed {
  border-left: 3px solid var(--success);
  background: rgba(5, 150, 105, 0.03);
}

/* Amber left-border when the entire line-items field is flagged as needing
   correction — mirrors the .field-card.is-needs-correction left indicator. */
.line-items-standalone.is-needs-correction {
  border-left: 3px solid var(--accent-warm);
  background: rgba(217, 119, 6, 0.03);
}

.field-card.empty {
  background: var(--surface-soft);
  border-style: dashed;
  padding: var(--space-5);
  text-align: center;
  color: var(--ink-soft);
  font-size: var(--text-base);
}

.field-card-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 10px;
  margin-bottom: var(--space-2);
}

.field-card-controls {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.field-edit-btn {
  margin-top: 0;
  min-width: 44px;
  padding: 5px 10px;
  font-size: var(--text-xs);
  font-weight: 600;
}

.field-edit-btn.is-editing {
  background: var(--accent-light);
  border-color: #c7d2fe;
  color: var(--accent);
}

/* Field name row: name + inline confidence badge */
.field-key-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  min-width: 0;
}

.field-key {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* Inline confidence badge */
/* Confidence chip — a machine-produced number, so it is set in the data mono
   (2026-08 rework) on the status TEXT/tint token pairs, which are
   contrast-pinned in both themes. No [data-theme] overrides needed. */
.field-confidence {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 5px;
  white-space: nowrap;
  flex-shrink: 0;
}
.field-confidence-high {
  background: var(--success-soft);
  color: var(--success-text);
}
.field-confidence-mid {
  background: var(--warn-soft);
  color: var(--warn-text);
}
.field-confidence-low {
  background: var(--danger-soft);
  color: var(--danger-text);
}

.field-meta {
  margin: 6px 0 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.field-value-read {
  margin: var(--space-2) 0 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-soft);
  padding: 10px var(--space-3);
  min-height: 38px;
  line-height: 1.4;
  overflow-wrap: anywhere;
  /* Extracted values are machine output — the data mono is the honesty cue
     that separates them from prose (2026-08 rework). */
  font-family: var(--font-mono);
  font-size: var(--text-md);
}

.field-meta-title {
  margin: 0 0 var(--space-2);
  font-weight: 700;
  color: var(--accent);
}

/* Confirm-field button: primary-styled (accent fill) on unconfirmed cards,
   quiet green toggle on confirmed cards. Sized by .tiny-btn. */
.field-confirm-btn {
  white-space: nowrap;
  background: var(--accent-cta);
  border-color: var(--accent-cta);
  color: #fff;
}
.field-confirm-btn:hover {
  background: var(--accent-cta-hover);
  border-color: var(--accent-cta-hover);
}
.field-confirm-btn.is-confirmed {
  background: rgba(5, 150, 105, 0.08);
  border-color: var(--success);
  color: var(--success-text);
}
.field-confirm-btn.is-confirmed:hover {
  background: rgba(5, 150, 105, 0.15);
  border-color: var(--success);
}

/* ---- Tables ---- */
.table-field-wrap { margin-top: 2px; }

.table-inline-wrap {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
  overflow-y: auto;
  max-height: 420px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}

.table-inline-wrap.read-only { background: var(--surface-soft); }
.table-inline-wrap.editable { background: var(--surface); }

.line-items-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  table-layout: fixed;
}

.line-items-table.read-only { min-width: 620px; font-size: var(--text-sm); }

.line-items-table.editable {
  width: max-content;
  min-width: 100%;
  font-size: var(--text-sm);
  table-layout: auto;
}

.line-items-table th, .line-items-table td {
  padding: 6px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
  overflow-wrap: anywhere;
}

.line-items-table.read-only th, .line-items-table.read-only td { padding: 5px; }
.line-items-table.editable th, .line-items-table.editable td { padding: 7px; }

.line-items-table thead th {
  background: var(--surface-soft);
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  position: sticky;
  top: 0;
  z-index: 2;
}

.line-items-table.read-only thead th { white-space: normal; line-height: 1.2; font-size: var(--text-xs); }
.line-items-table.editable thead th { white-space: normal; line-height: 1.2; font-size: var(--text-xs); }

.line-items-table input {
  width: 100%;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px;
  font-size: var(--text-xs);
  background: var(--surface);
}

.line-items-table.editable input { padding: var(--space-2) 9px; font-size: var(--text-sm); }

.line-items-table textarea {
  width: 100%;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px var(--space-2);
  background: var(--surface);
  resize: vertical;
  min-height: 46px;
  line-height: 1.3;
}

.line-items-table.editable textarea { padding: var(--space-2) 9px; min-height: 52px; font-size: var(--text-sm); }

.line-items-table .col-row { width: 42px; min-width: 42px; text-align: center; }
.line-items-table .row-index { text-align: center; color: var(--ink-soft); font-weight: 600; }

.table-cell-read { min-height: 20px; line-height: 1.35; color: var(--ink); }
.table-cell-read.multiline { white-space: pre-wrap; }

.line-items-table .col-description { min-width: 150px; }
.line-items-table.read-only .col-description { width: 96px; min-width: 96px; max-width: 118px; }
.line-items-table.editable .col-description { width: clamp(180px, 30vw, 340px); min-width: 180px; max-width: 360px; }

.line-items-table .col-unit_price, .line-items-table .col-net_amount,
.line-items-table .col-vat_amount, .line-items-table .col-gross_amount { width: 64px; min-width: 64px; max-width: 72px; }

.line-items-table.read-only .col-unit_price, .line-items-table.read-only .col-net_amount,
.line-items-table.read-only .col-vat_amount, .line-items-table.read-only .col-gross_amount { width: 78px; min-width: 78px; max-width: 94px; }

.line-items-table.editable .col-unit_price, .line-items-table.editable .col-net_amount,
.line-items-table.editable .col-vat_amount, .line-items-table.editable .col-gross_amount { width: clamp(94px, 9vw, 132px); min-width: 94px; max-width: 132px; }

/* Right-align money/quantity columns so digits stack for fast vertical
   scanning. Keyed off the same column-name synonyms columnLabel() in app.js
   already recognizes (invoice/receipt/PO line items use different keys for
   the same concept), so this covers real schemas rather than one fixed set.
   Columns outside this list (description, unit, custom diagnostic columns
   like unit_price_basis) keep the default left alignment. */
.line-items-table th.col-unit_price, .line-items-table td.col-unit_price,
.line-items-table th.col-price, .line-items-table td.col-price,
.line-items-table th.col-net_amount, .line-items-table td.col-net_amount,
.line-items-table th.col-vat_amount, .line-items-table td.col-vat_amount,
.line-items-table th.col-gross_amount, .line-items-table td.col-gross_amount,
.line-items-table th.col-line_total, .line-items-table td.col-line_total,
.line-items-table th.col-total, .line-items-table td.col-total,
.line-items-table th.col-amount, .line-items-table td.col-amount,
.line-items-table th.col-quantity, .line-items-table td.col-quantity,
.line-items-table th.col-qty, .line-items-table td.col-qty,
.line-items-table th.col-vat_rate, .line-items-table td.col-vat_rate,
.line-items-table th.col-tax_rate, .line-items-table td.col-tax_rate {
  text-align: right;
  /* Small extra gutter so adjacent right-aligned figures (e.g. two amount
     columns) don't visually run together. Both this and the right alignment
     itself are undone by the stacked-card layouts below (editable at 900px,
     read-only at 560px), which no longer place columns side by side. */
  padding-left: 10px;
}
.line-items-table.editable td.col-unit_price input, .line-items-table.editable td.col-price input,
.line-items-table.editable td.col-net_amount input, .line-items-table.editable td.col-vat_amount input,
.line-items-table.editable td.col-gross_amount input, .line-items-table.editable td.col-line_total input,
.line-items-table.editable td.col-total input, .line-items-table.editable td.col-amount input,
.line-items-table.editable td.col-quantity input, .line-items-table.editable td.col-qty input,
.line-items-table.editable td.col-vat_rate input, .line-items-table.editable td.col-tax_rate input {
  text-align: right;
}

.line-items-table .col-vat_rate, .line-items-table .col-quantity,
.line-items-table .col-unit { width: 58px; min-width: 58px; max-width: 64px; }

.line-items-table.read-only .col-vat_rate, .line-items-table.read-only .col-quantity,
.line-items-table.read-only .col-unit { width: 70px; min-width: 70px; max-width: 84px; }

.line-items-table.editable .col-vat_rate, .line-items-table.editable .col-quantity,
.line-items-table.editable .col-unit { width: clamp(82px, 8vw, 114px); min-width: 82px; max-width: 114px; }

.table-actions-cell { width: 76px; min-width: 76px; }
.line-items-table.editable .table-actions-cell { width: 110px; min-width: 110px; }

.line-items-standalone .line-items-table.read-only { min-width: 680px; }
.line-items-standalone .line-items-table.editable { min-width: 100%; }
.line-items-table.read-only td { background: var(--surface); }

/* ---- Code Block / Details ---- */
details {
  margin-top: 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}

summary {
  cursor: pointer;
  padding: 10px var(--space-3);
  font-weight: 600;
  color: var(--ink);
}

.code-block {
  margin: 0;
  padding: 10px var(--space-3) 14px;
  max-height: 320px;
  overflow: auto;
  border-top: 1px solid var(--border);
  background: var(--surface-soft);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Processing metadata section in the Details tab */
.processing-meta-list {
  margin: var(--space-2) 0 var(--space-1);
  padding: 0 var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.processing-meta-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.4;
}

/* `display: flex` above is an author rule that beats the UA `[hidden]` rule, so
   the empty-row hiding in fillProcessingMeta needs an explicit override. */
.processing-meta-row[hidden] { display: none; }

.processing-meta-row dt {
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}

.processing-meta-row dd {
  margin: 0;
  color: var(--ink);
  word-break: break-all;
}

/* ---- Generic Table ---- */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 10px;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-base);
}

th, td {
  text-align: left;
  padding: 10px var(--space-3);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

thead th { background: var(--surface-soft); font-weight: 600; }
tbody tr:last-child td { border-bottom: 0; }

/* ---- Auth Popup ---- */
.auth-popup {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: var(--space-5);
  background: rgba(0,0,0,0.4);
}
.auth-popup.hidden { display: none; }

.auth-popup-card {
  width: min(480px, 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  position: relative;
  display: grid;
  gap: var(--space-3);
}

.auth-popup-kicker {
  margin: 0;
  color: var(--accent);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.auth-popup-description {
  margin: 0;
  color: var(--ink-soft);
  line-height: 1.45;
}

.auth-popup-close {
  justify-self: end;
  padding: 6px var(--space-3);
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-soft);
  font-size: var(--text-sm);
  font-weight: 600;
}

.auth-popup-close:hover { background: var(--surface-soft); }

.auth-popup-actions {
  margin-top: var(--space-1);
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.auth-popup-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  padding: 10px 18px;
  border: 1px solid transparent;
  background: var(--accent-cta);
  color: #fff;
  font-weight: 600;
  text-decoration: none;
}

.auth-popup-link.secondary {
  background: var(--surface-soft);
  color: var(--ink);
  border-color: var(--border);
}

.auth-popup-link:hover { filter: brightness(0.95); }
.auth-popup-link.secondary:hover { background: var(--surface-soft-hover); }

/* ---- Auth Pages (login, register, account) ---- */
.auth-shell { width: min(760px, 92vw); }
.billing-shell { width: min(1080px, 94vw); }

.auth-panel { display: grid; gap: 10px; }
.billing-panel { display: grid; gap: var(--space-3); }
.billing-session-actions { margin-top: var(--space-1); }

#billing-status.error { color: var(--danger); }

.auth-panel-clerk-width { width: min(476px, 100%); margin-inline: auto; }
.auth-panel-clerk-width .clerk-auth-root { width: 100%; max-width: 430px; }

.auth-form { display: grid; gap: 10px; }
.clerk-auth-root { width: min(430px, 100%); margin: 2px auto; }

.oauth-actions { display: grid; gap: var(--space-2); margin-bottom: var(--space-2); }

.oauth-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  font-weight: 600;
  padding: 10px var(--space-3);
}

.oauth-btn:hover { background: var(--surface-soft); }

.checkbox-line { display: flex; align-items: center; gap: var(--space-2); color: var(--ink-soft); }
.checkbox-line input { width: 16px; height: 16px; }

.auth-note { margin: var(--space-1) 0 0; font-size: var(--text-base); color: var(--ink-soft); overflow-wrap: anywhere; }
.auth-note a, .account-links a { color: var(--accent); font-weight: 600; text-decoration: none; }
.auth-note a:hover, .account-links a:hover { text-decoration: underline; }

.twofa-setup-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 14px;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-soft);
  padding: var(--space-3);
}

.twofa-qr {
  width: 170px;
  height: 170px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}

.twofa-secret {
  margin: var(--space-1) 0;
  font-family: var(--font-mono);
  font-size: var(--text-base);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--space-2);
  word-break: break-all;
}

.account-meta {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-soft);
  padding: 10px var(--space-3);
}

.account-meta p { margin: 0 0 6px; font-size: var(--text-base); }
.account-meta p:last-child { margin-bottom: 0; }

.account-links {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.danger-btn { background: #dc2626; color: #fff; }
.danger-btn:hover { background: #b91c1c; }

.login-form { max-width: 420px; display: grid; gap: 10px; }

/* ---- Admin ---- */
.admin-shell { width: min(920px, 92vw); }

.admin-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.admin-header a { color: var(--accent); font-weight: 600; text-decoration: none; }
.admin-header a:hover { text-decoration: underline; }

.admin-grid {
  display: grid;
  gap: 14px;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
}

.admin-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  background: var(--surface);
}

.admin-meta { margin: var(--space-2) 0 var(--space-3); color: var(--ink-soft); font-size: var(--text-base); }
.admin-inline-label { font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-soft); margin-bottom: var(--space-1); }
.admin-current { margin: 0; font-weight: 700; color: var(--accent); }
.admin-help { margin: 10px 0 0; color: var(--ink-soft); font-size: var(--text-md); }

.admin-footer {
  margin-top: 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.admin-note { margin: 0; color: var(--ink-soft); font-size: var(--text-md); }
.admin-logout { margin-top: 0; background: #dc2626; }

.stats-summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3); margin-bottom: var(--space-4); }

.stat-item {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--space-3) var(--space-2);
  text-align: center;
}

.stat-value { display: block; font-size: var(--text-2xl); font-weight: 600; color: var(--accent); }
.stat-label { display: block; font-size: var(--text-2xs); color: var(--ink-soft); margin-top: var(--space-1); }
.stats-summary h3 { font-size: var(--text-base); margin: var(--space-4) 0 var(--space-2); color: var(--ink); }

.table-wrapper { overflow-x: auto; margin-top: var(--space-2); }

.data-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.data-table th, .data-table td { padding: var(--space-2) 10px; text-align: left; border-bottom: 1px solid var(--border); }
.data-table th { background: var(--surface-soft); font-weight: 600; color: var(--ink-soft); }
.data-table tr:hover td { background: var(--surface-soft); }

.small-btn {
  padding: var(--space-1) 10px;
  font-size: var(--text-xs);
  background: var(--accent-cta);
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.small-btn:hover { background: var(--accent-cta-hover); }
.inline-form { display: inline-block; }

/* ---- History Page ---- */
.history-panel { padding-top: 0; }

.history-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 14px;
  flex-wrap: wrap;
}

.history-controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
  align-items: end;
}

.inline-control {
  display: grid;
  gap: 6px;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.history-status {
  margin: var(--space-2) 0 var(--space-3);
  min-height: 1.1em;
  color: var(--ink-soft);
}

.history-status.success { color: var(--success-text); }
.history-status.error { color: var(--danger); }

.history-list {
  display: grid;
  gap: 10px;
  max-height: min(70vh, 700px);
  overflow: auto;
  padding-right: 2px;
}

.history-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--space-3) 14px;
}

.history-item-main { min-width: 0; }

.history-item-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

.history-file {
  margin: 0;
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.history-meta { margin: 0; font-size: var(--text-sm); color: var(--ink-soft); }
.history-summary { margin: var(--space-1) 0 0; font-size: var(--text-md); color: var(--ink); line-height: 1.35; }

.history-facts { margin: 6px 0 2px; display: flex; flex-wrap: wrap; gap: 6px; }

.history-fact {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px var(--space-2);
  background: var(--surface);
}

.history-fact strong { color: var(--ink); font-weight: 700; }

.history-item-actions { display: flex; gap: var(--space-2); align-items: center; }
.history-item-actions .tiny-btn { margin-top: 0; }

/* ---- Animations ---- */
/* fill-mode backwards, NOT both: `both` keeps the final transform applied
   forever, silently turning every .fade-in section into a containing block
   for position:fixed descendants (broke the result overflow bottom sheet). */
.fade-in { animation: fadeInUp 0.4s ease backwards; }

/* Reduced motion: skip the entry fade entirely. Besides honoring the OS
   preference, this is what makes the axe contrast scan deterministic — the
   fade interpolates opacity from 0, so any scanner (or user) catching a
   mid-animation frame sees every text node at a transiently failing
   contrast (A11Y-001 flaked exactly this way). */
@media (prefers-reduced-motion: reduce) {
  .fade-in { animation: none; }
}

@keyframes fadeInUp {
  from { transform: translateY(10px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/* ---- Consent Banner (keeping existing) ---- */
.consent-root { pointer-events: auto; position: fixed; inset: auto 0 0 0; z-index: 2500; }

.consent-banner {
  pointer-events: auto;
  width: min(800px, calc(100vw - 24px));
  margin: 0 auto var(--space-3);
  background: rgba(255,255,255,0.98);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
  display: grid;
  gap: var(--space-3);
}

.consent-banner[hidden], .consent-backdrop[hidden] { display: none !important; }

.consent-banner-copy h2 { margin: var(--space-1) 0 6px; font-size: var(--text-xl); }
.consent-banner-copy p { margin: 0; color: var(--ink-soft); line-height: 1.4; }

.consent-kicker {
  margin: 0;
  color: var(--accent);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.consent-banner-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.consent-btn {
  margin-top: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 9px 14px;
  background: var(--accent-cta);
  color: #fff;
  font-weight: 700;
  cursor: pointer;
}

.consent-btn-ghost {
  background: var(--surface);
  border-color: var(--border);
  color: var(--ink);
}

.consent-fab {
  pointer-events: auto;
  position: fixed;
  right: 14px;
  bottom: 14px;
  margin-top: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: var(--space-2) 13px;
  background: var(--surface);
  color: var(--ink-soft);
  font-size: var(--text-xs);
  font-weight: 700;
  box-shadow: var(--shadow);
  cursor: pointer;
}

.consent-backdrop {
  pointer-events: auto;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  display: grid;
  place-items: center;
  padding: var(--space-4);
}

.consent-panel {
  pointer-events: auto;
  width: min(760px, 100%);
  max-height: min(90vh, 860px);
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
  display: grid;
  gap: var(--space-3);
}

.consent-panel-header { display: flex; justify-content: space-between; gap: var(--space-2); align-items: flex-start; }
.consent-panel-header h3 { margin: 3px 0 0; }

.consent-close {
  margin-top: 0;
  padding: 6px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--ink-soft);
  cursor: pointer;
}

.consent-panel-text { margin: 0; color: var(--ink-soft); line-height: 1.4; }
.consent-categories { display: grid; gap: var(--space-2); }

.consent-row {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: center;
}

.consent-row strong { display: block; margin-bottom: 2px; }
.consent-row small { color: var(--ink-soft); }
.consent-row-locked { background: var(--surface-soft); }

.consent-scan { border-top: 1px solid var(--border); padding-top: 10px; display: grid; gap: 7px; }
.consent-scan h4, .consent-scan h5 { margin: 0; }
.consent-scan p { margin: 0; color: var(--ink-soft); }
.consent-scan-caption { font-size: var(--text-sm); }

.consent-scan ul {
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--ink-soft);
  font-size: var(--text-md);
  display: grid;
  gap: var(--space-1);
}

.consent-catalog-item {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--space-2) 9px;
  background: var(--surface-soft);
  display: grid;
  gap: var(--space-1);
}

.consent-catalog-top { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.consent-catalog-meta { font-size: var(--text-xs); color: var(--ink-soft); }
.consent-catalog-item p { margin: 0; color: var(--ink-soft); }

.consent-badge {
  border-radius: 999px;
  padding: 2px var(--space-2);
  font-size: var(--text-2xs);
  font-weight: 700;
  line-height: 1.2;
}

.consent-badge-necessary { background: #dcfce7; color: #166534; }
.consent-badge-preferences, .consent-badge-analytics, .consent-badge-marketing { background: var(--accent-light); color: var(--accent-strong); }

.consent-panel-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: flex-end; }

/* ---- Session panel styles (kept for auth pages) ---- */
.session-panel { padding: var(--space-4); display: grid; gap: 10px; }
.session-kicker { margin: 0; color: var(--accent); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; }
.session-note { margin: 0; color: var(--ink-soft); font-size: var(--text-md); line-height: 1.4; }

.runtime-banner {
  display: grid;
  gap: 10px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-soft);
  color: var(--ink-soft);
}

.session-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.session-actions[hidden] { display: none !important; }

.session-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--accent-cta);
  color: #fff;
  font-weight: 600;
  text-decoration: none;
  padding: 7px var(--space-3);
  font-size: var(--text-sm);
}

.session-link:hover { background: var(--accent-cta-hover); }

.session-link-secondary {
  background: var(--surface);
  color: var(--ink);
}

.session-link-secondary:hover { background: var(--surface-soft); }

.runtime-banner strong { color: var(--ink); }
.runtime-banner a { color: var(--accent); font-weight: 600; text-decoration: none; }
.runtime-banner a:hover { text-decoration: underline; }
.runtime-banner .session-link { color: #fff; text-decoration: none; }
.runtime-banner .session-link:hover { text-decoration: none; }
.runtime-banner .session-link-secondary { color: var(--ink); }

.runtime-link-btn {
  margin-top: 0;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-soft);
  font-size: var(--text-sm);
  font-weight: 700;
}

.runtime-link-btn:hover { background: var(--surface-soft); }

/* ---- Responsive ---- */
@media (max-width: 900px) {
  .app-layout { flex-direction: column; }
  /* History is an off-canvas drawer (see "History off-canvas drawer" block);
     it must stay reachable on mobile, where it renders full-width via 90vw. */
  /* Mobile keeps the tighter 16px page gutter (the 40px desktop gutter would
     starve the content column on phones). */
  .shell { width: 94vw; padding: var(--space-6) var(--space-4) 80px; }

  .hero-upload :is(h1, .hero-h1) { font-size: var(--text-3xl); }


  .results-top { flex-direction: column; align-items: flex-start; }

  .validation-toolbar {
    grid-template-columns: 1fr;
    position: sticky;
    bottom: 10px;
    z-index: 12;
    background: rgba(255,255,255,0.96);
    box-shadow: var(--shadow-lg);
  }

  .validation-primary-actions {
    justify-content: flex-start;
    width: 100%;
    display: grid;
    grid-template-columns: 1fr;
  }

  .validation-primary-actions button, .toolbar-actions button { width: 100%; }

  .meta-toggle { width: 100%; }

  .field-card-top { grid-template-columns: 1fr; }
  .field-card-controls { justify-content: flex-start; }
  /* Touch targets: field-card actions must be at least 44px tall on mobile. */
  .field-card-controls .tiny-btn {
    min-height: 44px;
    flex: 1 1 auto;
  }
  .field-group-header { flex-direction: column; }
  .field-group-count { align-self: flex-start; }

  .financial-grid { grid-template-columns: 1fr; }
  .validation-layout { grid-template-columns: 1fr; }

  .history-item { grid-template-columns: 1fr; }
  .history-item-actions { justify-content: flex-start; }

  .twofa-setup-grid { grid-template-columns: 1fr; justify-items: start; }
  .admin-grid { grid-template-columns: 1fr; }

  #pdf-preview { min-height: 420px; }

  .top-bar-actions { gap: var(--space-1); }
  /* P2-2: Ensure nav links meet 44px minimum touch-target height */
  .nav-link { padding: 10px var(--space-2); font-size: var(--text-sm); min-height: 44px; display: inline-flex; align-items: center; }
  .nav-link-btn { min-height: 44px; display: inline-flex; align-items: center; }
  .nav-btn { padding: 10px var(--space-3); font-size: var(--text-sm); min-height: 44px; display: inline-flex; align-items: center; }
  /* Mobile nav: secondary links (History/Workspace/API/Billing) collapse behind
     a hamburger toggle so they remain reachable instead of disappearing. */
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-left: 2px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    color: var(--ink);
    font-size: var(--text-xl);
    line-height: 1;
    cursor: pointer;
  }
  /* Nav dropdown: always positioned and laid-out so it can animate in/out */
  .top-bar-nav {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
    padding: 6px var(--space-2) var(--space-3);
    z-index: 199;
    visibility: hidden;
    opacity: 0;
    transform: translateY(-4px);
    pointer-events: none;
    transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
  }
  .top-bar-nav .nav-link,
  .top-bar-nav .nav-link-btn {
    width: 100%;
    min-height: 44px;
    display: flex;
    align-items: center;
    border-radius: 8px;
  }
  .top-bar-nav .hidden { display: none; }
  .top-bar.nav-open .top-bar-nav {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
    transition: opacity 0.15s ease, transform 0.15s ease;
  }

  .guest-nudge-actions { width: 100%; justify-content: center; flex-direction: column; }

  .line-items-table.read-only { min-width: 520px; }

  .line-items-table.editable { width: 100%; min-width: 0; }
  .table-inline-wrap.editable { max-height: none; overflow: visible; border: 0; background: transparent; }
  .line-items-table.editable thead { display: none; }

  .line-items-table.editable,
  .line-items-table.editable tbody,
  .line-items-table.editable tr,
  .line-items-table.editable td { display: block; width: 100%; }

  .line-items-table.editable tbody { display: grid; gap: 10px; }

  .line-items-table.editable tr {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    padding: 10px;
  }

  .line-items-table.editable td {
    border-bottom: 0;
    padding: 5px 0;
    display: grid;
    grid-template-columns: minmax(88px, 38%) minmax(0, 1fr);
    gap: var(--space-2);
    align-items: center;
  }

  .line-items-table.editable td::before {
    content: attr(data-col-label);
    font-size: var(--text-2xs);
    font-weight: 700;
    color: var(--ink-soft);
    line-height: 1.2;
  }

  .line-items-table.editable td.row-index {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--accent);
    font-weight: 700;
    padding-bottom: var(--space-2);
    margin-bottom: var(--space-1);
    border-bottom: 1px solid var(--border-light);
  }

  .line-items-table.editable td.row-index::before { content: "Row"; }

  .line-items-table.editable td.table-actions-cell { grid-template-columns: 1fr; margin-top: 2px; }
  .line-items-table.editable td.table-actions-cell::before { display: none; }
  .line-items-table.editable td.table-actions-cell .tiny-btn { width: 100%; margin-top: 0; }

  .line-items-table.editable input,
  .line-items-table.editable textarea { font-size: var(--text-md); }

  /* Stacked cards put each cell on its own full-width row, so the
     right-alignment that makes digits stack in a real table would instead
     shove a lone value (and its own generated label) to the far edge.
     Matches td by attribute to outrank the per-column rules above. */
  .line-items-table.editable td[class*="col-"],
  .line-items-table.editable td[class*="col-"] input { text-align: left; }

  .consent-banner { margin-bottom: 68px; }
  .consent-banner-actions, .consent-panel-actions { display: grid; grid-template-columns: 1fr; }
  .consent-fab { left: 50%; right: auto; transform: translateX(-50%); }
}

@media (max-width: 560px) {
  .line-items-table.editable td { grid-template-columns: 1fr; gap: 5px; }
  .line-items-table.editable td.row-index { flex-wrap: wrap; }
  .top-bar { padding: 10px var(--space-4); }
  .top-bar-actions { gap: 2px; }
}

/* ── Toast notifications ────────────────────────────────────────── */
#toast-container {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 420px;
}
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-3) 18px;
  border-radius: 10px;
  font-size: var(--text-md);
  font-weight: 600;
  color: #fff;
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
  animation: toast-in 0.3s ease-out;
  cursor: pointer;
  transition: opacity 0.3s;
}
.toast.toast-success { background: #16a34a; }
.toast.toast-error   { background: #dc2626; }
.toast.toast-info    { background: var(--accent-cta, #5b45e0); }
.toast.toast-warn    { background: #d97706; }
.toast.toast-fade-out { opacity: 0; }
/* Optional action link inside a toast (e.g. "Upgrade plan" on quota errors).
   Inherits the toast's white text on all four background colors. */
.toast .toast-action {
  color: inherit;
  text-decoration: underline;
  font-weight: 600;
  margin-left: 8px;
  white-space: nowrap;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateX(30px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ── Processing badge ───────────────────────────────────────────── */
.processing-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border-radius: 8px;
  background: var(--accent-light);
  color: var(--accent-text);
  font-size: var(--text-sm);
  font-weight: 700;
  margin-bottom: var(--space-3);
}
.processing-badge .spinner-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent-text);
  animation: pulse-dot 1s ease-in-out infinite;
}
@keyframes pulse-dot {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50% { opacity: 1; transform: scale(1.2); }
}

/* --- Interactive document preview (inline pages + bounding-box overlays) --- */
.results-columns {
  display: grid;
  gap: 18px;
  align-items: start;
}
@media (min-width: 1100px) {
  .results-columns {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .results-columns .viewer-card {
    position: sticky;
    top: 16px;
  }
}
.doc-preview {
  display: grid;
  gap: var(--space-4);
  max-height: 860px;
  overflow-y: auto;
}
.preview-page {
  position: relative;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface-soft);
}
.preview-page-media {
  display: block;
  width: 100%;
  height: auto;
}
.preview-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  isolation: isolate;
}
/* Hover/focus spotlight: mute the surrounding page while leaving the source
   region (and any click-pinned region) crisp above the veil. Keeping the veil
   inside the page avoids dimming the review controls or moving the preview. */
.preview-overlay.field-spotlight-active::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: rgba(31, 41, 55, 0.24);
}
/* Faint outline for every recognized OCR token (full coverage). Non-interactive
   and painted beneath the matched-field boxes so it never blocks their clicks. */
.preview-ocr-token {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  border: 1px solid rgba(120, 130, 145, 0.45);
  background: rgba(120, 130, 145, 0.06);
  border-radius: 2px;
}
.preview-bbox {
  position: absolute;
  pointer-events: auto;
  z-index: 1;
  border: 2px solid rgba(150, 150, 150, 0.7);
  background: rgba(150, 150, 150, 0.08);
  border-radius: 3px;
  padding: 0;
  cursor: pointer;
  min-width: 4px;
  min-height: 4px;
}
.preview-bbox.bbox-conf-unknown {
  border-color: rgba(217, 92, 47, 0.85);
  background: rgba(217, 92, 47, 0.12);
}
.preview-bbox.bbox-conf-unknown:hover { background: rgba(217, 92, 47, 0.28); }
.preview-bbox.bbox-conf-high {
  border-color: rgba(34, 197, 94, 0.9);
  background: rgba(34, 197, 94, 0.12);
}
.preview-bbox.bbox-conf-high:hover { background: rgba(34, 197, 94, 0.28); }
.preview-bbox.bbox-conf-mid {
  border-color: rgba(234, 179, 8, 0.9);
  background: rgba(234, 179, 8, 0.12);
}
.preview-bbox.bbox-conf-mid:hover { background: rgba(234, 179, 8, 0.28); }
.preview-bbox.bbox-conf-low {
  border-color: rgba(239, 68, 68, 0.9);
  background: rgba(239, 68, 68, 0.12);
}
.preview-bbox.bbox-conf-low:hover { background: rgba(239, 68, 68, 0.28); }
/* Line-item rows: same confidence colours as field boxes, dashed to signal
   "one row of a table" (matches the annotated-PDF export style). */
.preview-bbox.bbox-line-item {
  border-style: dashed;
}
.preview-bbox.active {
  z-index: 3;
  border-width: 3px;
  border-color: #0b57d0 !important;
  background: rgba(20, 99, 255, 0.30) !important;
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.95),
    0 0 0 6px rgba(11, 87, 208, 0.95),
    0 6px 18px rgba(15, 23, 42, 0.35);
}
/* Field-card hover cross-highlight (see setPreviewHoverHighlight in app.js).
   Lighter than .active so a click-pinned box stays distinct while hovering
   elsewhere; .active's !important wins when a box carries both. Placed after the
   confidence-colour rules so it overrides their border/background at equal
   specificity. */
.preview-bbox.hover-active {
  z-index: 3;
  border-width: 3px;
  border-color: #1463ff;
  background: rgba(20, 99, 255, 0.24);
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.95),
    0 0 0 5px rgba(20, 99, 255, 0.80),
    0 6px 18px rgba(15, 23, 42, 0.35);
}
.bbox-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) 10px;
  margin-top: 6px;
  background: var(--surface-soft);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: var(--text-2xs);
  color: var(--text-muted);
}
/* The author display:flex above sits LATER in this file than the generic
   `.hidden` utility and matches at the same specificity, so it silently won —
   the legend stayed painted every time JS tried to hide it (7th occurrence of
   this trap). Any new display: rule on a JS-toggled block needs its guard in
   the same commit. */
.bbox-legend[hidden],
.bbox-legend.hidden { display: none; }

.bbox-legend-item {
  display: flex;
  align-items: center;
  gap: 5px;
}
.bbox-legend-item::before {
  content: "";
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 2px;
  border: 2px solid;
  flex-shrink: 0;
}
.bbox-legend-item.bbox-conf-high::before  { border-color: rgba(34,197,94,0.9);  background: rgba(34,197,94,0.15); }
.bbox-legend-item.bbox-conf-mid::before   { border-color: rgba(234,179,8,0.9);  background: rgba(234,179,8,0.15); }
.bbox-legend-item.bbox-conf-low::before   { border-color: rgba(239,68,68,0.9);  background: rgba(239,68,68,0.15); }
.bbox-legend-item.bbox-conf-unknown::before { border-color: rgba(217,92,47,0.85); background: rgba(217,92,47,0.12); }
.bbox-legend-item.bbox-line-item::before { border-style: dashed; border-color: rgba(34,197,94,0.9); background: rgba(34,197,94,0.15); }
.bbox-legend-item.bbox-ocr-token::before { border-color: rgba(120,130,145,0.45); background: rgba(120,130,145,0.12); }
.field-card.source-flash,
.line-items-standalone.source-flash {
  outline: 2px solid #1463ff;
  outline-offset: 2px;
}

/* Flash for the specific line-item row a clicked preview box points at. */
.line-items-table tr.row-flash > td {
  background: rgba(20, 99, 255, 0.16);
  transition: background 0.4s ease;
}
.line-items-table tbody tr[data-row-index] {
  cursor: pointer;
}

/* --- Mobile overflow guards ------------------------------------------------ */
/* Grid/flex children default to min-width:auto, which lets wide content
   (line-items tables, long values) stretch ancestors past the viewport
   instead of scrolling inside their own wrapper. */
.shell > *,
.results-columns > *,
.panel > *,
.editor-card,
.viewer-card,
.line-items-panel,
.line-items-editor > *,
.line-items-standalone,
.table-field-wrap {
  min-width: 0;
}
.results img,
.results canvas,
.results svg {
  max-width: 100%;
}
@media (max-width: 900px) {
  /* Wide-table minimums otherwise force horizontal page scroll on phones;
     the .table-inline-wrap wrapper scrolls internally instead. */
  .line-items-table.read-only,
  .line-items-standalone .line-items-table.read-only {
    min-width: 520px;
  }
}
@media (max-width: 560px) {
  /* Read-only line items → stacked cards (no horizontal scroll). The plain
     table hides the key financial columns (unit price, line total) behind an
     internal scrollbar on phones; stacking mirrors the editable mobile layout
     so every column stays visible. */
  .line-items-table.read-only,
  .line-items-standalone .line-items-table.read-only {
    min-width: 0;
    width: 100%;
    font-size: var(--text-sm);
  }
  .table-inline-wrap.read-only {
    max-height: none;
    overflow: visible;
    border: 0;
    border-radius: 0;
    background: transparent;
  }
  .line-items-table.read-only thead { display: none; }
  .line-items-table.read-only,
  .line-items-table.read-only tbody,
  .line-items-table.read-only tr,
  .line-items-table.read-only td { display: block; width: 100%; }
  .line-items-table.read-only tbody { display: grid; gap: 10px; }
  .line-items-table.read-only tr {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    padding: 10px var(--space-3);
  }
  .line-items-table.read-only td {
    background: transparent;
    border-bottom: 0;
    padding: var(--space-1) 0;
    display: grid;
    grid-template-columns: minmax(96px, 42%) minmax(0, 1fr);
    gap: 10px;
    align-items: start;
    text-align: left;
    white-space: normal;
  }
  /* Neutralise the desktop fixed column widths (which have higher specificity
     and would otherwise squeeze the value column to a few px on phones). */
  .line-items-table.read-only td[class*="col-"] {
    width: 100%;
    min-width: 0;
    max-width: none;
  }
  .line-items-table.read-only td::before {
    content: attr(data-col-label);
    font-size: var(--text-2xs);
    font-weight: 700;
    color: var(--ink-soft);
    line-height: 1.3;
  }
  .line-items-table.read-only td .table-cell-read { text-align: left; }
  .line-items-table.read-only td.row-index {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--accent);
    font-weight: 700;
    padding-bottom: var(--space-2);
    margin-bottom: var(--space-1);
    border-bottom: 1px solid var(--border-light);
  }
  .line-items-table.read-only td.row-index::before { content: "Item"; }
  /* Flatten nested boxes so the table wrap gets usable width on phones;
     each nesting level otherwise eats ~30px of padding + border. */
  .line-items-panel .card-panel-body { padding: 10px; }
  .line-items-standalone { padding: 0; border: 0; background: transparent; }
  .validation-toolbar { grid-template-columns: 1fr; }
  .financial-grid { grid-template-columns: 1fr; }
  /* Belt-and-suspenders: any table container scrolls internally rather than
     widening the page (prevents whole-page horizontal scroll on results). */
  .line-items-editor,
  .table-field-wrap,
  .line-items-standalone { max-width: 100%; overflow-x: auto; }
}

/* --- Legal pages and shared footer ----------------------------------------- */
.legal-page {
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
  padding: var(--space-12) var(--space-6) 80px;
  line-height: 1.65;
}
.legal-page h1 { margin-bottom: var(--space-1); }

/* Long translated words, route identifiers and email addresses must not
   establish a page-wide minimum width. Code blocks keep their own scroller. */
.legal-page,
.docs-content,
.guide-content {
  min-width: 0;
  overflow-wrap: anywhere;
}
.legal-page h2 { margin-top: 28px; font-size: var(--text-xl); }
.legal-page h3 { margin-top: 20px; font-size: var(--text-lg); }
.legal-page ul { padding-left: 22px; }
.legal-page li { margin: 6px 0; }
.legal-updated { color: var(--muted, #667085); font-size: var(--text-md); }
/* Consolidated legal page: table of contents + per-section anchoring. */
.legal-page > section { scroll-margin-top: 80px; }
.legal-page > section + section {
  margin-top: var(--space-10);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
}
.legal-toc {
  margin: var(--space-6) 0 var(--space-4);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-soft);
}
.legal-toc-title {
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted, #667085);
  margin-bottom: var(--space-2);
}
.legal-toc ol {
  margin: 0;
  padding-left: 20px;
  display: grid;
  gap: 4px;
}
.legal-toc a { color: var(--accent, inherit); text-decoration: none; }
.legal-toc a:hover { text-decoration: underline; }
.legal-review-banner {
  border: 1px solid #f0c36d;
  background: #fdf6e3;
  color: #7a5b16;
  border-radius: 8px;
  padding: 10px 14px;
  font-size: var(--text-md);
}
.site-footer {
  border-top: 1px solid var(--border);
  /* auto pushes the footer to the bottom of the flex-column body on short
     pages; on tall pages it collapses to ~the old 40px breathing room. */
  margin-top: auto;
  padding: var(--space-10) var(--space-6) 18px;
  font-size: var(--text-md);
  color: var(--muted, #667085);
  background: var(--surface);
}
/* The floating "Guide" (bottom-left) and "Privacy settings" (bottom-right)
   buttons sit at the viewport bottom. Reserve space at the end of the page on
   small screens so footer links and the last CTA never rest underneath them.
   Declared after the base rule so its shorthand padding doesn't reset this. */
@media (max-width: 700px) {
  .site-footer { padding-bottom: 80px; }
}
.site-footer-inner {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-6);
  justify-content: space-between;
  align-items: center;
}
.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
}
.site-footer-links a {
  color: inherit;
  text-decoration: none;
}
.site-footer-links a:hover { text-decoration: underline; }
/* The cookie-settings control is a button (it opens a dialog, navigates
   nowhere) dressed as its link neighbours. */
.site-footer-links button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.site-footer-links button:hover { text-decoration: underline; }

/* EU processing mode — admin panel notice */
.eu-mode-admin-notice {
  padding: 10px 14px;
  border-radius: 8px;
  font-size: var(--text-sm);
  line-height: 1.5;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  color: #1e3a8a;
  margin-bottom: 16px;
}
[data-theme="dark"] .eu-mode-admin-notice {
  background: rgba(29, 78, 216, 0.1);
  border-color: rgba(29, 78, 216, 0.3);
  color: #93c5fd;
}

/* AI sparkle — marks controls that invoke an LLM. Opt-in utility rather than a
   change to .primary-btn/.secondary-btn/.tiny-btn, none of which set flex or
   gap (other icon+label buttons here rely on default inline whitespace, which
   a flex container would collapse). Those classes do now declare
   `display: inline-block`, but this rule follows them at equal specificity, so
   an opted-in control still becomes inline-flex. */
.has-ai-icon {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* Block-level carriers keep their block behaviour — inline-flex would shrink a
   <p> to its content width and quietly change the surrounding layout. */
p.has-ai-icon { display: flex; }
.ai-sparkle { flex: 0 0 auto; }
/* .primary-btn.is-loading::before puts a spinner in this same leading slot, so
   the sparkle stands down rather than showing two glyphs. Precautionary: today
   only #submit-btn takes is-loading, and it is deliberately unmarked. */
.primary-btn.is-loading .ai-sparkle,
.primary-btn.is-saving .ai-sparkle { display: none; }

/* AI-interaction disclosure badge (EU AI Act Art. 50(1)) — shown to every
   sidebar visitor, unlike the EU-residency badge below which is per-user. */
.appnav-ai-badge-row {
  /* 16px above separates the badge from the Appearance row (it is a disclosure,
     not a third preference); 11px inset aligns it with the nav/pref rows above,
     which the old 16px did not. Bottom clearance is owned by .appnav-scroll's
     padding so the badge and the account divider never merge. */
  padding: var(--space-4) 11px 0;
  /* The row is the width budget for the badge inside it. */
  display: flex;
  min-width: 0;
}
/* Informational badge, not a button. It IS a link (to the AI-transparency
   section), but a 1px border plus an accent border on hover made it read as a
   sidebar action next to real ones. A tinted chip with no outline reads as a
   label; the underline on hover/focus is what discloses that it is clickable. */
.ai-processing-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  /* Never wider than the rail. The label itself is short in every locale (see
     the "AI extraction" msgid — the long disclosure moved to the link's title=),
     but the cap plus the truncating inner span means no future translation can
     push a horizontal scrollbar into the sidebar. */
  max-width: 100%;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--surface-soft);
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 3px 9px;
  text-decoration: none;
}
/* nowrap lives on the inner span, not the badge: on the badge it would refuse
   to shrink and overflow the pill instead of ellipsing inside it. */
.ai-processing-badge > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ai-processing-badge:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.ai-processing-badge:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* EU processing mode badge — shown in sidebar when EU_PROCESSING_MODE=true */
.appnav-eu-badge-row {
  /* 11px inset matches the AI badge above and the nav rows; the old 16px was
     out of step and, with the badge's nowrap, overflowed the rail in fr
     ("Traitement des données dans l'UE" needs ~204px inside a 216px row).
     The old `margin-top` is gone on purpose: this chip sits directly under the
     AI badge, which already owns 16px of separation from Preferences, so the two
     related disclosures now read as a pair 8px apart instead of 16px. Bottom
     clearance belongs to .appnav-scroll's padding, one owner per gap. */
  padding: var(--space-2) 11px 0;
  display: flex;
  min-width: 0;
}
/* Same shrink contract as the AI badge — in the rail the chip is capped and
   ellipses; the (sidebar-less) footer copy keeps its intrinsic width. */
.appnav-eu-badge-row .eu-processing-badge {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.eu-processing-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--text-xs);
  font-weight: 600;
  color: #1d4ed8;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  border-radius: 4px;
  padding: 3px 8px;
  white-space: nowrap;
}
[data-theme="dark"] .eu-processing-badge {
  color: #93c5fd;
  background: rgba(29, 78, 216, 0.12);
  border-color: rgba(29, 78, 216, 0.3);
}

/* ---- Benchmarks page ------------------------------------------------------ */
/* body { display: flex; flex-direction: column } is the sticky-footer pattern.
   flex items with `margin: 0 auto` (cross-axis) opt out of align-items:stretch
   and size to intrinsic content width. On narrow viewports the tables drive
   the intrinsic width wider than the viewport, causing body overflow.
   Setting width:100% gives this flex item a definite cross-axis size (=
   viewport width) so the margin:auto centering works correctly AND max-width
   from .legal-page still caps it at 760px on wider screens. */
.benchmarks-page { width: 100%; }
.benchmarks-page .benchmarks-meta {
  color: var(--ink-soft);
  font-size: var(--text-md);
  margin: 0 0 var(--space-4);
  line-height: 1.6;
  max-width: 720px;
}
.benchmarks-table td { vertical-align: middle; }
/* Status-TEXT tokens (AA 4.5:1 as small text): the fill tokens --success
   (#059669, 3.35:1 on white) / --danger fail contrast as small table text. */
.bench-improved { color: var(--success-text); font-weight: 600; }
.bench-regressed { color: var(--danger-text); }
.bench-delta {
  font-size: var(--text-xs);
  font-weight: 400;
  color: var(--ink-soft);
  margin-left: 4px;
}
.benchmarks-note {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: var(--space-3) 0 0;
  max-width: 720px;
  line-height: 1.55;
}

/* ---- Retention table (data-retention page) --------------------------------- */
.retention-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-base);
  margin: var(--space-4) 0;
}
.retention-table th {
  background: var(--surface-soft);
  padding: 10px 14px;
  text-align: left;
  font-weight: 600;
  border-bottom: 1px solid var(--border);
  /* Prevent "Pages charged" from wrapping and compressing at narrow widths.
     The overflow-x:auto wrapper on the table in profile.html will let the
     table scroll horizontally rather than squeeze each column. */
  white-space: nowrap;
}
.retention-table td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-light);
  vertical-align: top;
}
.retention-table tr:last-child td { border-bottom: none; }

/* Legal tables have no horizontal-scroll wrapper; their headings must wrap.
   Usage tables inside the profile retain their existing scroll behavior. */
.legal-page .retention-table th { white-space: normal; }

/* ---- Danger button ---- */
.danger-btn {
  padding: 6px var(--space-3);
  background: none;
  border: 1px solid var(--danger);
  color: var(--danger);
  border-radius: 7px;
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: 500;
  transition: background 0.15s, color 0.15s;
}
.danger-btn:hover { background: var(--danger); color: #fff; }

/* --- History shortcut (mobile/tablet, sidebar hidden) ----------------------- */
/* Hidden by default — only shown when sidebar is not visible */
.history-mobile-shortcut { display: none; }
@media (max-width: 900px) {
  .history-mobile-shortcut {
    display: flex;
    justify-content: flex-end;
  }
  .history-mobile-link {
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--accent);
    text-decoration: none;
    padding: var(--space-2) var(--space-1);
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .history-mobile-link:hover { text-decoration: underline; }
}

/* --- Sample document chips --------------------------------------------------- */
.sample-docs {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/* The author display:flex above would otherwise defeat the [hidden] attribute
   (samples are collapsed behind the "Try a sample document" button). */
.sample-docs[hidden] { display: none; }
.sample-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: var(--space-2);
}
.sample-chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--accent);
  font-weight: 500;
  font-size: var(--text-sm);
  border-radius: 999px;
  padding: var(--space-1) var(--space-3);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.sample-chip:hover { background: var(--accent-light); border-color: var(--accent); }
.sample-chip:disabled { opacity: 0.6; cursor: wait; }

/* ============================================================
   Dark mode overrides — elements with hardcoded light colors
   ============================================================ */

/* Theme toggle emoji size — box/hover/shadow come from .lang-fab-btn. */
.theme-toggle { font-size: var(--text-lg); line-height: 1; }

/* Field card confirmed state */
[data-theme="dark"] .field-card.is-confirmed,
[data-theme="dark"] .line-items-standalone.is-confirmed {
  background: rgba(34, 197, 94, 0.05);
  border-left-color: #22c55e;
}

/* Inline confidence badges have no dark override: the base rules are fully
   tokenized since the 2026-08 rework, so a copy here would restate the same
   two declarations and silently go stale the next time the base rule moves. */

/* Confirm button confirmed state in dark mode */
[data-theme="dark"] .field-confirm-btn.is-confirmed {
  background: rgba(34, 197, 94, 0.1);
  border-color: rgba(34, 197, 94, 0.4);
  color: #4ade80;
}

/* Hardcoded white modal card */
[data-theme="dark"] .modal-card {
  background: var(--surface);
}

/* Warning banners — convert hardcoded amber to dark-friendly */
[data-theme="dark"] .warnings {
  background: rgba(245, 158, 11, 0.1);
  border-left-color: var(--accent-warm);
}

/* Validation toolbar mobile sticky — hardcoded white background */
[data-theme="dark"] .validation-toolbar {
  background: rgba(26, 29, 39, 0.96);
}

/* Processing badge */
[data-theme="dark"] .processing-badge {
  background: var(--accent-light);
  color: var(--accent-strong);
}
[data-theme="dark"] .processing-badge .spinner-dot {
  background: var(--accent-strong);
}

/* Step summary check circle (hardcoded green bg) */
[data-theme="dark"] .step-summary-check {
  background: rgba(34, 197, 94, 0.15);
}

/* Hardcoded badge colors for sidebar items */
[data-theme="dark"] .sb-confirmed,
[data-theme="dark"] .sb-ok { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
[data-theme="dark"] .sb-draft { background: var(--accent-light); color: var(--accent-strong); }
[data-theme="dark"] .sb-incomplete { background: rgba(239, 68, 68, 0.15); color: #f87171; }
[data-theme="dark"] .sb-review { background: rgba(245, 158, 11, 0.15); color: #fbbf24; }

/* Status badges */
[data-theme="dark"] .status-badge.status-ok { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
[data-theme="dark"] .status-badge.status-medium { background: rgba(245, 158, 11, 0.15); color: #fbbf24; }
[data-theme="dark"] .status-badge.status-high { background: rgba(239, 68, 68, 0.15); color: #f87171; }
[data-theme="dark"] .status-badge.status-neutral { background: var(--surface-soft); color: var(--ink-soft); }

/* Hardcoded flash message backgrounds */
[data-theme="dark"] .flash-message {
  background: rgba(34, 197, 94, 0.1);
  border-color: rgba(34, 197, 94, 0.25);
}
[data-theme="dark"] .flash-error {
  background: rgba(239, 68, 68, 0.1);
  border-color: rgba(239, 68, 68, 0.25);
}

/* Legal review banner */
[data-theme="dark"] .legal-review-banner {
  background: rgba(245, 158, 11, 0.1);
  border-color: rgba(245, 158, 11, 0.3);
  color: #fbbf24;
}

/* Cookie consent banner — override hardcoded white bg in dark mode */
[data-theme="dark"] .consent-banner {
  background: var(--surface);
}

/* Ghost button */
[data-theme="dark"] .ghost-btn {
  border-color: var(--accent-light);
  color: var(--accent-strong);
}
[data-theme="dark"] .ghost-btn:hover { background: var(--accent-light); }

/* Table rows */
[data-theme="dark"] .data-table th { background: var(--surface-soft); }

/* Preview unavailable placeholder */
.preview-page-unavailable {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
  color: var(--ink-soft);
  font-size: var(--text-md);
  background: var(--surface-soft);
}

/* Hardcoded color overrides for @media dark (system pref without explicit attr) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .modal-card { background: var(--surface); }
  :root:not([data-theme="light"]) .warnings { background: rgba(245,158,11,0.1); border-left-color: var(--accent-warm); }
  :root:not([data-theme="light"]) .validation-toolbar { background: rgba(26,29,39,0.96); }
  :root:not([data-theme="light"]) .processing-badge { background: var(--accent-light); color: var(--accent-strong); }
  :root:not([data-theme="light"]) .processing-badge .spinner-dot { background: var(--accent-strong); }
  :root:not([data-theme="light"]) .step-summary-check { background: rgba(34,197,94,0.15); }
  :root:not([data-theme="light"]) .sb-confirmed,
  :root:not([data-theme="light"]) .sb-ok { background: rgba(34,197,94,0.15); color: #4ade80; }
  :root:not([data-theme="light"]) .sb-draft { background: var(--accent-light); color: var(--accent-strong); }
  :root:not([data-theme="light"]) .sb-incomplete { background: rgba(239,68,68,0.15); color: #f87171; }
  :root:not([data-theme="light"]) .sb-review { background: rgba(245,158,11,0.15); color: #fbbf24; }
  :root:not([data-theme="light"]) .status-badge.status-ok { background: rgba(34,197,94,0.15); color: #4ade80; }
  :root:not([data-theme="light"]) .status-badge.status-medium { background: rgba(245,158,11,0.15); color: #fbbf24; }
  :root:not([data-theme="light"]) .status-badge.status-high { background: rgba(239,68,68,0.15); color: #f87171; }
  :root:not([data-theme="light"]) .flash-message { background: rgba(34,197,94,0.1); border-color: rgba(34,197,94,0.25); }
  :root:not([data-theme="light"]) .flash-error { background: rgba(239,68,68,0.1); border-color: rgba(239,68,68,0.25); }
  :root:not([data-theme="light"]) .legal-review-banner { background: rgba(245,158,11,0.1); border-color: rgba(245,158,11,0.3); color: #fbbf24; }
  :root:not([data-theme="light"]) .ghost-btn { border-color: var(--accent-light); color: var(--accent-strong); }
  :root:not([data-theme="light"]) .ghost-btn:hover { background: var(--accent-light); }
  :root:not([data-theme="light"]) .data-table th { background: var(--surface-soft); }
  :root:not([data-theme="light"]) .consent-banner { background: var(--surface); }
  :root:not([data-theme="light"]) .extraction-chip-demo { background: rgba(99,102,241,0.2); color: #a5b4fc; }
  :root:not([data-theme="light"]) .extraction-chip-ai { background: rgba(34,197,94,0.15); color: #4ade80; }
  :root:not([data-theme="light"]) .extraction-chip-fallback { background: rgba(245,158,11,0.15); color: #fbbf24; }
  :root:not([data-theme="light"]) .extraction-chip-exact { background: rgba(6,182,212,0.15); color: #67e8f9; }
  :root:not([data-theme="light"]) .field-card.is-confirmed,
  :root:not([data-theme="light"]) .line-items-standalone.is-confirmed { background: rgba(34,197,94,0.05); border-left-color: #22c55e; }
  /* .field-confidence-* need no mirror here — tokenized in the base rules. */
  :root:not([data-theme="light"]) .field-confirm-btn.is-confirmed { background: rgba(34,197,94,0.1); border-color: rgba(34,197,94,0.4); color: #4ade80; }
}

/* ============================================================
   Review Cockpit — sticky summary bar
   ============================================================ */
.review-cockpit {
  /* Static and opaque: the old sticky translucency needed backdrop-filter,
     which turns this into a containing block for position:fixed descendants
     and breaks the overflow menu's mobile bottom sheet. */
  position: static;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  margin: 0 0 var(--space-4);
  padding: var(--space-3) 0 var(--space-4);
  /* The negative margins pull this opaque header out to the panel's padding-box
     edges, which are rounded (panel radius − 1px border). Match that radius on
     the top corners so the header's fill follows the panel's rounded corners
     instead of overpainting them square. */
  border-top-left-radius: calc(var(--radius-lg) - 1px);
  border-top-right-radius: calc(var(--radius-lg) - 1px);
}

/* Two-column header: left identity+meta column, right actions pinned */
.cockpit-main {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

.cockpit-left {
  flex: 1 1 auto;
  min-width: 0;
}

.cockpit-doctype {
  /* Serif retired per the 2026-07-12 typography decision (decision-log.md). */
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0;
  overflow-wrap: break-word;
}

/* Flat inline badge row: confidence · fields confirmed · review state */
.cockpit-badges {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin-top: 5px;
}

/* Confidence badge: shrink from its default 1.1rem to match the badge row */
.cockpit-badges .confidence {
  font-size: var(--text-sm);
  font-weight: 700;
  line-height: 1.3;
}

.cockpit-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  align-self: flex-start;
}

/* ---- Meta line (type · date · total) ---- */
.cockpit-meta-line {
  margin: 3px 0 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  line-height: 1.4;
}
#doc-meta-line[hidden] { display: none; }

/* ---- Review summary rows (replace the former single status line) ----
   One dense row per kind of fact, rendered by renderReviewSummaryRows():
   ✕ blocked (with the concrete reason) / ⚠ needs review / ✓ verified.
   Tone is carried by text AND colour — the glyph is part of the text. */
.review-status-line {
  margin: 5px 0 0;
  font-size: 0.8125rem; /* 13px */
  font-weight: 600;
  line-height: 1.4;
}
.review-status-line[hidden] { display: none; }

.review-summary-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.review-summary-row { margin: 0; }
.review-summary-row.is-warn { color: var(--warn-text); }
.review-summary-row.is-ok { color: var(--success-text); }
/* Nothing confirmed yet: state the fact without the success colour a tick and
   green would imply (see renderReviewSummaryRows). */
.review-summary-row.is-neutral { color: var(--text-muted, #6b7280); }
/* Inline resolution for a blocker the reviewer can settle in one click. */
.review-summary-action {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.review-summary-action:disabled { opacity: 0.6; cursor: default; }
/* The classification blocker's inline type picker: the reviewer settles the
   type here, so correcting it is as reachable as confirming it. Neutral
   surface colours — the row itself is red, and a red control would read as a
   destructive action rather than a choice. */
.review-summary-select {
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1px 4px;
  max-width: 16rem;
}
/* The picker and its button read as one affordance, so they wrap together. */
.review-summary-resolution {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  vertical-align: baseline;
}
.review-summary-row.is-bad { color: var(--danger, #dc2626); }
/* The ✓ row reads as quiet context once a blocker is present: the blocker owns
   the reviewer's attention. */
.review-status-line.has-blocked .review-summary-row.is-ok { font-weight: 500; }

/* ---- Primary workflow-status badge (one per document, .cockpit-badges) ---- */
.wf-badge {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: var(--space-1) 10px;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  border: 1px solid transparent;
}
/* Defensive guards — author display rules must never resurrect a hidden badge
   (see the hide-mechanism note at the top of this file). */
#workflow-status-badge[hidden] { display: none; }
.wf-badge.is-idle {
  color: var(--ink-soft);
  background: var(--surface-soft);
  border-color: var(--border);
}
.wf-badge.is-active {
  color: var(--accent);
  background: var(--accent-light); /* theme-tracking tint */
  border-color: rgba(79, 70, 229, 0.25);
}
.wf-badge.is-done {
  color: var(--success-text);
  background: rgba(5, 150, 105, 0.1);
  border-color: rgba(5, 150, 105, 0.35);
}
.wf-badge.is-warn {
  color: var(--warn-text);
  background: rgba(217, 119, 6, 0.1);
  border-color: rgba(217, 119, 6, 0.35);
}
.wf-badge.is-bad {
  color: var(--danger-text);
  background: rgba(220, 38, 38, 0.08);
  border-color: rgba(220, 38, 38, 0.3);
}
/* Dark tints follow the dark token hues (same convention as .extraction-chip-*). */
[data-theme="dark"] .wf-badge.is-done { background: rgba(34, 197, 94, 0.15); }
[data-theme="dark"] .wf-badge.is-warn { background: rgba(245, 158, 11, 0.15); }
[data-theme="dark"] .wf-badge.is-bad { background: rgba(239, 68, 68, 0.15); }

/* ---- Export button (opens the shared Export dialog) ---- */
.export-menu-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 14px;
}

/* ---- Result overflow menu (⋯) ---- */
.icon-btn {
  width: 44px;
  height: 44px;
  margin-top: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  padding: 0;
}
.icon-btn:hover { color: var(--ink); background: var(--surface-soft); }

.result-overflow { position: relative; }
.result-overflow-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 60;
  min-width: 264px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.result-overflow-menu[hidden] { display: none; }

.overflow-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  margin-top: 0;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}
.overflow-row:hover { background: var(--surface-soft); }
.overflow-row svg { flex: 0 0 auto; color: var(--ink-soft); }
.overflow-row.hidden { display: none; }
.overflow-divider { height: 1px; background: var(--border-light); margin: 4px var(--space-2); }
/* Evidence toggle row: checkbox sits at the far right. */
.overflow-row.meta-toggle input { margin-left: auto; width: 16px; height: 16px; }

/* Mobile: the menu becomes a bottom sheet. */
@media (max-width: 900px) {
  .result-overflow { position: static; }
  .result-overflow-menu {
    position: fixed;
    top: auto;
    left: var(--space-2);
    right: var(--space-2);
    bottom: calc(var(--space-2) + env(safe-area-inset-bottom));
    min-width: 0;
    padding: var(--space-2);
  }
  .overflow-row { min-height: 48px; }
}

/* ---- State-aware cockpit actions (updateCockpitActions in app.js) ----
   review         → "Review fields" (secondary) + "Complete review" (primary)
                    clicking Complete while blockers exist shows a popover;
   reviewed       → "Complete review" (primary); "Analyze another" in ⋯ menu
   is-review-complete → "Reopen review" (secondary, fields locked)
   no-fields      → Export promoted to the primary slot */
/* #post-save-analyze-another used to be revealed only once review was complete,
   because the always-available "Upload another" sat beside it. That twin is
   gone, so this is now the only way out of a document from the ⋯ menu and must
   be offered in every state — starting another analysis mid-review is a normal
   thing to want, and the unsaved-changes dialog is what protects the work. */
.post-save-analyze-another { display: flex; }

/* Default: hide both; each state reveals what it needs. */
#review-fields-btn,
#confirm-all-menu-btn { display: none; }
/* Class-scoped (not #results #…) so the .state-* rules below can win. */
.results #confirm-all-btn { display: none; }

/* review state: "Review N fields" secondary + "Complete review" primary.
   Both are shown so the reviewer can navigate to flagged fields OR force-attempt
   completion (which shows the blockers popover when fields remain). */
.results.state-review #review-fields-btn { display: inline-flex; align-items: center; justify-content: center; }
.results.state-review:not(.is-shared-readonly) #confirm-all-btn { display: inline-flex; align-items: center; justify-content: center; }
/* NB: no #confirm-all-menu-btn rule here any more. The ⋯ row used to be shown
   alongside the bar button in this state; it is now a fallback that appears only
   when the bar button is demoted — see the .demote-reopen block below, which owns
   that decision outright. */

/* reviewed state: just "Complete review" primary. */
.results.state-reviewed:not(.is-shared-readonly) #confirm-all-btn { display: inline-flex; align-items: center; justify-content: center; }

/* is-ready (review state, zero flagged): hide the now-redundant "Review fields". */
.results.state-review.is-ready #review-fields-btn { display: none; }
.results.state-review.is-ready #confirm-all-menu-btn { display: none; }

/* is-review-complete: show "Reopen review" (the button repurposes via JS),
   hide "Review fields" and overflow menu item. Fields are locked via
   .is-review-locked on #fields-editor (set by updateCockpitActions). */
.results.is-review-complete:not(.is-shared-readonly) #confirm-all-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.results.is-review-complete #review-fields-btn { display: none; }
.results.is-review-complete #confirm-all-menu-btn { display: none; }

/* state-no-fields: promote Export to primary + restore visible "View preview"
   button (phase A moved it into the ⋯ overflow menu; restore the cockpit
   affordance so unstructured-doc users have a direct path to the preview). */
.results.state-no-fields #export-btn {
  background: var(--accent-cta);
  border-color: var(--accent-cta);
  color: #fff;
  font-weight: 700;
}
.results.state-no-fields #export-btn:hover { background: var(--accent-cta-hover); }
#view-preview-cockpit-btn { display: none; }
.results.state-no-fields #view-preview-cockpit-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

/* Shared read-only records (updateCockpitActions): confirm/save is owner-only —
   the save endpoint would clone a foreign record into the viewer's history.
   Must follow the .state-* show rules to win at equal specificity. */
.results.is-shared-readonly #review-fields-btn,
.results.is-shared-readonly #confirm-all-btn,
.results.is-shared-readonly #bulk-verify-btn,
.results.is-shared-readonly #confirm-all-menu-btn { display: none; }

/* Secondary bulk confirmation: visibility is decided by updateCockpitActions
   via the hidden attribute (issues remain AND the policy accepts something).
   [hidden] must beat the .cockpit-actions flex display for buttons. */
#bulk-verify-btn[hidden] { display: none; }

/* Share lives in the overflow menu; .share-btn.hidden and .share-btn.is-disabled
   rules in the share section below handle visibility and disabled state. */

/* ---- Two-prominent-actions rule (§6) — .demote-* set by updateActionPriority()
   in app.js. The action bar never shows more than one primary + one secondary
   (plus ⋯). Whatever gets displaced keeps working from an existing ⋯ row wired
   to the SAME handler — no action is removed, none is duplicated.

   demote-export : two workflow actions already own both slots
                   (Approve+Request changes, or Submit+Review fields …)
   demote-reopen : "Reopen review" yields its slot to the approval action
   Both selectors are .results-scoped so they outrank the state show rules
   above (.state-review, .is-review-complete …), and carry the same :not()
   guards those rules use — a shared-readonly viewer or an approved document
   must stay locked out.
   NB: never write a state glob followed by a slash in a CSS comment; the "*"
   plus "/" pair closes the comment early and silently eats the next rule. */
#export-menu-row { display: none; }
.results .cockpit-actions.demote-export #export-btn { display: none; }
.results .cockpit-actions.demote-export #export-menu-row { display: flex; }

.results .cockpit-actions.demote-reopen #confirm-all-btn { display: none; }
/* The ⋯ "Complete review" row is a FALLBACK for the demoted bar button, never a
   twin shown beside it — the same action in two places at once is exactly the
   header noise this pass removes. */
.results .cockpit-actions:not(.demote-reopen) #confirm-all-menu-btn { display: none; }
.results:not(.is-shared-readonly):not(.is-approved) .cockpit-actions.demote-reopen #confirm-all-menu-btn {
  display: flex;
}

/* ---- Completion gate (§2) — classes set by updateCockpitActions from the
   shared unresolved-work model, which mirrors the server rule.

   is-completion-blocked : the backend would answer "Complete review" with 409,
                           so the action is not offered at all. Excludes
                           .is-review-complete, where the SAME button means
                           "Reopen review" — reopening is never gated on
                           unresolved work.
   has-unresolved-units  : there is at least one field card or the line-items
                           collection to click through to, so the review CTA is
                           the primary action in every state that has work left
                           (including state-reviewed, which can still hold a
                           missing required key that owns no card).

   Declared after the state and demote blocks above so it wins the
   equal-specificity ties against their show rules. */
.results.is-completion-blocked:not(.is-review-complete) #confirm-all-btn,
.results.is-completion-blocked:not(.is-review-complete) #confirm-all-menu-btn {
  display: none;
}
.results.has-unresolved-units:not(.is-review-complete):not(.is-shared-readonly) #review-fields-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Destructive overflow row — colour is a reinforcement, the separator above and
   the last position carry the meaning too. */
.overflow-row-danger { color: var(--danger); }
.overflow-row-danger svg { color: var(--danger); }
.overflow-row-danger:hover { background: rgba(220, 38, 38, 0.08); }
/* Drop the separator when the destructive row it introduces is not shown. */
.result-overflow-menu .overflow-divider:has(+ #reject-doc-btn[hidden]) { display: none; }

/* "The document preview is still available." would contradict the no-preview
   fallback; hide the note whenever the fallback is showing (covers every
   sync/async preview path without extra JS bookkeeping). */
#pdf-section:has(#pdf-fallback:not(.hidden)) .preview-boxes-note { display: none; }

/* Empty-fields card holds two short lines, not a single text node. */
.field-card.empty p { margin: 0; }
.field-card.empty p + p { margin-top: var(--space-1); }

/* ---- Classification details accordion ---- */
.classification-details {
  margin: var(--space-2) 0 var(--space-4);
  text-align: left;
}
/* `[hidden]` is a UA stylesheet rule; no author display override means it is
   not clobbered — same reasoning as .processing-meta-row[hidden]. */
.classification-details[hidden] { display: none; }
.classification-details-summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 48px;
  padding: var(--space-2) var(--space-1);
  cursor: pointer;
  list-style: none;
  color: var(--ink-soft);
  font-size: var(--text-base);
  font-weight: 600;
  border-top: 1px solid var(--border-light);
}
.classification-details-summary::-webkit-details-marker { display: none; }
.classification-details-summary:hover { color: var(--ink); }
.classification-details-summary svg { flex: 0 0 auto; margin-left: auto; transition: transform 0.15s; }
.classification-details[open] > .classification-details-summary { color: var(--ink); }
.classification-details[open] > .classification-details-summary svg { transform: rotate(180deg); }
.classification-hint {
  flex: 1;
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--ink-soft);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.classification-details-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-1) var(--space-3);
}
.classification-kv-list {
  display: grid;
  grid-template-columns: 175px 1fr;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  padding: 0;
  font-size: var(--text-sm);
}
.classification-kv-row { display: contents; }
.classification-kv-list dt {
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
  align-self: baseline;
}
.classification-kv-list dd {
  margin: 0;
  color: var(--ink);
  word-break: break-word;
  align-self: baseline;
}
.classification-actions { }
.classification-change-type-btn {
  font-size: var(--text-sm);
  padding: 5px 12px;
}
@media (max-width: 640px) {
  .classification-kv-list {
    grid-template-columns: 1fr;
  }
  .classification-kv-list dt { color: var(--ink-soft); }
}

/* ---- Technical details disclosure (replaces the Details tab) ---- */
.technical-details {
  margin: var(--space-2) 0 var(--space-4);
  text-align: left;
}
.technical-details-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: 48px;
  padding: var(--space-2) var(--space-1);
  cursor: pointer;
  list-style: none;
  color: var(--ink-soft);
  font-size: var(--text-base);
  font-weight: 600;
  border-top: 1px solid var(--border-light);
}
.technical-details-summary::-webkit-details-marker { display: none; }
.technical-details-summary:hover { color: var(--ink); }
.technical-details-summary svg { flex: 0 0 auto; transition: transform 0.15s; }
.technical-details[open] > .technical-details-summary { color: var(--ink); }
.technical-details[open] > .technical-details-summary svg { transform: rotate(180deg); }
.technical-details-body {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-1) 0 var(--space-2);
}
.tech-subsection {
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.tech-subsection.hidden { display: none; }
.tech-subsection > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  list-style: none;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink-secondary);
}
.tech-subsection > summary::-webkit-details-marker { display: none; }
.tech-subsection > summary:hover { background: var(--surface-soft); }
.copy-code-btn {
  margin-left: auto;
  margin-top: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 0;
}
.copy-code-btn:hover { color: var(--ink); background: var(--surface-soft); }

/* ── Post-result feedback widget ──────────────────────────────────────────── */
/* The <section hidden> uses the global [hidden]{display:none!important} rule.
   Layout (flex) lives on .result-feedback-inner, never on the toggled element. */
.result-feedback {
  margin-top: var(--space-4);
  border-top: 1px solid var(--border);
  padding-top: var(--space-4);
}
.result-feedback-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.result-feedback-prompt {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.result-feedback-question {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
}
.result-feedback-ratings {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.result-feedback-reasons {
  margin: 0;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.result-feedback-reasons-legend {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
  padding: 0 var(--space-1);
}
.result-feedback-reason-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.875rem;
  color: var(--ink);
  margin-top: var(--space-2);
  cursor: pointer;
}
.result-feedback-send-btn {
  margin-top: var(--space-3);
}
.result-feedback-thankyou {
  margin: 0;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* ---- Field-panel header: status line + completeness bar (Phase C) ---- */
.field-panel-head {
  margin-bottom: var(--space-3);
}
.field-panel-status {
  font-size: 0.8125rem; /* 13px */
  color: var(--ink-soft);
  margin: 0 0 4px;
  line-height: 1.4;
}
/* Per-field completeness breakdown ("Required X/Y · Recommended N not found ·
   Not applicable M"). One row that wraps to a single column at narrow widths /
   200% zoom — never a two-column grid, so nothing clips. The middot separators
   are decorative; each segment is self-describing text (status never
   colour-only, §10). */
.field-panel-breakdown { display: flex; flex-wrap: wrap; row-gap: 2px; }
.field-panel-breakdown-seg { white-space: nowrap; }
.field-panel-breakdown-seg:not(:last-child)::after {
  content: "·";
  margin: 0 6px;
  color: var(--ink-soft);
}
/* Missing-critical callout: a quiet amber action line under the count summary.
   Button semantics (jumps to the flagged fields) styled as text — the fields
   below stay the panel's visual focus. */
.field-panel-critical {
  display: block;
  margin: 4px 0 0;
  padding: 0;
  border: none;
  background: none;
  text-align: left;
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.4;
  /* --warn-text (5.02:1 on white), not --accent-warm (#d97706, only 3.18:1 as
     small text) — this button states a missing-required-field fact, AA 4.5:1. */
  color: var(--warn-text);
  cursor: pointer;
}
.field-panel-critical:hover { text-decoration: underline; }
.field-panel-critical[hidden] { display: none; }
/* §8/§11: on a closed review the same fact is reported, not actioned — quiet
   ink, no pointer affordance, no amber urgency next to an "Approved" badge. */
.field-panel-critical.is-informational {
  color: var(--ink-soft);
  font-weight: 500;
  cursor: default;
}
.field-panel-critical.is-informational:hover { text-decoration: none; }

/* ---- Guided-review progress strip ---- */
/* TRAP: never put display:flex on the bare .guided-review-strip selector —
   it would override the [hidden] attribute and make the strip always visible.
   Scope flex to the .is-active state and add an explicit [hidden] guard. */
#guided-review-strip[hidden],
#grs-kbd-hint[hidden] { display: none !important; }

.guided-review-strip.is-active {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--accent-light);
  border-radius: 8px;
  font-size: var(--text-xs);
}
.grs-icon {
  flex-shrink: 0;
  color: var(--accent);
  font-size: var(--text-sm);
}
.grs-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--accent-strong);
  font-weight: 600;
}
.grs-actions {
  display: flex;
  gap: var(--space-1);
  flex-shrink: 0;
}
@media (max-width: 900px) {
  .grs-actions .tiny-btn { min-height: 44px; }
}
.grs-kbd-hint {
  margin: 0 0 var(--space-3);
  padding: 0 var(--space-1);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  opacity: 0.7;
}

/* ---- Completeness details: quiet inline disclosure (Phase C) ---- */
.financial-inline {
  margin: 0 0 var(--space-3);
}
.financial-inline.hidden { display: none; }
.financial-inline-summary {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-xs);
  color: var(--accent);
  cursor: pointer;
  list-style: none;
  user-select: none;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
  margin-bottom: var(--space-2);
}
.financial-inline-summary::-webkit-details-marker { display: none; }
.financial-inline-summary::marker { display: none; }
.financial-inline-summary:hover { opacity: 0.75; }
.financial-inline .when-open { display: none; }
details.financial-inline[open] .when-closed { display: none; }
details.financial-inline[open] .when-open { display: inline; }
.financial-card-body { padding: 0 0 var(--space-3); }

/* Explicitly labeled schema-completeness score, demoted into the disclosure
   (§4) — quiet metadata, never an overall-quality signal. */
.completeness-score-detail {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-soft);
}
.completeness-score-detail[hidden] { display: none; }

/* ---- Review body — 2-col layout ---- */
.review-body {
  /* Shared breathing room above/below both sticky columns. Declared here (not
     inside the media query) so the desktop rules below can never reference an
     undefined var() — an undefined var invalidates the WHOLE declaration at
     computed-value time, silently dropping the max-height that bounds the
     workspace. */
  --review-sticky-offset: var(--space-4);
  display: grid;
  gap: 18px;
  align-items: start;
  margin-bottom: 18px;
}

/* THE single desktop two-column block — previously split across two duplicate
   @media (min-width:1100px) blocks whose shared properties silently desynced
   (preview top 116px vs fields top 60px). Keep every column rule here. */
@media (min-width: 1100px) {
  .review-body {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: 22px;
  }
  /* The preview stays visible while the field column is worked through: sticky
     within the review workspace, with its own internal scroll for documents
     taller than the viewport. No sticky app header on desktop — the offset is
     just breathing room. */
  .review-panel-preview {
    position: sticky;
    top: var(--review-sticky-offset);
    max-height: calc(100vh - var(--review-sticky-offset) - var(--space-4));
    overflow: auto;
  }

  /* ---- Viewport-bounded field column (§1/§10) ----
     The fields column is capped to the same viewport box as the preview and
     scrolls INTERNALLY, so a 30-field document no longer makes the whole page
     several viewports tall and no longer strands a multi-screen empty area
     under the (much shorter) preview. Line items / Classification / Review
     history / Technical details therefore start right after the workspace.

     The scroller is #fields-editor, not the panel: the panel head, the
     completeness disclosure and the segmented filter stay pinned above it and
     the "+ Add field" affordance stays pinned below, so neither is ever
     scrolled out of reach — the reason the earlier pass skipped the internal
     scroll altogether. */
  .review-panel-fields {
    position: sticky;
    top: var(--review-sticky-offset);
    align-self: start;
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-height: calc(100vh - var(--review-sticky-offset) - var(--space-4));
  }
  /* Flex chain down to the scroller. Every link needs min-height:0, otherwise
     the default min-height:auto of a flex item refuses to shrink below its
     content and the cap leaks back out into page height. */
  .review-panel-fields > .card-panel[open],
  .review-panel-fields > .card-panel[open] > .card-panel-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  .review-panel-fields > .card-panel[open] > .card-panel-body { overflow: hidden; }
  /* THE link that is easy to miss: Chrome (131+) generates a real
     ::details-content box between <details> and its children, so
     .card-panel-body is NOT the flex item of the panel — that anonymous box is,
     and it defaults to min-height:auto, refusing to shrink. Without this rule
     the body stays at full content height (measured 1829px inside an 854px
     details) and the field list never scrolls internally.
     Selectors match the DOM tree, so on Chrome the rule above STILL matches
     .card-panel-body and both apply — that is required, not redundant: the chain
     is details → ::details-content → .card-panel-body → #fields-editor and every
     link needs min-height:0. On engines that generate no such box the browser
     drops this rule as an unknown selector and the chain is one link shorter. */
  .review-panel-fields > .card-panel[open]::details-content {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
  }
  /* Pinned head/footer rows keep their natural height … */
  .review-panel-fields > .card-panel[open] > .card-panel-body > * { flex: none; }
  /* … except the completeness disclosure, which is allowed to shrink and scroll
     rather than squeeze the field list to nothing when expanded. The max-height
     cap is what actually enforces that intent: flex:0 1 auto on its own lets the
     disclosure take its full content height (600px+ with issues/suggestions), and
     a short field list creates no flex-shrink pressure to claw it back — so on an
     invoice with missing required data the auto-opened disclosure pushed
     #fields-editor off the bottom of the viewport-capped column ("field
     validation not visible"). Capped, the disclosure scrolls internally and the
     field list keeps usable space. */
  .review-panel-fields #financial-validation[open] {
    flex: 0 1 auto;
    min-height: 0;
    max-height: 240px;
    overflow-y: auto;
  }
  /* … and the field list itself, which takes the remaining space and scrolls.
     overscroll-behavior:contain stops the page from scroll-chaining when the
     list hits its end — the "confusing nested scroll" failure mode. */
  .review-panel-fields #fields-editor {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Room for the scrollbar so the focus ring / left status border of a row is
       never clipped against it. */
    padding-right: var(--space-2);
  }
}

/* The scroll region is focusable so it can be scrolled from the keyboard
   (a plain overflow container is not reachable by Tab in every browser).
   Only show the ring for keyboard focus — clicking a field inside must not
   outline the whole list. */
.fields-editor:focus { outline: none; }
.fields-editor:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius);
}

/* ---- Mobile tab bar ---- */
.review-tab-bar {
  display: none; /* hidden on desktop */
  overflow-x: auto;
  gap: 2px;
  background: var(--surface-soft);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--space-1);
  margin-bottom: 14px;
}

@media (max-width: 900px) {
  .review-tab-bar { display: flex; }

  /* Tab panels: only active tab visible on mobile */
  .review-panel[data-tab-panel] { display: none; }
  /* min-width:0 prevents the grid item from widening beyond the shell content
     area when white-space:nowrap children (e.g. .field-seg-btn) have a
     min-content wider than the viewport — the root cause of the ~18px
     horizontal overflow on 390px screens. */
  .review-panel[data-tab-panel].tab-active { display: block; min-width: 0; }

}

/* Two-way segmented control: equal halves, active = surface + dot + weight
   (never color alone). */
.review-tab {
  flex: 1;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 14px;
  border: 1px solid transparent;
  background: transparent;
  border-radius: 7px;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  transition: all 0.15s;
  margin-top: 0;
}

.review-tab:hover { color: var(--ink); background: var(--surface); }
.review-tab.is-active {
  background: var(--surface);
  border-color: var(--border);
  color: var(--ink);
  box-shadow: var(--shadow);
}
.review-tab.is-active::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--accent);
}

/* ---- Field segmented filter control ---- */
.field-seg {
  display: flex;
  align-items: stretch;
  /* Wrap instead of clipping labels when the fields column is narrow. */
  flex-wrap: wrap;
  gap: 2px;
  background: var(--surface-soft);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--space-1);
  margin: 0 0 var(--space-3);
}

/* Divider between the status filter ("Needs review", a subset) and the schema
   partition ("With values" + "Missing", which sum to the total). Signals the
   three counts are NOT one additive set. See §3. */
.field-seg-divider {
  flex: 0 0 auto;
  align-self: center;
  width: 1px;
  height: 20px;
  margin: 0 4px;
  background: var(--border);
}

/* Success state (§5): an empty "Needs review · 0" tab is never shown — the
   green "✓ No review needed" flag above the control already carries the status,
   leaving just the With values / Missing schema partition. */
.field-seg.is-no-review .field-seg-status,
.field-seg.is-no-review .field-seg-divider { display: none; }

.field-seg-btn {
  flex: 1 1 auto;
  min-height: 36px;
  min-width: max-content; /* never clip the label — the row wraps instead */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid transparent;
  background: transparent;
  border-radius: 7px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  transition: all 0.15s;
  margin-top: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.field-seg-btn:hover { color: var(--ink); background: var(--surface); }
.field-seg-btn.is-active {
  background: var(--surface);
  border-color: var(--border);
  color: var(--ink);
  box-shadow: var(--shadow);
}
/* Dot indicator for active tab — never color-only */
.field-seg-btn.is-active::before {
  content: "";
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--accent);
}

/* ---- CSS-driven field filter (data-fieldfilter on #fields-editor) ----
   The filter shows/hides individual field-row cards and their groups without
   any DOM re-render — just a data attribute flip on the container.
   Three filters: needs-review | extracted | not-found */

/* needs-review view: hide OK/unconfirmed-ok cards and confirmed cards */
#fields-editor[data-fieldfilter="needs-review"] .field-card.field-row:not(.needs-review),
#fields-editor[data-fieldfilter="needs-review"] .field-card.field-row.is-confirmed {
  display: none;
}

/* not-found view: hide all regular field groups (only the not-found accordion is visible) */
#fields-editor[data-fieldfilter="not-found"] .field-group {
  display: none;
}

/* extracted view: all non-empty cards in their groups are visible (no card hiding needed);
   groups remain collapsible as in the default view. */

/* Hide groups that have no visible cards in the needs-review filter */
#fields-editor[data-fieldfilter="needs-review"] .field-group:not(:has(.field-card.needs-review:not(.is-confirmed))) {
  display: none;
}

/* In needs-review view, force-expand collapsed group bodies so matching cards remain visible */
#fields-editor[data-fieldfilter="needs-review"] .field-group.collapsed .field-group-body {
  display: grid;
}

/* Extracted/default view: collapsed groups hide their body */
.field-group.collapsed .field-group-body { display: none; }

/* ---- Field group toggle (collapse/expand) ---- */
/* .field-group-header is now a <button>; reset browser defaults while preserving
   the existing flex/spacing rules above. */
button.field-group-header {
  width: 100%;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border-light);
  border-radius: 0;
  cursor: pointer;
  font: inherit;
  text-align: left;
  padding: 0 0 var(--space-2);
  margin: 0 0 10px;
}
button.field-group-header:hover { background: var(--surface-soft); }
button.field-group-header:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ---- "Not found" accordion (empty-value fields) ---- */
.not-found-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  margin-top: var(--space-2);
  overflow: hidden;
}

.not-found-summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  list-style: none;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-soft);
  user-select: none;
}
.not-found-summary::-webkit-details-marker { display: none; }
.not-found-summary::marker { display: none; }
.not-found-summary:hover { background: var(--surface-soft); }
.not-found-card[open] > .not-found-summary { color: var(--ink); border-bottom: 1px solid var(--border-light); }

/* Bullet-dot like the card-panel chevron pattern */
.not-found-summary::before {
  content: "";
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--ink-muted, var(--ink-soft));
  opacity: 0.4;
}
.not-found-card[open] > .not-found-summary::before { opacity: 1; }

.not-found-body {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3) var(--space-3);
}

/* Not-found accordion is only shown in the "not-found" filter view —
   hide it when any other filter (needs-review or extracted) is active. */
#fields-editor:not([data-fieldfilter="not-found"]) .not-found-card { display: none; }
/* …except in the Needs-review filter when it holds flagged missing REQUIRED
   fields (§3): those must appear under Needs review. The per-card filter rules
   still hide the accordion's unflagged optional rows; applyFieldFilter opens
   the <details> so the flagged cards are actually visible. */
#fields-editor[data-fieldfilter="needs-review"] .not-found-card:has(.field-card.needs-review:not(.is-confirmed)) { display: block; }

/* ---- Empty-filter state messages ---- */
/* Messages are hidden by default; CSS reveals the relevant one when the
   active filter matches zero field cards / no not-found accordion. */
.fields-empty-state-needs-review,
.fields-empty-state-not-found {
  display: none;
  padding: var(--space-3) 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

/* "Nothing needs review" when needs-review filter shows nothing */
#fields-editor[data-fieldfilter="needs-review"]:not(:has(.field-card.field-row.needs-review:not(.is-confirmed))) .fields-empty-state-needs-review {
  display: block;
}

/* "Every schema field has a value" when not-found filter has no accordion to show */
#fields-editor[data-fieldfilter="not-found"]:not(:has(.not-found-card)) .fields-empty-state-not-found {
  display: block;
}

/* ---- Visually hidden (accessible but not visible) ---- */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* ---- Compact field cards (horizontal layout) ---- */
/* Override the existing vertical field-card layout */
.field-card-compact .field-card-top {
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 2fr) auto;
  align-items: center;
  margin-bottom: 0;
}

/* Apply compact layout when class is set on the container */
.fields-editor.compact-mode .field-card:not([data-editing="true"]) .field-card-top {
  display: grid;
  grid-template-columns: minmax(120px, 1.2fr) minmax(0, 2fr) auto;
  align-items: center;
  gap: 10px;
  margin-bottom: 0;
}

.fields-editor.compact-mode .field-card:not([data-editing="true"]) .field-value-read {
  margin-top: 0;
  min-height: 0;
  padding: 6px 10px;
  font-size: var(--text-md);
  border: none;
  background: none;
  border-radius: 0;
}

.fields-editor.compact-mode .field-card:not([data-editing="true"]) {
  padding: var(--space-2) var(--space-3);
}

/* Responsive cockpit */
@media (max-width: 900px) {
  .review-cockpit {
    margin: 0 0 var(--space-4);
    padding: 0 0 var(--space-4);
    position: static; /* not sticky on mobile — takes too much space */
  }

  .cockpit-main {
    flex-direction: column;
    gap: 10px;
  }

  .cockpit-left { width: 100%; }

  .cockpit-actions {
    width: 100%;
    flex-wrap: wrap;
    align-self: auto;
  }

  .cockpit-actions .primary-btn,
  .cockpit-actions .export-menu-btn { flex: 1; justify-content: center; }
  /* One full-width dominant action on phones; ⋯ keeps its square. */
  .cockpit-actions .primary-btn { flex-basis: 100%; order: -1; min-height: 48px; }
  /* No structured fields: promoted Export inherits the dominant slot. */
  .results.state-no-fields .cockpit-actions #export-btn { flex-basis: 100%; order: -1; min-height: 48px; }
  .cockpit-actions .secondary-btn { min-height: 44px; }
}

/* ============================================================
   Review Workspace v2 — post-analysis layout, wide panels,
   compact field rows, history drawer
   ============================================================ */

/* ---- Wider application shell ---- */
.app-layout { max-width: 1440px; }

/* After analysis the page becomes a review workspace: the hero is collapsed
   (via #step-upload.hidden) and the shell expands to most of the viewport. */
.is-reviewing .shell {
  width: 100%;
  max-width: none;
  padding-top: var(--space-4);
}

/* Desktop review workspace: the floating corner pill (#lang-fab) would sit on
   top of the full-width result header, so on this surface the theme + language
   controls move into the persistent sidebar's Preferences section (already the
   home for both on mobile) and read as genuine app-shell chrome instead of
   floating beside the result. With the pill retired here, the header actions
   anchor to the right edge via the normal .cockpit-main flex — no gutter hack —
   and the fab-clearance top band is no longer needed. Upload/History keep the
   corner pill; their narrower / non-overlapping layouts don't conflict with it.
   (.is-reviewing is set on <html>; the higher-specificity selector below wins
   over the generic body:has(#app-sidebar) .shell top-padding rule.) */
@media (min-width: 901px) {
  .is-reviewing body:has(#app-sidebar) .shell { padding-top: var(--space-4); }
  /* Same conflict for width: the sidebar-aware 800px cap must not shrink the
     review workspace back to a centered column. The :not() clauses are pure
     specificity ballast — they mirror the capping rule above so this override
     outranks it ((1,4,1) vs (1,3,1)); without them it loses and the cap
     survives the .is-reviewing toggle. */
  .is-reviewing body:has(#app-sidebar) .shell:not(.workspace-shell):not(.billing-shell) {
    width: 100%;
    max-width: none;
  }
  .is-reviewing .lang-fab { display: none; }
  .is-reviewing .appnav-prefs {
    display: flex;
    flex-direction: column;
    margin: var(--space-6) 0 0;
  }
}

/* ============================================================
   Saved-analysis loading & error states
   Opening a history record (?record=ID) toggles is-loading-record /
   is-record-error on <html>. These hide the upload hero and results so the
   Analyze page never flashes as an intermediate fallback, and show a
   document-detail skeleton (or a clear error) instead.
   ============================================================ */
.record-loading,
.record-error { display: none; }
.is-loading-record #record-loading { display: block; }
.is-record-error #record-error { display: block; }

/* While loading or after a load failure, never reveal the upload/analyze flow
   or stale results for the selected record. */
.is-loading-record #step-upload,
.is-loading-record #step-upload-summary,
.is-loading-record #analyzing-state,
.is-loading-record #batch-panel,
.is-loading-record #results,
.is-record-error #step-upload,
.is-record-error #step-upload-summary,
.is-record-error #analyzing-state,
.is-record-error #batch-panel,
.is-record-error #results { display: none !important; }

/* Batch deep-link (?batch= / ?instant_batch=): hide the upload hero before the
   batch container arrives. Unlike ?record= we skip the record-loading skeleton —
   the batch panel is the content target.  Cleared by initBatchPanel on resolve. */
.is-loading-batch #step-upload,
.is-loading-batch #step-upload-summary,
.is-loading-batch #analyzing-state,
.is-loading-batch #results { display: none !important; }

.record-loading { padding: var(--space-2) 0 var(--space-6); }
.record-loading-text {
  margin: 18px 2px 0;
  font-size: var(--text-base);
  color: var(--muted, #667085);
}
.record-skel-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 22px;
  margin-top: 18px;
}
@media (min-width: 1100px) {
  .record-skel-grid { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
}
.record-skel-col { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.record-skel,
.skel {
  background: var(--surface-soft);
  border-radius: 10px;
  position: relative;
  overflow: hidden;
}
.record-skel-bar { height: 56px; border-radius: 12px; }
.record-skel-line { height: 18px; width: 100%; }
.record-skel-line.short { width: 45%; }
.record-skel-block { height: 120px; }
.record-skel-preview { height: 420px; border-radius: 12px; }
/* Generic skeleton building blocks — same visual system, any async surface. */
.skel-line { height: 12px; border-radius: 6px; width: 100%; }
.skel-list { display: flex; flex-direction: column; gap: var(--space-3); }
.skel-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  background: var(--surface);
}
/* Subtle shimmer — respects reduced-motion. */
.record-skel::after,
.skel::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in srgb, var(--ink) 7%, transparent),
    transparent
  );
  animation: record-skel-shimmer 1.3s ease-in-out infinite;
}
@keyframes record-skel-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .record-skel::after,
  .skel::after { animation: none; }
}

.record-error {
  margin: var(--space-10) auto;
  max-width: 460px;
  text-align: center;
  padding: var(--space-8) var(--space-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
}
.record-error-text {
  margin: 0 0 var(--space-4);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--ink);
}
.record-error-back { display: inline-block; }

/* Two-panel review body: all desktop column rules live in the single
   @media (min-width:1100px) block next to .review-body above. */
.is-reviewing #pdf-preview { min-height: 70vh; }

/* ---- Top-bar History toggle button ---- */
.nav-link-btn {
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
}

/* ============================================================
   History off-canvas drawer (replaces the always-on sidebar)
   ============================================================ */
.history-sidebar {
  position: fixed;
  top: var(--topbar-h);
  right: 0;
  width: min(360px, 90vw);
  height: calc(100vh - var(--topbar-h));
  transform: translateX(100%);
  transition: transform 0.25s ease;
  z-index: 70;
  box-shadow: -8px 0 28px rgba(0, 0, 0, 0.18);
  border-left: 1px solid var(--border);
}
.history-sidebar.is-open { transform: translateX(0); }

.history-backdrop {
  position: fixed;
  inset: var(--topbar-h) 0 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 65;
}
/* Desktop has no top bar, so the history drawer spans the full height. */
@media (min-width: 901px) {
  .history-sidebar { top: 0; height: 100vh; }
  .history-backdrop { inset: 0; }
}
.history-backdrop.hidden { display: none; }

.sidebar-header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: 10px;
}
.sidebar-header-top .sidebar-title { margin: 0; }
.sidebar-close {
  background: none;
  border: none;
  font-size: var(--text-3xl);
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 0 var(--space-1);
}
.sidebar-close:hover { color: var(--ink); }

/* ============================================================
   Compact field rows (dense, scannable, click-to-expand)
   ============================================================ */
.fields-editor { gap: var(--space-2); }
/* §3: a group is structure, not decoration. The fields panel is already a card
   and every row carries its own frame, so a third nested card around each group
   was pure noise and cost ~26px of height per group. Flatten to "header + list"
   and keep only the thin rule under the header for hierarchy. */
.field-group {
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
}
.field-group + .field-group { margin-top: var(--space-3); }
.field-group-header {
  padding-bottom: var(--space-1);
  margin-bottom: var(--space-2);
}
.field-group-subtitle { margin-top: 1px; font-size: var(--text-xs); }
/* Only a group that still holds unresolved work is highlighted; a fully handled
   group reads as quiet structure. The count badge keeps its own colour logic
   (groupReviewStatus) — untouched. */
.field-group:not(:has(.field-card.needs-review:not(.is-confirmed))) .field-group-title {
  color: var(--ink-soft);
}
.field-group:has(.field-card.needs-review:not(.is-confirmed)) .field-group-title {
  color: var(--warn-text);
}
.field-group-body { gap: var(--space-1); }

.field-card.field-row {
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-left: 3px solid transparent;
  border-radius: 0;
  overflow: hidden;
  transition: background 0.15s, border-color 0.15s;
}
.field-card.field-row.is-confirmed {
  border-left-color: var(--success);
  background: rgba(5, 150, 105, 0.03);
}
.field-card.field-row.needs-review { border-left-color: var(--accent-warm); }

.field-row-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 6px 2px 2px;
}

.field-row-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
  padding: 7px var(--space-2);
  background: none;
  border: none;
  border-radius: 6px;
  text-align: left;
  cursor: pointer;
  font: inherit;
}
.field-row-toggle:hover { background: var(--surface-soft); }
.field-row-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.field-row .field-key {
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Value preview — second line, full-width, readable */
.field-row-value-preview {
  padding: 0 var(--space-3) 7px var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--ink);
  word-break: break-word;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}
.field-row.is-confirmed .field-row-value-preview { color: var(--ink); }

/* Inline spans inside the value preview */
.field-value-empty {
  font-style: italic;
  font-size: var(--text-xs);
  /* --ink-soft (5.17:1 on white) reads as muted on its own; the old opacity:0.6
     composited it to ~2.5:1. "No value extracted" conveys state → needs AA 4.5:1. */
  color: var(--ink-soft);
}
/* Extracted-value preview text (summary rows) — data mono, like
   .field-value-read above. */
.field-value-text {
  font-family: var(--font-mono);
  font-size: var(--text-md);
}

.field-row-status {
  flex-shrink: 0;
  font-size: var(--text-2xs);
  font-weight: 700;
  /* Sentence case, not SHOUTING CAPS — "Needs review" / "✓ Confirmed" read as
     plain-language status, not technical tier codes. */
  letter-spacing: 0.01em;
  padding: 3px 7px;
  border-radius: 4px;
  white-space: nowrap;
}
.field-row-status.is-ok { background: var(--surface-soft); color: var(--ink-soft); }
.field-row-status.is-confirmed-chip { background: rgba(5, 150, 105, 0.12); color: var(--success-text); }
/* Amber "Needs review" — one flag for every unconfirmed low/mid-confidence field,
   matching the amber left indicator (.field-row.needs-review). Replaces the old
   split CHECK (amber) / REVIEW (red) tier chips. */
.field-row-status.is-needs-review { background: rgba(217, 119, 6, 0.08); color: var(--warn-text); }
/* Needs correction — a human flagged the value as wrong (amber, distinct copy). */
.field-row-status.is-needs-correction { background: rgba(217, 119, 6, 0.08); color: var(--warn-text); }
/* A live deterministic failure (header totals do not reconcile) outranks the
   verification state on the implicated cards — red, never amber. */
.field-row-status.is-validation-failed { background: rgba(220, 38, 38, 0.08); color: var(--danger, #dc2626); }
/* Not applicable — an explicit "does not apply" decision (quiet, neutral). */
.field-row-status.is-not-applicable { background: var(--surface-soft); color: var(--ink-soft); }
/* Proposed not-applicable (reverse-charge / exempt VAT) awaiting confirmation:
   neutral like a settled N/A but with a dashed edge to read as "pending" — never
   the amber "missing" flag. Full ink for AA contrast. */
.field-row-status.is-not-applicable.is-proposed-na {
  color: var(--ink);
  border: 1px dashed var(--border);
  padding: 2px 6px;
}
/* Informational per-field note (e.g. "job title preserved as printed"). Neutral,
   never amber — it needs no action and is never counted as an issue (§7). */
.field-info-note {
  min-width: 0;
  font-size: var(--text-2xs);
  font-weight: 500;
  color: var(--ink-soft);
  background: var(--surface-soft);
  padding: 3px 7px;
  border-radius: 4px;
  overflow-wrap: anywhere;
}
/* One-click "Confirm reverse charge / not applicable" primary on a proposed-N/A
   card; full touch target on mobile (WCAG 2.5.8). */
.field-confirm-na-btn { font-weight: 600; white-space: nowrap; }
@media (max-width: 900px) {
  .field-confirm-na-btn { min-height: 44px; }
}
/* Current guided-review item — a persistent inset bar (distinct from the
   transient keyboard-active outline) so its position is clear to sighted and AT
   users. Uses a forced-colors fallback since box-shadow is dropped there. */
.field-card[aria-current="true"] { box-shadow: inset 3px 0 0 0 var(--accent); }
@media (forced-colors: active) {
  .field-card[aria-current="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
}

/* §12: compact in-flight state for a workflow action. Deliberately not a
   spinner swap — the label must stay readable so the user can see WHICH action
   is running, and the button must not resize and shift the bar underneath it. */
button.is-busy {
  position: relative;
  opacity: 0.72;
  cursor: progress;
}
button.is-busy::after {
  content: "";
  display: inline-block;
  width: 0.7em;
  height: 0.7em;
  margin-left: 0.5em;
  vertical-align: -0.05em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: busy-spin 0.6s linear infinite;
}
@keyframes busy-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  button.is-busy::after { animation: none; opacity: 0.6; }
}

/* §11: once the review is closed the fields are locked, so an amber "needs
   attention" row indicator promises an action the user can no longer take. The
   rows go quiet; the accessible status text still describes the final state. */
#fields-editor.is-review-locked .field-card.field-row.needs-review,
#line-items-editor.is-review-locked .line-items-standalone.is-needs-correction {
  border-left-color: var(--border);
}
#fields-editor.is-review-locked .field-row-status.is-needs-review,
#line-items-editor.is-review-locked .field-row-status.is-needs-review {
  background: var(--surface-soft);
  color: var(--ink-soft);
}

/* §6: a plain "Needs verification" chip is the fourth copy of the same signal
   (border + Verify button + group count + header count), so it is removed from
   sight — but NOT from the accessibility tree, so the state is still announced
   and is never carried by colour alone (§14). Chips that add information
   ("Required · not found", "Needs correction") keep their visible label. */
.field-row-status.is-implicit {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Per-field state actions (Needs correction / Not applicable), shown in the
   expanded field detail. Outlined so they read as controls rather than the
   passive .field-row-status chip (both would otherwise share .tiny-btn's flat
   surface-soft fill); the active one reads pressed via .is-active + aria-pressed. */
.field-state-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 8px 0 4px;
}
.field-state-actions .tiny-btn {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent-strong);
}
.field-state-actions .tiny-btn:hover {
  background: var(--surface-soft-hover);
  border-color: var(--accent-strong);
}
.field-state-actions .tiny-btn.is-active {
  background: transparent;
  border-color: var(--accent-warm);
  color: var(--accent-warm);
  font-weight: 700;
}
.field-state-reason {
  font-size: var(--text-2xs);
  color: var(--ink-soft);
}

/* Per-row "Flag row" button in the read-only line-items table.
   Compact affordance for flagging a specific row; clicking sets the entire
   line_items field to needs_correction (field-level server contract).
   When the parent block is already flagged (.is-needs-correction), all row
   buttons reflect the amber state so the reviewer sees which field is flagged
   and can click any row button to resolve it. */
.flag-line-item-row-btn { white-space: nowrap; }
.line-items-standalone.is-needs-correction .flag-line-item-row-btn {
  border-color: var(--accent-warm);
  color: var(--accent-warm);
  font-weight: 700;
}

/* ── Line-item arithmetic check UI ──────────────────────────────────────────
   .line-items-math-summary: <details> banner; reuses the .warnings palette.
   .has-math-issue: row background tint for rows with a reconciliation mismatch.
   .math-issue-chip: inline "Mismatch" badge inside the actions cell.
   ───────────────────────────────────────────────────────────────────────── */
.line-items-math-summary {
  margin: var(--space-3) 0 var(--space-2);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-4);
}
.line-items-math-summary > summary {
  list-style: none;
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.line-items-math-summary > summary::before { content: "▸"; font-size: 0.65em; transition: transform 0.15s; }
.line-items-math-summary[open] > summary::before { transform: rotate(90deg); }
.line-items-math-summary ul.math-summary-list {
  margin: var(--space-2) 0 0;
  padding-left: 18px;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.math-detail-tolerance {
  display: block;
  margin-top: var(--space-1);
  color: var(--ink-soft);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}
.line-items-math-summary.warning {
  border-left: 4px solid var(--accent-warm);
  background: #fffbeb;
}
.line-items-math-summary.warning > summary { color: #92400e; }
.line-items-math-summary.informational {
  border-left: 4px solid var(--border);
  background: var(--surface-soft);
}
.line-items-math-summary.informational > summary { color: var(--ink-soft); }

/* Subtle amber row tint when a line-item row has an arithmetic mismatch. */
.has-math-issue td { background: color-mix(in srgb, #f59e0b 6%, transparent) !important; }

/* Inline chip in the table-actions cell for mismatch rows. */
.math-issue-chip {
  display: inline-block;
  font-size: var(--text-2xs);
  font-weight: 700;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: #fef3c7;
  color: #92400e;
  white-space: nowrap;
  margin-right: var(--space-1);
  vertical-align: middle;
}

/* Dark-mode math styles */
[data-theme="dark"] .line-items-math-summary.warning {
  background: rgba(245, 158, 11, 0.1);
  border-left-color: var(--accent-warm);
}
[data-theme="dark"] .line-items-math-summary.warning > summary { color: #fbbf24; }
[data-theme="dark"] .line-items-math-summary.informational { background: var(--surface-soft); }
[data-theme="dark"] .has-math-issue td { background: color-mix(in srgb, #f59e0b 10%, transparent) !important; }
[data-theme="dark"] .math-issue-chip { background: rgba(245, 158, 11, 0.15); color: #fbbf24; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .line-items-math-summary.warning {
    background: rgba(245, 158, 11, 0.1);
    border-left-color: var(--accent-warm);
  }
  :root:not([data-theme="light"]) .line-items-math-summary.warning > summary { color: #fbbf24; }
  :root:not([data-theme="light"]) .has-math-issue td { background: color-mix(in srgb, #f59e0b 10%, transparent) !important; }
  :root:not([data-theme="light"]) .math-issue-chip { background: rgba(245, 158, 11, 0.15); color: #fbbf24; }
}

/* Autosave status microtext for per-field verification edits. */
.field-save-status {
  margin: 4px 0 0;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--ink-soft);
}
.field-save-status.is-saving { color: var(--ink-soft); }
.field-save-status.is-saved { color: var(--success-text); }
.field-save-status.is-failed { color: var(--danger, #dc2626); }

/* Confidence suffix: quiet, muted, middot-separated (e.g. "· 55% confidence").
   Never coloured — the status chip carries the only colour on the meta line. */
.field-row-conf {
  flex-shrink: 0;
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  white-space: nowrap;
}
.field-row-conf::before {
  content: "·";
  margin: 0 0.4em 0 0;
  color: var(--ink-soft);
}
/* Confirmed rows read simply "✓ Confirmed" — the confidence detail is redundant
   once the reviewer has vouched for the value. */
.field-card.field-row.is-confirmed .field-row-conf { display: none; }

.field-row .field-card-controls {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding-right: var(--space-1);
  /* Phase F: hide controls by default (visibility, not display, so focus still works).
     Revealed on hover, focus-within, or unconditionally for needs-review cards. */
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.12s, visibility 0.12s;
}
.field-card.field-row:hover .field-card-controls,
.field-card.field-row:focus-within .field-card-controls {
  visibility: visible;
  opacity: 1;
}
/* Always show controls for cards that need attention — no hover required */
.field-card.field-row.needs-review .field-card-controls {
  visibility: visible;
  opacity: 1;
}

/* Phase F: status chip + confidence badge move below the value preview */
.field-row-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 var(--space-3) var(--space-2);
  min-height: 0;
}
/* Hide meta line when the row is expanded (detail takes over) */
.field-row[data-editing="true"] .field-row-meta { display: none; }

/* Detail revealed only when the row is expanded */
.field-row-detail { display: none; padding: 0 var(--space-3) var(--space-3) var(--space-3); }
.field-row[data-editing="true"] { background: var(--surface-soft); }
.field-row[data-editing="true"] .field-row-detail { display: block; }
.field-row[data-editing="true"] .field-row-value-preview { display: none; }
.field-row-inline-meta { padding: 0 var(--space-3) var(--space-2); margin: 0; font-size: var(--text-xs); }

/* Mobile: stack label on its own row so it isn't squeezed to 0px by the controls */
@media (max-width: 560px) {
  .field-row-head { flex-wrap: wrap; }
  .field-row-toggle {
    flex: 1 1 100%;
    padding-bottom: 2px;
  }
  .field-row .field-card-controls {
    width: 100%;
    gap: var(--space-1);
    padding: 0 var(--space-1) var(--space-1);
    justify-content: flex-end;
  }
  .field-row-value-preview { padding: 0 var(--space-2) 6px var(--space-2); }
}

/* ── Share button ──────────────────────────────────────────────────────────── */
.share-btn { display: flex; align-items: center; gap: 6px; }
.share-btn svg { flex-shrink: 0; }
/* `.share-btn` sets display:flex, which (same specificity, declared later) would
   otherwise beat the generic `.hidden { display:none }`. This compound selector
   outranks both so the share button can actually be hidden. */
.share-btn.hidden { display: none; }
/* Greyed-out-but-clickable: the button stays in the action bar (so sharing never
   appears to "vanish"), shows a tooltip explaining why, and surfaces a toast on
   click. pointer-events stays enabled so the click handler can run. */
.share-btn.is-disabled { color: var(--ink-soft); border-color: var(--border); cursor: not-allowed; }

/* ── Shares management section ─────────────────────────────────────────────── */
.shares-section {
  margin: var(--space-5) 0 0;
  padding: var(--space-4) var(--space-5) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-soft);
}
.shares-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}
.shares-section-title {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--ink);
  margin: 0;
}
.shares-list { display: flex; flex-direction: column; gap: 10px; }
.share-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  font-size: var(--text-md);
}
.share-card-info { flex: 1; min-width: 0; }
.share-card-prefix {
  font-family: monospace;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  background: var(--surface-soft);
  padding: 1px 6px;
  border-radius: 4px;
  margin-right: 6px;
}
.share-card-meta { color: var(--ink-soft); font-size: var(--text-xs); margin-top: 2px; }
.share-card-status {
  font-size: var(--text-2xs);
  font-weight: 600;
  padding: 2px var(--space-2);
  border-radius: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.share-card-status.is-active { background: rgba(5,150,105,.12); color: var(--success-text); }
.share-card-status.is-expired { background: var(--surface-soft); color: var(--ink-soft); }
.share-card-status.is-revoked { background: rgba(220,38,38,.1); color: var(--danger); }
.share-card-actions { display: flex; gap: 6px; flex-shrink: 0; }

/* ── Share modal dialog ────────────────────────────────────────────────────── */
.share-modal {
  border: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 0;
  width: min(560px, 96vw);
  max-height: 90vh;
  overflow-y: auto;
  background: var(--surface);
  color: var(--ink);
}
.share-modal::backdrop {
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(2px);
}
.share-modal-inner { display: flex; flex-direction: column; }
.share-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px var(--space-5) 14px;
  border-bottom: 1px solid var(--border-light);
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}
.share-modal-title { font-size: var(--text-lg); font-weight: 700; margin: 0; }
.share-modal-close {
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--space-1);
  border-radius: 6px;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
}
.share-modal-close:hover { background: var(--surface-soft); color: var(--ink); }

/* Steps indicator */
.share-steps {
  display: flex;
  gap: 0;
  padding: 10px var(--space-5) 0;
}
.share-step {
  flex: 1;
  text-align: center;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--ink-soft);
  padding: 6px var(--space-1);
  border-bottom: 2px solid var(--border-light);
  transition: color 0.15s, border-color 0.15s;
}
.share-step.is-active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
.share-step.is-done {
  color: var(--success-text);
  border-bottom-color: var(--success);
}

/* Modal body / step panels */
.share-modal-body { padding: var(--space-5); display: flex; flex-direction: column; gap: 14px; }
.share-modal-body.hidden { display: none; }

.share-field-label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-soft);
  display: block;
  margin-bottom: 5px;
}
.share-field-hint { font-weight: 400; color: var(--ink-muted, var(--ink-soft)); }
.share-text-input {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: var(--ink);
  font-size: var(--text-md);
  box-sizing: border-box;
}
.share-text-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
.share-link-input { font-family: monospace; font-size: var(--text-sm); }

.share-expiry-group { border: none; padding: 0; margin: 0; }
.share-expiry-options { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.share-expiry-option {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 6px var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: var(--text-sm);
  transition: border-color 0.12s, background 0.12s;
}
.share-expiry-option:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-light);
  color: var(--accent);
}

.share-checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--text-md);
  cursor: pointer;
}
.share-checkbox-row input[type="checkbox"] { margin-top: 2px; flex-shrink: 0; }
/* `.share-checkbox-row` sets display:flex; this compound selector lets the
   `.hidden` toggle actually hide the row (e.g. the redacted-PDF option when
   redaction can't be guaranteed). */
.share-checkbox-row.hidden { display: none; }
.share-checkbox-row.is-disabled { opacity: .55; }
.share-checkbox-row.is-disabled input[type="checkbox"] { cursor: not-allowed; }
.share-hint {
  display: block;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin-top: var(--space-1);
}

.share-modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) 0 var(--space-1);
  border-top: 1px solid var(--border-light);
  position: sticky;
  bottom: 0;
  background: var(--surface);
  z-index: 1;
}

/* Field selector table */
.share-field-table-wrap { overflow-x: auto; border: 1px solid var(--border-light); border-radius: var(--radius); }
.share-field-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.share-field-table th {
  padding: 7px 10px;
  text-align: left;
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  background: var(--surface-soft);
  border-bottom: 1px solid var(--border-light);
}
.share-field-table td {
  padding: 7px 10px;
  border-bottom: 1px solid var(--border-light);
  vertical-align: middle;
}
.share-field-table tr:last-child td { border-bottom: none; }
.share-field-table tr.is-hidden-row td { opacity: 0.45; }
.share-field-table select {
  padding: 3px 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface-soft);
  color: var(--ink);
  font-size: var(--text-sm);
}
.share-sensitivity-badge {
  font-size: var(--text-2xs);
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 10px;
  white-space: nowrap;
}
.share-sensitivity-badge.sev-high { background: rgba(220,38,38,.12); color: var(--danger); }
.share-sensitivity-badge.sev-medium { background: rgba(217,119,6,.12); color: var(--accent-warm); }
.share-sensitivity-badge.sev-low { background: rgba(5,150,105,.1); color: var(--success-text); }
/* Non-finding states so a field's check status is always explicit. */
.share-sensitivity-badge.is-clear,
.share-sensitivity-badge.is-checking,
.share-sensitivity-badge.is-unknown {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.share-sensitivity-badge.is-clear { background: rgba(5,150,105,.1); color: var(--success-text); }
.share-sensitivity-badge.is-checking { background: var(--surface-soft); color: var(--ink-soft); }
.share-sensitivity-badge.is-unknown { background: rgba(217,119,6,.12); color: var(--accent-warm); }
.share-badge-spinner {
  width: 9px;
  height: 9px;
  border: 1.5px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: share-badge-spin 0.7s linear infinite;
}
@keyframes share-badge-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .share-badge-spinner { animation: none; }
}

.share-modal-hint { font-size: var(--text-md); color: var(--ink-soft); margin: 0; }

/* Scan results */
.share-scan-no-issues {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px 14px;
  background: rgba(5,150,105,.08);
  border: 1px solid rgba(5,150,105,.2);
  border-radius: var(--radius);
  font-size: var(--text-md);
  color: var(--success-text);
}
.share-scan-warnings {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.share-scan-warning-item {
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: var(--text-sm);
  border-left: 3px solid;
}
.share-scan-warning-item.sev-high { background: rgba(220,38,38,.06); border-color: var(--danger); }
.share-scan-warning-item.sev-medium { background: rgba(217,119,6,.06); border-color: var(--accent-warm); }
.share-scan-warning-item .warn-field { font-weight: 600; }
.share-scan-warning-item .warn-reason { color: var(--ink-soft); font-size: var(--text-sm); margin-top: 2px; }
.share-confirm-row { margin-top: var(--space-1); font-weight: 500; }
.share-step-error {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  background: rgba(220,38,38,.08);
  color: var(--danger);
  font-size: var(--text-sm);
}

/* Step 4: success */
.share-success-icon { text-align: center; }
.share-success-title { font-size: var(--text-lg); font-weight: 700; text-align: center; margin: 0; }
.share-success-hint { font-size: var(--text-md); color: var(--ink-soft); text-align: center; margin: 0; }
.share-link-row { display: flex; gap: var(--space-2); align-items: center; }
.share-link-row .share-text-input { flex: 1; }
.share-copy-btn { flex-shrink: 0; white-space: nowrap; }

/* ── Redacted-PDF preview (share step 3) ────────────────────────────────── */
.share-redact-preview {
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  padding: var(--space-3);
  background: var(--surface-soft);
}
.share-redact-pages {
  max-height: 50vh;
  overflow-y: auto;
  margin: var(--space-2) 0;
}
.share-redact-page {
  position: relative;
  margin-bottom: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: #fff;
}
.share-redact-page:last-child { margin-bottom: 0; }
.share-redact-page img {
  display: block;
  max-width: 100%;
  width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
}
.share-redact-page canvas {
  position: absolute;
  inset: 0;
  cursor: crosshair;
  touch-action: none;
}
.share-redact-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  flex-wrap: wrap;
}


/* ─── Team / Workspace styles ─────────────────────────────────────────── */

/* Team share badge */
.badge-team-only {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: 4px;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  background: #ede9fe;
  color: #5b21b6;
  border: 1px solid #c4b5fd;
}

/* "Beta" marker for not-yet-GA features (e.g. redacted-PDF share preview). */
.badge-beta {
  display: inline-flex;
  align-items: center;
  padding: 1px var(--space-2);
  border-radius: 999px;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: #fef3c7;
  color: #92400e;
  border: 1px solid #fde68a;
  vertical-align: middle;
}

/* Workspace switcher in header (compact) */
.ws-header-switcher {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-sm);
  color: var(--ink-soft, #666);
}

.ws-header-switcher a {
  color: var(--accent, #6366f1);
  text-decoration: none;
  font-weight: 500;
}

.ws-header-switcher a:hover {
  text-decoration: underline;
}

.ws-context-label {
  color: var(--ink-soft, #666);
  font-size: var(--text-sm);
}

/* ── Document ownership row ─────────────────────────────────────────────────── */
/* Compact badge holder inside .cockpit-badges — the ownership ACTIONS live in
   the ⋯ overflow menu, so this row only ever carries the badge itself. */
.doc-ownership-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  font-size: var(--text-sm);
}
.doc-ownership-row.hidden { display: none; }

.ownership-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0.25rem 0.625rem;
  border-radius: 20px;
  font-size: var(--text-xs);
  font-weight: 500;
}

.ownership-badge-ws {
  background: #dbeafe;
  color: #1e40af;
}

.ownership-badge-private {
  background: var(--bg, #f8f8fa);
  color: var(--text-muted, #666);
  border: 1px solid var(--border, #e5e5e5);
}

.ownership-move-group {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  flex-wrap: wrap;
}

.ownership-ws-select {
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--border, #e5e5e5);
  border-radius: 6px;
  font-size: var(--text-xs);
  background: var(--surface, #fff);
  color: var(--text, #1a1a1a);
  cursor: pointer;
}

.ownership-action-btn {
  padding: 0.2rem 0.625rem;
  border: 1px solid var(--border, #e5e5e5);
  border-radius: 6px;
  background: var(--surface, #fff);
  color: var(--text, #1a1a1a);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}

.ownership-action-btn:hover {
  background: var(--bg, #f8f8fa);
}

/* ── Share modal: access type selector ─────────────────────────────────────── */
.share-access-group {
  border: none;
  padding: 0;
  margin: 0 0 1rem;
}

.share-access-group legend {
  margin-bottom: 0.5rem;
}

.share-access-options {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.share-access-option {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 0.5rem;
  row-gap: 0.1rem;
  cursor: pointer;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--border, #e5e5e5);
  border-radius: 8px;
  transition: background .15s;
}

.share-access-option:has(input:checked) {
  border-color: var(--accent, #6366f1);
  background: var(--accent-light, #ede9fe);
}

.share-access-option input[type="radio"] {
  grid-row: 1 / 3;
  grid-column: 1;
  align-self: center;
  accent-color: var(--accent, #6366f1);
}

.share-access-option-label {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text);
}

.share-access-option-desc {
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: 1.4;
}

/* ── Share card: team-only badge ────────────────────────────────────────────── */
.badge-team-only {
  display: inline-block;
  padding: 0.18rem 0.5rem;
  border-radius: 4px;
  font-size: var(--text-2xs);
  font-weight: 600;
  background: #dbeafe;
  color: #1e40af;
  margin-right: var(--space-1);
}

/* ── Workspace nav link: hidden by default, shown when authenticated ─────────── */
.workspace-nav-link.hidden {
  display: none;
}

/* ── Batch (multi-file) processing panel ─────────────────────────────────── */
.batch-panel {
  margin-top: 1rem;
}
.batch-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}
.batch-title {
  margin: 0;
  font-size: var(--text-xl);
}
.batch-hint {
  color: #6b7280;
  font-size: var(--text-base);
  margin: 0.35rem 0 0.85rem;
}
.batch-status-chip {
  font-size: var(--text-sm);
  padding: 0.15rem 0.65rem;
  border-radius: 999px;
  background: #eef2ff;
  color: #4338ca;
  white-space: nowrap;
}
.batch-status-chip.status-completed {
  background: #dcfce7;
  color: #166534;
}
.batch-status-chip.status-failed,
.batch-status-chip.status-expired,
.batch-status-chip.status-cancelled {
  background: #fee2e2;
  color: #b91c1c;
}
.batch-file-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}
.batch-file-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid #e5e5e5;
  border-radius: 8px;
}
.batch-file-name {
  flex: 1;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.batch-file-badge {
  font-size: var(--text-xs);
  padding: 0.12rem 0.55rem;
  border-radius: 999px;
  background: #f3f4f6;
  color: #4b5563;
  white-space: nowrap;
}
.batch-file-badge.status-ok {
  background: #dcfce7;
  color: #166534;
}
.batch-file-badge.status-error,
.batch-file-badge.status-rejected {
  background: #fee2e2;
  color: #b91c1c;
}
.batch-file-view {
  flex-shrink: 0;
}
.batch-file-view:disabled {
  opacity: 0.35;
  cursor: default;
}
.batch-file-dismiss {
  flex-shrink: 0;
}

/* Async batch lane: one group per job, so multiple concurrent jobs each own a
   group instead of clobbering a single shared list. */
.batch-jobs {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin-top: 0.5rem;
}
.batch-job {
  border: 1px solid #e5e5e5;
  border-radius: 10px;
  padding: 0.6rem 0.7rem;
  transition: box-shadow 0.4s ease;
}
[data-theme="dark"] .batch-job {
  border-color: var(--border);
}
.batch-job--focused {
  box-shadow: 0 0 0 2px #6366f1;
}
.batch-job-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}
.batch-job-count {
  font-weight: 600;
  font-size: var(--text-base);
  flex: 1;
}
.batch-job-head-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}
.batch-show-all {
  display: block;
  width: 100%;
  margin-top: 0.4rem;
  font-size: var(--text-sm);
  text-align: center;
  padding: 0.3rem 0.6rem;
}
/* Flex on .batch-file-list overrides [hidden]; be explicit. */
.batch-file-list[hidden] {
  display: none !important;
}

/* Lifecycle badge colors — queued/processing in addition to ok/error/rejected. */
.batch-file-badge.status-queued,
.batch-file-badge.status-submitting {
  background: #e0e7ff;
  color: #3730a3;
}
.batch-file-badge.status-processing,
.batch-file-badge.status-in_progress {
  background: #fef9c3;
  color: #854d0e;
}
.batch-file-badge.status-completed {
  background: #dcfce7;
  color: #166534;
}
.batch-file-badge.status-failed {
  background: #fee2e2;
  color: #b91c1c;
}

/* Additional lifecycle states (completed_with_errors, timed-out). */
.batch-status-chip.status-completed_with_errors {
  background: #fef9c3;
  color: #854d0e;
}
.batch-file-badge.status-completed_with_errors {
  background: #fef9c3;
  color: #854d0e;
}
.batch-status-chip.status-timed_out,
.batch-status-chip.status-dead_letter {
  background: #fee2e2;
  color: #b91c1c;
}
.batch-file-badge.status-timed_out,
.batch-file-badge.status-dead_letter {
  background: #fee2e2;
  color: #b91c1c;
}

/* Dark-mode overrides for batch chips (the light values above are hardcoded). */
[data-theme="dark"] .batch-status-chip {
  background: var(--accent-light, rgba(99,102,241,0.15));
  color: var(--accent-strong, #818cf8);
}
[data-theme="dark"] .batch-status-chip.status-completed { background: rgba(34,197,94,0.15); color: #4ade80; }
[data-theme="dark"] .batch-status-chip.status-failed,
[data-theme="dark"] .batch-status-chip.status-expired,
[data-theme="dark"] .batch-status-chip.status-cancelled,
[data-theme="dark"] .batch-status-chip.status-timed_out,
[data-theme="dark"] .batch-status-chip.status-dead_letter { background: rgba(239,68,68,0.15); color: #f87171; }
[data-theme="dark"] .batch-status-chip.status-completed_with_errors { background: rgba(245,158,11,0.15); color: #fbbf24; }
[data-theme="dark"] .batch-file-badge { background: var(--surface-soft); color: var(--ink-soft); }
[data-theme="dark"] .batch-file-badge.status-ok { background: rgba(34,197,94,0.15); color: #4ade80; }
[data-theme="dark"] .batch-file-badge.status-error,
[data-theme="dark"] .batch-file-badge.status-rejected,
[data-theme="dark"] .batch-file-badge.status-failed,
[data-theme="dark"] .batch-file-badge.status-timed_out,
[data-theme="dark"] .batch-file-badge.status-dead_letter { background: rgba(239,68,68,0.15); color: #f87171; }
[data-theme="dark"] .batch-file-badge.status-queued,
[data-theme="dark"] .batch-file-badge.status-submitting { background: rgba(99,102,241,0.15); color: #818cf8; }
[data-theme="dark"] .batch-file-badge.status-processing,
[data-theme="dark"] .batch-file-badge.status-in_progress,
[data-theme="dark"] .batch-file-badge.status-completed_with_errors { background: rgba(245,158,11,0.15); color: #fbbf24; }
[data-theme="dark"] .batch-file-badge.status-completed { background: rgba(34,197,94,0.15); color: #4ade80; }
[data-theme="dark"] .batch-file-row { border-color: var(--border); }

/* Batch failure summary (shown in the batch-job-head for terminal failed/timed-out jobs). */
.batch-failure-summary {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--danger, #dc2626);
}
[data-theme="dark"] .batch-failure-summary { color: #f87171; }

/* Inline rename control: pencil button → input → confirm on Enter/blur. */
.batch-rename-ctl {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.batch-rename-ctl .batch-title-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}
.batch-rename-btn {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 2px 4px;
  cursor: pointer;
  color: var(--ink-soft);
  border-radius: 4px;
  line-height: 1;
  font-size: var(--text-base);
  opacity: 0;
  transition: opacity 0.15s;
}
.batch-rename-ctl:hover .batch-rename-btn,
.batch-rename-btn:focus-visible { opacity: 1; }
.batch-rename-btn:hover { color: var(--accent); background: var(--surface-soft); }
.batch-rename-input {
  font-size: var(--text-lg, 1rem);
  font-weight: 600;
  border: 1px solid var(--accent);
  border-radius: 6px;
  padding: 2px 8px;
  background: var(--surface);
  color: var(--ink);
  width: 280px;
  max-width: 100%;
}
.batch-rename-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.batch-rename-input.hidden { display: none; }
.batch-title-text.hidden { display: none; }
.batch-rename-btn.hidden { display: none; }

/* Batch export button — sits in the batch-job-head-right area. */
.batch-export-btn {
  font-size: var(--text-sm);
  padding: 0.2rem 0.7rem;
  border-radius: 6px;
  background: var(--surface-soft);
  border: 1px solid var(--border);
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}
.batch-export-btn:hover { background: var(--surface); color: var(--ink); border-color: var(--border); }
.batch-export-btn:disabled,
.batch-export-btn[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* Left-sidebar pending-batch status dot in the Recents secondary line. */
.appnav-batch-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  margin-right: 5px;
  vertical-align: middle;
  background: var(--muted, #667085);
}
/* Two changes, and the second is the load-bearing one.
   1. Themed tokens rather than fixed hexes, so a dot tracks the theme instead
      of being authored once against one rail. The old values did clear the 3:1
      that 1.4.11 asks of a meaningful graphic on both surfaces; the tokens just
      give more headroom where it was thinnest (measured on --surface-sidebar:
      completed 3.30 → 3.77:1 light and 5.38 → 7.79:1 dark, queued 4.47 →
      6.29:1 light). Amber stays a literal — there is no --warning token.
   2. Exhaustiveness. The map listed four statuses out of the eleven the store
      and worker can produce, so everything else fell through to the grey
      default while the text beside it said something entirely different — an
      expired batch reading "Failed" next to a neutral dot. Keep this list in
      step with BATCH_LABELS in templates/_nav.html: a status named there and
      missing here is exactly that bug coming back. */
.appnav-batch-dot.status-queued,
.appnav-batch-dot.status-submitting { background: var(--accent); }
.appnav-batch-dot.status-processing,
.appnav-batch-dot.status-in_progress,
.appnav-batch-dot.status-completed_with_errors { background: #d97706; }
.appnav-batch-dot.status-failed,
.appnav-batch-dot.status-expired,
.appnav-batch-dot.status-dead_letter,
.appnav-batch-dot.status-timed_out { background: var(--danger); }
/* Neutral on purpose, and stated rather than inherited from the grey default:
   a cancelled batch is a user's own decision, not a failure to flag. */
.appnav-batch-dot.status-cancelled { background: var(--ink-soft); }
.appnav-batch-dot.status-completed { background: var(--success); }

/* ============================================================
   Mobile UX cleanup (2026-06-22)
   Issues: floating FABs, doc-type selector, upload screen,
   receipt summary hierarchy, compact line items, nested cards.
   Desktop styles are intentionally left untouched — every change
   below is either a new element or guarded by a mobile media query.
   ============================================================ */

/* Issue 5: keep the demo badge from competing with the document type — a muted,
   theme-aware neutral pill instead of a saturated fill. */
.extraction-chip-demo {
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-weight: 600;
}
[data-theme="dark"] .extraction-chip-demo {
  background: var(--surface-soft);
  color: var(--ink-soft);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .extraction-chip-demo {
    background: var(--surface-soft);
    color: var(--ink-soft);
  }
}

/* ---- Doc-type selector: one labelled select at every width ---------------- */
.doc-type-select-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  min-width: 0;
  text-align: left;
}
.doc-type-select-label {
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}
.doc-type-select {
  width: 100%;
  min-height: 44px;
  padding: 10px var(--space-3);
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-input);
  color: var(--ink);
  font-size: var(--text-base);
  font-weight: 600;
}
.doc-type-select:disabled { opacity: 0.5; cursor: not-allowed; }
.doc-type-select-hint {
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--ink-soft);
  margin-top: -2px; /* input → helper reads at ~6px (cell gap is 8px) */
}

/* ---- Issue 6: compact receipt line items (shown on phones only) ----------- */
.line-items-compact { display: none; }

/* ---- Issue 1: utility nav items (How it works / Cookie settings) ----------
   Injected by tour.js & consent.js. Desktop keeps the floating pills, so these
   stay hidden there and only surface inside the hamburger menu on phones. */
.nav-utility-item { display: none; }

/* ===================== Phones / small tablets ≤700px ===================== */
@media (max-width: 700px) {
  /* Issue 1: the floating Guide + Privacy-settings pills overlapped page
     content and the iOS Safari bottom bar. Hide them and rely on the hamburger
     menu entries instead — nothing floats over the content column anymore. */
  #tour-guide-btn,
  #tour-menu,
  .consent-fab { display: none !important; }
  .top-bar-nav .nav-utility-item { display: flex; }

  /* With the FABs gone, the page no longer needs to reserve a floating-button
     gutter at the very bottom. */
  .site-footer { padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom)); }

  /* Issue 9: flatten the double-framed panels inside the review tabs. The tab
     bar already separates Fields / Preview / Details, so the inner card frame is
     redundant nesting — drop the border/background and lean on spacing instead. */
  .review-body .card-panel {
    border: 0;
    background: transparent;
    border-radius: 0;
  }
  .review-body .card-panel > .card-panel-summary { padding: var(--space-3) 0; }
  .review-body .card-panel-body { padding: var(--space-3) 0 0; }

  /* Advanced options: the two-column form grid folds to one column on phones
     and narrow tablets — every field stacks full width. */
  .advanced-options-body { grid-template-columns: minmax(0, 1fr); }
}

/* ===================== Phones ≤560px ===================== */
@media (max-width: 560px) {
  /* Shorter, action-oriented upload area. Drag-and-drop isn't the primary
     gesture on phones, so trim the height; the stacked Browse/camera buttons
     are the default layout at every width now. */
  .dropzone-hero {
    padding: var(--space-6);
    gap: var(--space-2);
    max-width: 100%;
  }
  .dropzone-title { font-size: var(--text-lg); font-weight: 700; }
  .dropzone-sub { margin-top: 0; }
  .dropzone-actions { max-width: 100%; }
  .dropzone-formats { font-size: var(--text-xs); }
  /* The create-account line is a touch target here, not just a text line. */
  .dropzone-signup-line { min-height: 44px; }
  /* Narrower side padding only — the vertical padding from the base rule has
     to stay, because this is exactly where the two-line row wraps to three. */
  .try-example-link { padding: var(--space-3) var(--space-4); }

  /* Inside the advanced disclosure: tighter padding; controls are already
     full-width 44px-tall at every width. Manage templates becomes a real
     touch target here. */
  .advanced-options-summary { padding: var(--space-2) var(--space-4); }
  .advanced-options-body { padding: var(--space-4); }
  .tpl-manage-link { min-height: 44px; }

  /* Issue 6: receipt line items as compact rows — description + right-aligned
     amount, with quantity / unit price / VAT as a secondary line. The full
     read-only table is hidden here; edit mode still uses the table. */
  .table-inline-wrap.read-only.has-compact { display: none; }
  .line-items-compact {
    display: grid;
    gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .li-compact-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-3);
    row-gap: 2px;
    padding: 9px 0;
    border-bottom: 1px solid var(--border-light);
  }
  .li-compact-row:first-child { padding-top: 2px; }
  .li-compact-row:last-child { border-bottom: 0; padding-bottom: 2px; }
  .li-compact-desc {
    font-weight: 600;
    color: var(--ink);
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .li-compact-amount {
    font-weight: 700;
    color: var(--ink);
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
  }
  .li-compact-meta {
    grid-column: 1 / -1;
    font-size: var(--text-sm);
    color: var(--ink-soft);
  }
}

/* ── Extraction options (region + template pickers) on the Analyze form ──────
   Each .analyze-opt is one field cell in the advanced-options grid; controls
   share the .doc-type-select metrics so both grid columns align. */
.analyze-opt {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  text-align: left;
}
/* The author display:flex above would otherwise defeat the [hidden] attribute. */
.analyze-opt[hidden] { display: none; }
.analyze-opt-label {
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}
/* Was unstyled (fell back to full-size body text) — every existing caller
   is a secondary hint under a label/control, so give it hint styling.
   Margin is deliberately NOT set here: the class is also worn by the inline
   status spans inside .custom-extract-actions (a centred flex row), where any
   margin-top would drop them off the button's cross-axis centre. */
.analyze-opt-hint {
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
/* Block hints only: kills the 1em <p> default and sets the label→hint gap. */
p.analyze-opt-hint { margin: 4px 0 0; }
.analyze-opt-select {
  width: 100%;
  min-height: 44px;
  padding: 10px var(--space-3);
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-input);
  color: var(--ink);
  font-size: var(--text-base);
}
.analyze-opt-select:disabled { opacity: 0.5; cursor: not-allowed; }
.analyze-opt-link {
  background: none;
  border: none;
  color: var(--accent);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  padding: var(--space-2) 2px;
}
.analyze-opt-link:hover { color: var(--accent-strong); text-decoration: underline; }
/* "Manage templates": a low-emphasis text action sitting directly beneath the
   Template select (same grid cell), so template upkeep stays next to the
   control it affects without competing with it. */
.tpl-manage-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  padding: var(--space-1) 2px;
  margin-top: -2px; /* control → helper action reads at ~6px, like the hints */
}
.tpl-manage-link:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
/* inline-flex above would defeat the [hidden] attribute toggled in JS. */
.tpl-manage-link[hidden] { display: none; }
/* Precedence hint shown when a template is selected (overrides type/region). */
.analyze-opt-note {
  width: 100%;
  text-align: center;
  font-size: var(--text-xs);
  color: var(--accent-strong);
  margin: -8px 0 14px;
}
.analyze-opt-note[hidden] { display: none; }

/* Provenance badge on the results cockpit. */
/* ── Templates manager dialog ──────────────────────────────────────────────── */
.tpl-modal {
  border: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 0;
  width: min(680px, 96vw);
  max-height: 90vh;
  overflow-y: auto;
  background: var(--surface);
  color: var(--ink);
}
.tpl-modal::backdrop { background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(2px); }
.tpl-modal-card { display: flex; flex-direction: column; }
.tpl-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px var(--space-5) 14px;
  border-bottom: 1px solid var(--border-light);
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}
.tpl-modal-title { font-size: var(--text-lg); font-weight: 700; margin: 0; }
.tpl-modal-close {
  background: none;
  border: none;
  cursor: pointer;
  font-size: var(--text-3xl);
  line-height: 1;
  padding: 2px var(--space-2);
  border-radius: 6px;
  color: var(--ink-soft);
}
.tpl-modal-close:hover { background: var(--surface-soft); color: var(--ink); }
.tpl-view { padding: 18px var(--space-5) var(--space-5); display: flex; flex-direction: column; gap: 14px; }

/* Scope tabs: personal vs workspace templates (separate endpoints, so a real
   switch rather than a filter). Hidden entirely with no active workspace. */
.tpl-scope-tabs {
  display: inline-flex;
  gap: var(--space-1);
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-soft);
  align-self: flex-start;
}
.tpl-scope-tabs[hidden] { display: none; }
.tpl-scope-tab {
  padding: 5px 14px;
  border: none;
  border-radius: 999px;
  background: none;
  color: var(--ink-soft);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
.tpl-scope-tab:hover { color: var(--ink); }
.tpl-scope-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tpl-scope-tab.is-active {
  background: var(--surface);
  border: 1px solid var(--accent);
  color: var(--accent);
  font-weight: 600;
  padding: 4px 13px;
}

.tpl-search-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-input);
}
.tpl-search-row[hidden] { display: none; }
.tpl-search-icon { flex: 0 0 auto; color: var(--ink-soft); }
.tpl-search-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 0;
  border: none;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--text-md);
}
.tpl-search-input:focus { outline: none; }
.tpl-search-row:focus-within { border-color: var(--accent); }
.tpl-list:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Per-row overflow menu — keeps three actions off the row until asked for. */
.tpl-row-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.tpl-row-menu-btn:hover { background: var(--surface-soft); border-color: var(--border); color: var(--ink); }
.tpl-row-menu-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.tpl-row-actions { position: relative; }
.tpl-row-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 5;
  min-width: 150px;
  display: flex;
  flex-direction: column;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
.tpl-row-menu[hidden] { display: none; }
.tpl-menu-item {
  padding: 7px 10px;
  border: none;
  border-radius: 7px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.tpl-menu-item:hover { background: var(--surface-soft); }
.tpl-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tpl-menu-item.is-danger { color: var(--danger); }

/* Creation-method selector inside the editor. */
.tpl-method-block { display: flex; flex-direction: column; gap: var(--space-2); }
.tpl-method-block[hidden] { display: none; }
.tpl-method-label { font-size: var(--text-xs); font-weight: 600; color: var(--ink-soft); }
.tpl-method-selector {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-2);
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-soft);
}
.tpl-method {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 9px 11px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: none;
  color: var(--ink-soft);
  font: inherit;
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.tpl-method:hover { background: var(--surface); color: var(--ink); }
.tpl-method:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tpl-method.is-active {
  background: var(--surface);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}
.tpl-method-icon { display: inline-flex; flex: 0 0 auto; }
.tpl-label-optional { font-weight: 400; color: var(--ink-soft); }
.tpl-fields-count { color: var(--ink-soft); font-weight: 500; }
.tpl-fields-empty {
  margin: 0;
  padding: var(--space-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  text-align: center;
}
.tpl-fields-empty[hidden] { display: none; }
.tpl-empty-title { margin: var(--space-1) 0 0; font-size: var(--text-lg); font-weight: 600; color: var(--ink); }
.tpl-empty-note { margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--ink-soft); }
.tpl-empty-search { padding: var(--space-5) var(--space-2); }
.tpl-list-footer[hidden] { display: none; }
/* display:flex above defeats [hidden]; restore the toggle for list/editor swap. */
.tpl-view[hidden] { display: none; }
.tpl-hint { font-size: var(--text-md); color: var(--ink-soft); margin: 0; }
/* Empty state: icon, reason, one action. Column-flex and vertically padded so
   it reads as a deliberate state rather than a large blank area. */
.tpl-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-md);
  color: var(--ink-soft);
  padding: var(--space-8) var(--space-4);
  text-align: center;
}

.tpl-list { display: flex; flex-direction: column; gap: var(--space-2); }
.tpl-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px var(--space-3);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.tpl-row { align-items: flex-start; }
.tpl-row-main { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.tpl-row-titleline { display: flex; align-items: center; gap: var(--space-2); }
.tpl-row-name { font-weight: 600; font-size: var(--text-base); }
.tpl-row-desc { font-size: var(--text-sm); color: var(--ink-soft); }
.tpl-row-meta { font-size: var(--text-xs); color: var(--ink-soft); }
.tpl-row-actions { display: flex; gap: var(--space-2); flex-shrink: 0; align-items: center; }
.tpl-badge {
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--accent-strong);
  background: var(--accent-light);
  border-radius: 999px;
  padding: 1px var(--space-2);
}
.tpl-chips { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: 2px; }
.tpl-chip {
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  background: var(--surface-soft);
  border: 1px solid var(--border-light);
  border-radius: 6px;
  padding: 1px 7px;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tpl-chip-more { font-weight: 600; color: var(--ink); }
.tpl-empty-hint { font-size: var(--text-sm); margin-top: var(--space-1); }
.tpl-list-footer { display: flex; justify-content: flex-end; }

.tpl-field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.tpl-label { display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--text-xs); font-weight: 600; color: var(--ink-soft); }
/* AI-interaction hint (EU AI Act Art. 50(1)) — lighter weight so it reads as
   a secondary note under the label above it, not a second label. */
/* Nested inside .tpl-label, so it must opt OUT of the .analyze-opt-hint base
   font-size — otherwise the hint renders larger than its own label. Spacing
   is left to .tpl-label's flex gap. */
.tpl-describe-ai-hint { font-weight: 400; font-size: inherit; }
.tpl-label input,
.tpl-label select {
  padding: var(--space-2) 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 400;
}
.tpl-fields-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  border-top: 1px solid var(--border-light);
  padding-top: var(--space-3);
}
.tpl-help { font-size: var(--text-2xs); font-weight: 400; color: var(--ink-soft); margin: 0; line-height: 1.45; }
.tpl-help strong { color: var(--ink); font-weight: 600; }
.tpl-help em { font-style: normal; font-weight: 600; }
.tpl-fields-legend { margin-top: -6px; }
.tpl-fields { display: flex; flex-direction: column; gap: var(--space-2); }
.tpl-field-row {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) 0.9fr 1fr minmax(0, 2fr) auto;
  gap: var(--space-2);
  align-items: center;
}
.tpl-field-row .tpl-f-cols { grid-column: 1 / -2; }
.tpl-field-row input,
.tpl-field-row select {
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--text-sm);
  min-width: 0;
}
.tpl-f-remove {
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: var(--text-xl);
  line-height: 1;
  padding: var(--space-1) 9px;
}
.tpl-f-remove:hover { background: var(--surface-soft); color: var(--ink); }
.tpl-error { color: #dc2626; font-size: var(--text-sm); margin: 0; }
.tpl-editor-footer { display: flex; justify-content: flex-end; gap: 10px; padding-top: var(--space-1); }

@media (max-width: 640px) {
  .tpl-field-grid { grid-template-columns: 1fr; }
  .tpl-field-row { grid-template-columns: 1fr 1fr; }
  .tpl-field-row .tpl-f-desc,
  .tpl-field-row .tpl-f-cols { grid-column: 1 / -1; }
}

/* ===================== Shared page patterns ===================== */
/* Signed-out prompt used by history, profile, analytics, workspace. */
.login-prompt {
  text-align: left;
  padding: var(--space-8) 0;
  max-width: 560px;
}
.login-prompt h2 {
  font-size: var(--text-3xl);
  margin-bottom: 0.5rem;
}
.login-prompt p { color: var(--muted); margin-bottom: 1.5rem; }
.login-prompt a {
  display: inline-block;
  padding: 0.625rem 1.5rem;
  background: var(--accent-cta);
  color: #fff;
  border-radius: 8px;
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-md);
}

/* ===================== Command palette (Ctrl/Cmd+K) ===================== */
/* Sidebar trigger — quiet, input-shaped. */
.cmdk-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 36px;
  margin-top: var(--space-4);
  padding: 7px var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-input);
  color: var(--ink-soft);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.cmdk-trigger:hover { border-color: var(--accent); color: var(--ink); }
.cmdk-trigger-kbd,
.cmdk-foot kbd {
  margin-left: auto;
  padding: 1px 5px;
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  background: var(--surface-soft);
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  white-space: nowrap;
}
.cmdk-foot kbd { margin-left: 0; }

/* Overlay above every drawer/modal (max app z-index is 9999 on toasts). */
.cmdk-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.42);
  z-index: 10000;
}
.cmdk {
  position: fixed;
  inset: 0;
  z-index: 10001;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh var(--space-4) 0;
  pointer-events: none;
}
.cmdk-panel {
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  width: min(560px, 100%);
  max-height: min(430px, 76vh);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: cmdk-in 0.12s ease-out;
}
@keyframes cmdk-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.99); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .cmdk-panel { animation: none; }
}
.cmdk-input {
  border: none;
  border-bottom: 1px solid var(--border-light);
  border-radius: 0;
  background: transparent;
  padding: var(--space-4);
  font-size: var(--text-lg);
  color: var(--ink);
  outline: none;
}
.cmdk-input::placeholder { color: var(--ink-soft); }
.cmdk-list {
  overflow-y: auto;
  padding: var(--space-2);
  overscroll-behavior: contain;
}
.cmdk-section {
  padding: var(--space-2) var(--space-2) var(--space-1);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.cmdk-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 9px var(--space-2);
  border-radius: 8px;
  font-size: var(--text-md);
  color: var(--ink);
  cursor: pointer;
}
.cmdk-item[aria-selected="true"] { background: var(--accent-light); }
.cmdk-item-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cmdk-item-sub {
  margin-left: auto;
  flex-shrink: 0;
  color: var(--ink-soft);
  font-size: var(--text-xs);
}
.cmdk-empty {
  padding: var(--space-6);
  text-align: center;
  color: var(--ink-soft);
  font-size: var(--text-md);
}
.cmdk-foot {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--border-light);
  color: var(--ink-soft);
  font-size: var(--text-2xs);
}
.cmdk-foot span { display: inline-flex; align-items: center; gap: var(--space-1); }

/* ============================================================
   Phase B — Workflow review semantics
   ============================================================ */

/* ── Workflow stepper (cockpit) ─────────────────────────────────────────── */
/* Workflow status renders as a single compact badge (.wf-badge, defined with
   the cockpit badge styles) — the two-stage stepper was retired with it. */

/* Attribution line for the primary workflow status (also used by the
   #workflow-attribution line under the cockpit badges). */
.wf-reviewer {
  width: 100%;
  margin: 4px 0 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
#workflow-attribution[hidden] { display: none; }

/* ── Completion-blocked popover ─────────────────────────────────────────── */
/* Positioned absolutely inside .cockpit-actions (which is position:relative). */
.cockpit-actions { position: relative; }
.completion-blocked-popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 200;
  width: 360px;
  max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
}
/* [hidden] — no author display override on the popover so the attribute wins. */
#completion-blocked-popover[hidden] { display: none; }
/* §10/§13: on phones `right:0` anchors to .cockpit-actions, whose right edge sits
   past the viewport — the popover measured 9px off-screen at 390px. Pin it to the
   viewport instead, matching the ⋯ menu's bottom-sheet treatment so the two
   overlays behave identically on mobile. */
@media (max-width: 900px) {
  .completion-blocked-popover {
    position: fixed;
    top: auto;
    left: var(--space-2);
    right: var(--space-2);
    bottom: calc(var(--space-2) + env(safe-area-inset-bottom));
    width: auto;
    max-width: none;
  }
}
.cbp-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--accent-warm);
}
.cbp-body {
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--ink);
}
.cbp-intro {
  margin: 0 0 var(--space-2);
}
.cbp-fields-list {
  margin: 0 0 var(--space-2);
  padding-left: var(--space-5);
  font-size: var(--text-sm);
}
.cbp-more {
  margin: 0;
  color: var(--ink-soft);
  font-size: var(--text-xs);
}
.cbp-truncation {
  margin: var(--space-2) 0 0;
  color: var(--accent-warm);
  font-size: var(--text-sm);
}
.cbp-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}
.cbp-review-btn { font-size: var(--text-sm); }

/* On narrow viewports the popover should not overflow left of the screen. */
@media (max-width: 500px) {
  .completion-blocked-popover {
    right: auto;
    left: 0;
  }
}

/* ── Incomplete-analysis banner ─────────────────────────────────────────── */
.incomplete-analysis-banner {
  margin-bottom: var(--space-4);
  border-radius: var(--radius);
  border: 1px solid;
  overflow: hidden;
}
#incomplete-analysis-banner[hidden] { display: none; }
.incomplete-analysis-banner.is-blocking {
  background: rgba(220, 38, 38, 0.06);
  border-color: rgba(220, 38, 38, 0.3);
}
.incomplete-analysis-banner.is-accepted {
  background: rgba(217, 119, 6, 0.06);
  border-color: rgba(217, 119, 6, 0.3);
}
.iab-content {
  padding: var(--space-3) var(--space-4);
}
.iab-title {
  margin: 0 0 4px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--danger);
}
.incomplete-analysis-banner.is-accepted .iab-title { color: var(--accent-warm); }
.iab-body {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink);
}
.iab-footer {
  margin-top: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.iab-meta {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.iab-accept-btn { font-size: var(--text-sm); }

/* ── Generic-document-type banner ───────────────────────────────────────── */
.generic-type-banner {
  margin-bottom: var(--space-4);
  border-radius: var(--radius);
  border: 1px solid;
  overflow: hidden;
  background: var(--accent-light);
  border-color: var(--accent);
}
#generic-type-banner[hidden] { display: none; }
.gtb-content {
  padding: var(--space-3) var(--space-4);
}
.gtb-title {
  margin: 0 0 4px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--accent-strong);
}
.gtb-body {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink);
}
.gtb-footer {
  margin-top: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.gtb-cta-btn { font-size: var(--text-sm); }

/* ── Review-locked fields editor ────────────────────────────────────────── */
/* When review is complete (.is-review-locked set by updateCockpitActions),
   hide per-field action controls so the fields are read-only. */
#fields-editor.is-review-locked .field-confirm-btn,
#fields-editor.is-review-locked .field-confirm-na-btn,
#fields-editor.is-review-locked .field-edit-btn,
#fields-editor.is-review-locked .add-custom-field-btn { display: none; }
/* Also hide the line-items editor controls */
#line-items-editor.is-review-locked .field-confirm-btn,
#line-items-editor.is-review-locked .field-edit-btn,
#line-items-editor.is-review-locked .field-needs-correction-btn,
#line-items-editor.is-review-locked .flag-line-item-row-btn { display: none; }

/* ════════════════════════════════════════════════════════════════════════
   Phase C — Approval UI + Audit History Panel
   ════════════════════════════════════════════════════════════════════════ */

/* ── Danger ghost button (outline/ghost; used for Reject) ────────────────── */
.danger-ghost-btn {
  padding: 10px 16px;
  background: transparent;
  border: 1px solid var(--danger);
  border-radius: 10px;
  color: var(--danger);
  font-size: var(--text-md);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  white-space: nowrap;
}
.danger-ghost-btn:hover { background: var(--danger); color: #fff; }
/* [hidden] must not be overridden — no display: rule on this selector */

/* ── Danger confirm button (solid red; inside decision dialog) ───────────── */
.danger-confirm-btn {
  padding: 10px 16px;
  background: var(--danger);
  border: 1px solid var(--danger);
  border-radius: 10px;
  color: #fff;
  font-size: var(--text-md);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.danger-confirm-btn:hover { background: #b91c1c; border-color: #b91c1c; }
.danger-confirm-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* ── Workflow decision modal ─────────────────────────────────────────────── */
.wdm-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}
.wdm-overlay[hidden] { display: none; }
.wdm-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  width: 100%;
  max-width: 480px;
}
.wdm-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--ink);
}
.wdm-body {
  margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  line-height: 1.5;
}
.wdm-field { margin-bottom: var(--space-4); }
.wdm-label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}
.wdm-required-hint {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  color: var(--accent-warm);
}
.wdm-required-hint[hidden] { display: none; }
.wdm-textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-input);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--ink);
  font-size: var(--text-sm);
  font-family: inherit;
  resize: vertical;
  min-height: 88px;
}
.wdm-textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
.wdm-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* ── Approval-decision banner ────────────────────────────────────────────── */
.approval-banner {
  margin-bottom: var(--space-4);
  border-radius: var(--radius);
  border: 1px solid;
  overflow: hidden;
}
#approval-banner[hidden] { display: none; }
.approval-banner.is-changes-requested {
  background: rgba(217, 119, 6, 0.06);
  border-color: rgba(217, 119, 6, 0.3);
}
.approval-banner.is-rejected {
  background: rgba(220, 38, 38, 0.06);
  border-color: rgba(220, 38, 38, 0.3);
}
.approval-banner-inner { padding: var(--space-2) var(--space-3); }
.ab-title {
  margin: 0 0 4px;
  font-size: var(--text-sm);
  font-weight: 600;
}
.approval-banner.is-changes-requested .ab-title { color: var(--accent-warm); }
.approval-banner.is-rejected .ab-title { color: var(--danger); }
.ab-comment {
  margin: var(--space-2) 0;
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid;
  font-size: var(--text-sm);
  color: var(--ink);
  font-style: italic;
  background: rgba(0, 0, 0, 0.03);
  border-radius: 0 4px 4px 0;
}
[data-theme="dark"] .ab-comment { background: rgba(255, 255, 255, 0.04); }
.approval-banner.is-changes-requested .ab-comment { border-color: var(--accent-warm); }
.approval-banner.is-rejected .ab-comment { border-color: var(--danger); }
.ab-meta {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  line-height: 1.4;
}
.ab-fix-btn {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
}

/* ── Extended reviewer/decision attribution lines ────────────────────────── */
.wf-reviewer-ok  { color: var(--success-text); }
.wf-reviewer-bad { color: var(--danger); }

/* ── Revision lineage banner ─────────────────────────────────────────────── */
/* Subtle single-line bar below the approval banner that shows parent/child links. */
.record-lineage {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
#record-lineage[hidden] { display: none; }
.lineage-text { color: var(--ink-soft); }
.lineage-link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.lineage-link:hover { color: var(--accent-dark, var(--accent)); }
.lineage-sep {
  color: var(--border);
  padding: 0 var(--space-1);
}
.lineage-action {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm, 4px);
  color: var(--ink);
  cursor: pointer;
  font-size: var(--text-xs);
  padding: 2px var(--space-2);
  line-height: 1.4;
}
.lineage-action:hover { background: var(--surface-hover); }

/* ── Bank-detail-change alert banner ────────────────────────────────────── */
/* Warn-only: amber/warm tone so it reads as "please verify" not "hard error". */
.bank-change-alert-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border-radius: var(--radius);
  border: 1px solid rgba(217, 119, 6, 0.35);
  background: rgba(217, 119, 6, 0.07);
}
/* The [hidden] guard prevents a grid/flex parent's display rule from overriding
   the browser attribute (see CSS hidden-utility gotcha: 6 production bugs). */
#bank-change-alert-banner[hidden] { display: none; }
.bca-icon {
  font-size: 1.2rem;
  line-height: 1;
  color: var(--accent-warm, #d97706);
  flex-shrink: 0;
  margin-top: 1px;
}
.bca-content { flex: 1; min-width: 0; }
.bca-title {
  margin: 0 0 4px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--accent-warm, #d97706);
}
.bca-body {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink);
  /* Long unbroken tokens (IBANs, tax ids) must still break at overflow,
     but prose must never break mid-word — so not word-break: break-all. */
  overflow-wrap: anywhere;
}

/* ── Revision diff modal ─────────────────────────────────────────────────── */
.rdm-box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  max-width: 780px;
  width: 95vw;
  max-height: 80vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.rdm-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--border-light);
}
.rdm-title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--ink);
}
.rdm-body {
  overflow-y: auto;
  padding: var(--space-4);
  flex: 1;
}
.rdm-loading,
.rdm-error { color: var(--ink-soft); font-size: var(--text-sm); }
.rdm-error { color: var(--danger); }

/* ── Vendor learning loop: supplier-notes chip + modal rows ────────────────
   The modal reuses the rdm-box/rdm-header/rdm-body chrome above. */
.vendor-hints-chip {
  background: var(--accent-light);
  color: var(--accent);
  border: none;
  cursor: pointer;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
}
.vendor-hints-chip:hover { filter: brightness(0.95); }
.vhm-vendor {
  font-size: var(--text-base);
  font-weight: 700;
  margin: var(--space-4) 0 var(--space-2);
}
.vhm-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-light);
  font-size: var(--text-sm);
}
.vhm-row-text { min-width: 0; overflow-wrap: anywhere; }
.vhm-del {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 10px;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  cursor: pointer;
  flex-shrink: 0;
}
.vhm-del:hover { color: var(--danger); border-color: var(--danger); }

/* ── Pre-analysis split/merge plan (upload card) + split editor modal ────── */
.upload-plan {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-light);
  border-radius: 10px;
  background: var(--surface-soft);
  display: grid;
  gap: var(--space-2);
  text-align: left;
}
.upload-plan[hidden],
.upload-plan.hidden,
.upload-plan:empty { display: none; }
.upload-plan-note {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--accent);
}
.upload-plan-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  cursor: pointer;
}
.upload-plan-option input { accent-color: var(--accent); }
.upload-plan-option .text-link-btn { margin-left: var(--space-2); font-size: var(--text-xs); }
/* Price of each plan option — the split-vs-whole comparison the card exists
   for, so it right-aligns into a scannable column. */
.upload-plan-cost {
  margin-left: auto;
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  white-space: nowrap;
}
.upload-plan-note.is-muted {
  font-weight: 400;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.sem-box { max-width: 860px; width: min(860px, 94vw); }
.sem-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.sem-page {
  position: relative;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  background: var(--surface);
}
.sem-page img { display: block; width: 96px; height: auto; }
.sem-page-num {
  position: absolute;
  bottom: 2px;
  right: 4px;
  font-size: var(--text-2xs);
  font-weight: 700;
  color: var(--ink-soft);
  background: var(--surface);
  border-radius: 4px;
  padding: 0 4px;
}
.sem-cut {
  border: 1px dashed var(--border);
  background: none;
  border-radius: 999px;
  width: 30px;
  height: 30px;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: var(--text-sm);
  flex-shrink: 0;
}
.sem-cut:hover { border-color: var(--accent); color: var(--accent); }
.sem-cut.is-active {
  border-style: solid;
  border-color: var(--accent);
  background: var(--accent-light);
  color: var(--accent);
}
.sem-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-light);
}
.sem-summary { margin-right: auto; font-size: var(--text-sm); color: var(--ink-soft); }
.rdm-summary {
  font-size: var(--text-sm);
  margin: 0 0 var(--space-3);
  color: var(--ink-soft);
}
.rdm-summary.rdm-same { color: var(--success-text); }
.rdm-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.rdm-table th,
.rdm-table td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.rdm-table th {
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--surface-alt);
  position: sticky;
  top: 0;
}
.rdm-field { color: var(--ink-soft); white-space: nowrap; }
.rdm-val { word-break: break-word; }
.rdm-row-changed { background: rgba(251, 191, 36, 0.06); }
.rdm-removed { color: var(--danger); text-decoration: line-through; opacity: 0.8; }
.rdm-added { color: var(--success-text); }
.rdm-status { white-space: nowrap; color: var(--ink-soft); }

/* ── Revision history-list badge ─────────────────────────────────────────── */
.revision-badge {
  background: rgba(99, 102, 241, 0.12);
  color: #6366f1;
  border: 1px solid rgba(99, 102, 241, 0.25);
  font-size: var(--text-xs);
}

/* ── Approved state: hide confirm-all-btn entirely ───────────────────────── */
/* Approved documents cannot be reopened in-place; the server returns 400.
   !important overrides the higher-specificity .is-review-complete show rule. */
.results.is-approved #confirm-all-btn { display: none !important; }

/* ── has-approval-primary: approval submit is the one purple primary ──────── */
/* Phase B's updateCockpitActions already makes confirm-all secondary when
   review_complete, so this is a safety anchor. */
.cockpit-actions.has-approval-primary #confirm-all-btn.primary-btn {
  background: var(--surface);
  border-color: var(--border);
  color: var(--ink);
  font-weight: 500;
}

/* ── Review & approval history panel ────────────────────────────────────── */
/* A shared bottom section since §1 — same card treatment as Line items, sitting
   between Classification details and Technical details. */
.workflow-history-panel { margin: 0 0 var(--space-3); }
#workflow-history-panel[hidden] { display: none; }
/* .card-panel-body already sets padding; workflow-history-body is an extra hook only */
.wh-loading {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  padding: var(--space-2) 0;
}
.wh-empty {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  padding: var(--space-2) 0;
  margin: 0;
}
.workflow-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
}
.wh-item {
  display: flex;
  gap: var(--space-3);
  position: relative;
  padding-bottom: var(--space-4);
}
/* Connector line between dots */
.wh-item:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 15px;
  bottom: 0;
  width: 1px;
  background: var(--border-light);
}
.wh-dot {
  flex-shrink: 0;
  width: 13px;
  height: 13px;
  margin-top: 2px;
  border-radius: 50%;
}
.wh-dot-success { background: var(--success); }
.wh-dot-accent  { background: var(--accent); }
.wh-dot-warn    { background: var(--accent-warm); }
.wh-dot-danger  { background: var(--danger); }
.wh-dot-muted   { background: var(--border); outline: 1px solid var(--border); }
.wh-content { flex: 1; min-width: 0; }
.wh-label {
  margin: 0 0 2px;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink);
}
.wh-comment {
  margin: var(--space-1) 0;
  padding: var(--space-1) var(--space-2);
  border-left: 2px solid var(--border);
  font-size: var(--text-xs);
  color: var(--ink-soft);
  font-style: italic;
  background: var(--surface-soft);
  border-radius: 0 4px 4px 0;
}
.wh-payload {
  margin: 2px 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.wh-time {
  display: block;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin-top: 2px;
}

/* ── Full activity log (§9) ────────────────────────────────────────────────
   The default timeline lists significant workflow decisions only; the complete
   trail — per-field verifications, value edits, blocked completions — sits
   behind this disclosure. Nothing is dropped, only deferred. */
.wh-view-all { margin-top: var(--space-1); }
.wh-full-activity {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-light);
}
.wh-full-heading {
  margin: 0 0 var(--space-3);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
/* The full log is the long one — cap it and let it scroll inside the panel
   instead of pushing Technical details off the bottom of the page. */
.workflow-timeline-full {
  max-height: 420px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Field-level rows are quieter than workflow decisions. */
.workflow-timeline-full .wh-item { padding-bottom: var(--space-3); }

/* ── Keyboard field-review navigation ─────────────────────────────────────── */

/* Active-card outline (j/k/ArrowUp/ArrowDown). Uses the same 2px accent ring as
   :focus-visible so the cue is familiar without competing with hover styles.
   The class is removed on any mousedown so it never fights pointer interactions. */
.field-card.field-row.is-kbd-active,
.line-items-standalone.is-kbd-active {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
/* Always show action controls on the keyboard-active card — same rule as
   .needs-review (visibility hidden by default, revealed on hover/focus-within). */
.field-card.field-row.is-kbd-active .field-card-controls {
  visibility: visible;
  opacity: 1;
}

/* One-line shortcut hint shown below the field list on desktop review screens.
   Muted and small so it doesn't compete with field content. Hidden on touch/mobile. */
.fields-kbd-hint {
  display: none;
}
html.is-reviewing .fields-kbd-hint {
  display: block;
  margin-top: var(--space-3);
  padding: 0 var(--space-3) var(--space-2);
  font-size: var(--text-2xs);
  /* No opacity: it composited --ink-soft down to ~2.5:1. --ink-soft alone is
     5.17:1 on white and reads as a quiet hint. */
  color: var(--ink-soft);
  text-align: center;
}
@media (max-width: 900px) {
  /* Desktop-only feature: suppress on small screens where touch is primary. */
  html.is-reviewing .fields-kbd-hint { display: none; }
}

/* ── Duplicate-document alert banner ────────────────────────────────────── */
/* Warn: amber tone identical to bank-change-alert for "please verify" weight. */
.dup-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border-radius: var(--radius);
  border: 1px solid rgba(217, 119, 6, 0.35);
  background: rgba(217, 119, 6, 0.07);
}
/* ID-scoped guard: .dup-banner uses display:flex, so the UA [hidden] rule
   would lose to it. This ensures hidden wins (see CSS hide-mechanism gotcha). */
#dup-banner[hidden] { display: none; }
.dup-banner-icon {
  font-size: 1.1rem;
  line-height: 1.3;
  color: var(--accent-warm, #d97706);
  flex-shrink: 0;
}
.dup-banner-body {
  flex: 1;
  min-width: 0;
}
.dup-banner-title {
  margin: 0 0 4px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--warn-text, #b45309);
}
.dup-banner-text {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--ink);
}
.dup-banner-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.dup-banner-view-btn {
  font-size: var(--text-sm);
}
.dup-banner-dismiss {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.dup-banner-dismiss:hover { color: var(--ink); }
[data-theme="dark"] .dup-banner {
  background: rgba(245, 158, 11, 0.1);
  border-color: rgba(245, 158, 11, 0.25);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .dup-banner {
    background: rgba(245, 158, 11, 0.1);
    border-color: rgba(245, 158, 11, 0.25);
  }
}

/* ── Related-documents panel (matches card) ─────────────────────────────── */
/* Sits below the workflow-history panel; uses the shared card-panel shell.  */
.related-docs-panel { margin-top: var(--space-4); }
.related-docs-body { display: flex; flex-direction: column; gap: var(--space-3); }

/* Three-way match summary row */
.rd-three-way {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm, 6px);
  background: rgba(5, 150, 105, 0.07);
  border: 1px solid rgba(5, 150, 105, 0.2);
  font-size: var(--text-sm);
  margin-bottom: var(--space-2);
}
.rd-three-way-label {
  font-weight: 600;
  color: var(--success-text, #047857);
  white-space: nowrap;
}
.rd-three-way-detail { color: var(--ink-soft); font-size: var(--text-xs); }
[data-theme="dark"] .rd-three-way {
  background: rgba(34, 197, 94, 0.08);
  border-color: rgba(34, 197, 94, 0.2);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .rd-three-way {
    background: rgba(34, 197, 94, 0.08);
    border-color: rgba(34, 197, 94, 0.2);
  }
}

/* Individual match entry card */
.rd-match {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.rd-match-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* Confidence chip — extends the extraction-chip visual family.
   Three tones map to high/medium/low match strength. */
.rd-conf-chip {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 2px 8px;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}
.rd-conf-chip.hidden { display: none; }
.rd-conf-chip.is-high {
  background: rgba(5, 150, 105, 0.1);
  color: var(--success-text, #047857);
}
.rd-conf-chip.is-medium {
  background: rgba(217, 119, 6, 0.1);
  color: var(--warn-text, #b45309);
}
.rd-conf-chip.is-low {
  background: rgba(220, 38, 38, 0.08);
  color: var(--danger-text, #b91c1c);
}
[data-theme="dark"] .rd-conf-chip.is-high {
  background: rgba(34, 197, 94, 0.15);
  color: #4ade80;
}
[data-theme="dark"] .rd-conf-chip.is-medium {
  background: rgba(245, 158, 11, 0.15);
  color: #fbbf24;
}
[data-theme="dark"] .rd-conf-chip.is-low {
  background: rgba(239, 68, 68, 0.15);
  color: #f87171;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .rd-conf-chip.is-high {
    background: rgba(34, 197, 94, 0.15);
    color: #4ade80;
  }
  :root:not([data-theme="light"]) .rd-conf-chip.is-medium {
    background: rgba(245, 158, 11, 0.15);
    color: #fbbf24;
  }
  :root:not([data-theme="light"]) .rd-conf-chip.is-low {
    background: rgba(239, 68, 68, 0.15);
    color: #f87171;
  }
}

.rd-match-type {
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--ink);
}
.rd-match-reason {
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.rd-match-meta {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: 0;
}
/* Discrepancy chips list */
.rd-disc-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}
.rd-disc-chip {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 2px 8px;
  font-size: var(--text-xs);
  font-weight: 500;
}
.rd-disc-chip.sev-high {
  background: rgba(220, 38, 38, 0.08);
  color: var(--danger, #dc2626);
}
.rd-disc-chip.sev-medium {
  background: rgba(217, 119, 6, 0.08);
  color: var(--accent-warm, #d97706);
}
.rd-disc-chip.sev-low {
  background: var(--surface-soft);
  color: var(--ink-soft);
}
[data-theme="dark"] .rd-disc-chip.sev-high { background: rgba(239,68,68,0.15); color: #f87171; }
[data-theme="dark"] .rd-disc-chip.sev-medium { background: rgba(245,158,11,0.15); color: #fbbf24; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .rd-disc-chip.sev-high { background: rgba(239,68,68,0.15); color: #f87171; }
  :root:not([data-theme="light"]) .rd-disc-chip.sev-medium { background: rgba(245,158,11,0.15); color: #fbbf24; }
}
.rd-match-ok {
  font-size: var(--text-xs);
  color: var(--success, #059669);
}
.rd-open-link {
  align-self: flex-start;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}
.rd-open-link:hover { color: var(--accent-strong, var(--accent)); }

/* ── Match / Duplicates modal (opened from banner, ⋯ menu, or match card) ── */
.match-modal-backdrop {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  z-index: 9100;
  padding: var(--space-4);
}
/* ID-scoped guard: .match-modal-backdrop uses display:flex. */
#match-modal[hidden] { display: none; }
.match-modal-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 12px);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 560px;
  max-height: 80vh;
  overflow-y: auto;
  padding: var(--space-4) var(--space-5, 20px);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.match-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.match-modal-title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 700;
}
.match-modal-anchor {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.match-modal-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Loading / empty states inside the modal */
.mm-loading,
.mm-empty {
  text-align: center;
  padding: var(--space-5, 20px) 0;
  color: var(--ink-soft);
  font-size: var(--text-sm);
}
/* Match entry card inside the modal (same data shape as .rd-match) */
.mm-match {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.mm-match-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.mm-match-type {
  font-weight: 600;
  font-size: var(--text-sm);
}
.mm-match-reason {
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.mm-match-meta {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: 0;
}
.mm-disc-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}
.mm-open-link {
  align-self: flex-start;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}
.mm-open-link:hover { color: var(--accent-strong, var(--accent)); }
.mm-match-ok {
  font-size: var(--text-xs);
  color: var(--success, #059669);
}
/* Duplicates list in modal */
.mm-dup {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.mm-dup-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.mm-dup-type {
  font-weight: 600;
  font-size: var(--text-sm);
}
.mm-dup-when {
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.mm-dup-meta {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: 0;
}
@media (max-width: 600px) {
  .match-modal-card {
    max-width: 100%;
    max-height: 90vh;
    padding: var(--space-3) var(--space-4);
  }
}

/* ── Canonical entities: review panel + edit modal (2026-07-28) ─────────────
   Panel body reuses the related-docs card chrome; these rules only shape the
   per-role blocks and the compact modal form rows. */
.entity-role-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line-soft, rgba(120, 120, 140, 0.15));
}
.entity-role-block:last-child { border-bottom: 0; }
.entity-role-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.entity-role-label {
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}
.entity-role-badge { font-size: var(--text-xs); color: var(--ink-soft); }
.entity-role-name { font-weight: 600; overflow-wrap: anywhere; }
.entity-role-sub { font-size: var(--text-sm); color: var(--ink-soft); overflow-wrap: anywhere; }
.entity-role-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-1); }
.entity-action-btn { align-self: flex-start; }
.eem-remember-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-direction: row;
}
/* The edit modal stacks its fields; keep it scrollable within small viewports
   (390px check: the .rdm-box chrome already caps width/height). */
#entity-edit-modal .rdm-body { max-height: min(70vh, 34rem); overflow-y: auto; }
/* Must stack ABOVE the cookie-consent banner (.consent-root, z-index 2500):
   on 390px the consent banner otherwise covers the lower form fields, which
   are unreachable until it is dismissed. Id-scoped so the other .modal-overlay
   dialogs keep their existing stacking. */
#entity-edit-modal { z-index: 2600; }

/* One-click "Same entity" offer + rule suggestion chips (2026-07-28).
   Compact inline cards inside a role block; stack vertically on narrow
   screens so the primary action never leaves the viewport. */
.entity-same-box,
.entity-rule-suggest {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line-soft, rgba(120, 120, 140, 0.2));
  border-radius: var(--radius-md, 8px);
  background: var(--surface-raised, rgba(120, 120, 140, 0.06));
}
.entity-same-text { min-width: 0; flex: 1 1 14rem; }
.entity-same-title { font-weight: 600; font-size: var(--text-sm); overflow-wrap: anywhere; }
.entity-same-sub { font-size: var(--text-xs); color: var(--ink-soft); overflow-wrap: anywhere; }
.entity-same-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* Title preview inside the entity edit modal (spec §14). */
.eem-title-preview {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  overflow-wrap: anywhere;
  margin: var(--space-1) 0 0;
}

/* Conflict banner: stacked Known / Detected sections — never a wide
   comparison table (390px rule, spec §15). */
.ecb-section { margin: var(--space-1) 0; }
.ecb-section p { margin: 0; overflow-wrap: anywhere; }
.ecb-section-title {
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
}
.ecb-note { font-size: var(--text-sm); color: var(--ink-soft); }

@media (max-width: 480px) {
  .entity-same-box,
  .entity-rule-suggest { flex-direction: column; align-items: stretch; }
  .entity-same-actions { flex-direction: column; }
  .entity-same-actions .entity-action-btn { width: 100%; }
  /* Conflict actions stack too — three buttons never fit a 390px row. */
  #entity-conflict-banner .ra-modal-actions { flex-direction: column; align-items: stretch; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Custom extraction (2026-07-28): analyze-page proposal editor + review chips
   ═══════════════════════════════════════════════════════════════════════════ */

/* ===================== Analysis mode selector ===================== */
/* The three modes: Automatic / Custom extraction / Saved template. A segmented
   radiogroup — one visible choice, obvious selection, no dropdown to open. */
.mode-selector {
  display: grid;
  /* Four options with Custom extraction on, three with it off: auto-fit lays
     out however many exist, one row where the width allows, wrapping cleanly
     below it (the 640px media block still collapses to a single column). */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-2);
  /* No bottom margin: the "1 Upload document" eyebrow below owns the gap to
     the next section, so exactly one rule decides that distance. */
  margin: 0;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-soft);
}
.mode-option {
  display: flex;
  /* Centred, not flex-start: the icon is a fixed 26px square and the text block
     is one or two lines depending on breakpoint, so top-aligning left the icon
     visibly high against a two-line label. */
  align-items: center;
  gap: var(--space-3);
  min-height: 60px;
  padding: var(--space-3) 14px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  color: var(--ink-soft);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* Hover reveals the border an unselected option is missing, so the pointer
   affordance reads before the click — an unselected option is a control, not a
   disabled label. Its title keeps full --ink contrast (see .mode-option-title);
   only the description is muted, which is hierarchy rather than "off".
   The icon brightens but keeps its NEUTRAL chip: the accent-tinted chip is one
   of the four signals that mean "selected" below, and spending it on hover left
   a hovered option wearing two of the selected option's four cues. It also
   never clears on touch, where :hover sticks after a tap — so the last option
   touched would look chosen indefinitely. Hovering the SELECTED option still
   shows the accent chip: .is-active sets it later at equal specificity. */
.mode-option:hover { background: var(--surface); border-color: var(--border); color: var(--ink); }
.mode-option:hover .mode-option-icon { color: var(--ink); }
.mode-option:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Selected state carries FOUR signals, not colour alone: filled surface, an
   accent border + inset ring, the accent-tinted icon, and a heavier title
   weight — so it survives both themes, greyscale, and colour-blind viewing.
   aria-checked carries it for assistive tech. */
.mode-option.is-active {
  background: var(--accent-light);
  border-color: var(--accent);
  color: var(--ink);
  box-shadow: 0 0 0 1px var(--accent) inset;
}
.mode-option.is-active .mode-option-title { font-weight: 700; }
.mode-option-icon {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--surface-soft);
  color: var(--ink-soft);
}
.mode-option.is-active .mode-option-icon {
  background: var(--accent-light);
  color: var(--accent);
}
.mode-option-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* Always full-contrast, including on unselected options: inheriting the button's
   muted --ink-soft made "Custom extraction" / "Saved template" read as disabled
   next to the selected one. The description below carries the hierarchy instead. */
.mode-option-title { font-size: var(--text-md); font-weight: 600; color: var(--ink); }
.mode-option-desc { font-size: var(--text-xs); color: var(--ink-soft); line-height: 1.35; }
/* Belongs to the selector above it, so it sits in the selector's own rhythm
   (8px) and adds nothing below — the eyebrow's top margin still owns the gap
   to the next section. Was a -12px pull against the selector's old bottom
   margin, which meant two rules had to agree to produce one gap. */
.mode-selector-notice {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
}

/* Mode panels. Explicit class pair rather than the .hidden utility: a later
   display rule can silently defeat .hidden (see the hide-mechanism gotcha),
   while "hidden unless .is-active" cannot regress. */
.analyze-body { display: flex; flex-direction: column; }
.mode-panel { display: none; }
.mode-panel.is-active { display: block; }
/* Flex order, so one authored copy of the step label and the benefits row can
   serve all three modes. The configuring eyebrow leads; whichever panel is
   active follows it. Automatic's panel sits at 3 only so the ordering stays
   readable if a fourth mode is added. */
.step-eyebrow-configure { order: 0; }
#mode-panel-custom,
#mode-panel-template { order: 1; }
#mode-panel-auto { order: 3; }

/* ── Per-mode flow order ──
   The three modes are different flows, not one flow with three labels, so the
   page orders itself to match the one actually selected. #analyze-form is
   already a flex column, and .analyze-upload wraps the whole upload block, so
   the two halves can swap as units.

     Automatic / Custom   upload → body      (pick a file, then options)
     Saved template       body → upload      (pick the template, THEN feed it)

   Saved template really does run template-first: the template decides what is
   extracted, so choosing a file before it is choosing in the dark. The
   trust row is a direct child of the form at order 90 precisely so this swap
   cannot drag it up out of last place. */
/* A flex column, not a plain block: as a block container it would let the
   eyebrow's and dropzone's adjacent margins COLLAPSE, which they do not do
   today as siblings in the form's flex column — every gap on this page has
   exactly one owner, and margin collapsing would silently give two of them
   away. (display: contents would keep the layout identical but is useless
   here: it removes the box, and with it the `order` this wrapper exists for.) */
.analyze-upload { display: flex; flex-direction: column; }

/* EVERY direct child of the form is listed. Once any sibling carries an order,
   an unlisted one keeps the initial `order: 0` and jumps to the FRONT of the
   column — so leaving one out does not nudge it, it teleports it above the
   mode selector. #filename-override is the live example: it is authored last
   and relocated into a mode's slot by mountFilenameControl(), but until that
   call runs it is still a form child, and at order 0 it would flash at the top
   of the page on first paint. */
#mode-selector { order: 0; }
.mode-selector-notice { order: 1; }
.analyze-upload { order: 10; }
.analyze-body { order: 20; }
#analyze-form.is-mode-template .analyze-body { order: 10; }
#analyze-form.is-mode-template .analyze-upload { order: 20; }
.hero-benefits { order: 90; }
/* Authored after the trust row and moved out by JS — keep it there until then. */
#filename-override { order: 95; }
#analyze-form > #status { order: 100; }

/* Automatic has ONE action (upload, or try a sample instead) plus optional
   settings — no sequence to number. The eyebrows lose their counters, and the
   configuring eyebrow is hidden outright by the `hidden` attribute that
   syncStepLabels() manages, so nothing here has to fight it. */
#analyze-form.is-mode-auto .step-eyebrow-num { display: none; }

/* "—or—" between the upload card and the sample row: two alternatives, not two
   steps. Rules on both sides so it reads as a fork rather than a heading. */
.analyze-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0;
  color: var(--ink-soft);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.analyze-or::before,
.analyze-or::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}
/* Only Automatic offers the sample, so only Automatic shows the fork. */
#analyze-form:not(.is-mode-auto) .analyze-or { display: none; }
/* Once a file is chosen the alternative is moot — the sample link and chips
   are already hidden by .is-file-selected, and a divider with nothing under it
   would be a rule floating in space. */
#analyze-form.is-file-selected .analyze-or { display: none; }

/* ── Signed-out gate for the two account-bound modes ──
   One primary action per mode. Signed out, Automatic keeps the dropzone and
   its "Sign in to upload"; Custom and Saved template have nothing that works
   without a session (both /api/schema/propose and template reads are 401 for
   anonymous callers), so they replace the whole configuring surface — and the
   upload card above it — with a single centred prompt. Previously all three
   competed: an upload CTA, a per-mode CTA and the sidebar's. */
#analyze-form.is-anon-gated #step-eyebrow-upload,
#analyze-form.is-anon-gated #step-eyebrow-configure,
#analyze-form.is-anon-gated #dropzone,
#analyze-form.is-anon-gated #file-meta,
#analyze-form.is-anon-gated #upload-plan,
#analyze-form.is-anon-gated #mode-panel-custom .cx-card-body,
#analyze-form.is-anon-gated #mode-panel-custom .cx-actionbar {
  display: none;
}
/* Both eyebrows are hidden above, and they are what normally owns the gap
   below the mode selector — so the panel, now the first visible block under
   it, has to carry that distance itself. */
#analyze-form.is-anon-gated .analyze-body { margin-top: var(--space-8); }

.mode-authgate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: var(--space-8) var(--space-5);
  text-align: center;
}
/* The template gate is toggled by showTemplateModeState() via the `hidden`
   attribute; the `display: flex` above would normally defeat it, but the
   stylesheet's global `[hidden] { display: none !important }` (top of file)
   already covers every element, so no per-element guard is needed here. */
/* The custom gate is the inverse: absent unless the form says signed-out. */
.cx-signin { display: none; }
#analyze-form.is-anon-gated #mode-panel-custom .cx-signin { display: flex; }
.mode-authgate-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  margin-bottom: var(--space-1);
  border-radius: 50%;
  background: var(--surface-soft);
  color: var(--ink-soft);
}
.mode-authgate-title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--ink);
}
.mode-authgate-hint {
  margin: 0 0 var(--space-3);
  max-width: 42ch;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  line-height: 1.5;
}
/* The secondary route under the primary button. Two doors, because the block
   now speaks to a stranger first ("Create account") without stranding the
   returning visitor who already has one. */
.mode-authgate-alt {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
}

/* ── Sequence eyebrows ──
   A section divider, not a caption stuck to the block below. It owns the gap
   on BOTH sides — 32 above, 12 below, identical for step 1 and step 2 — so no
   neighbour sets a margin toward it and the two labels sit in the same rhythm
   wherever they land. The trailing hairline runs to the content edge, which is
   what stops the label reading as text floating above a card. */
.step-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-8) 0 var(--space-3);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
/* --border, not --border-light: the lighter token is #f3f4f6 on white and 9%
   alpha on the dark surface — a rule nobody can see is not a divider.
   Similarly, never use --border-light as a hover background; use
   --surface-soft-hover (defined at the top of :root) instead. */
.step-eyebrow::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  margin-left: var(--space-1);
  background: var(--border);
}
.step-eyebrow-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 0.65rem;
  letter-spacing: 0;
  color: var(--ink-soft);
}

/* ===================== Hero benefits ===================== */
/* Three lines on why the output can be trusted. Monochrome currentColor icons
   at --ink-soft — this row is exactly where the design system warns against
   "marketing icons", so they read as workspace glyphs, not badges. */
.hero-benefits {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
  /* Trails whichever mode panel is active, so it takes the same 32px a step
     eyebrow takes — one section-break distance for the whole column. */
  margin: var(--space-8) 0 var(--space-5);
  padding: 0;
  list-style: none;
}
.hero-benefits li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
}
/* 3px, not 1px: the glyph box is 18px against a 14px title, so a near-zero
   offset left the icon riding above the title's cap height. */
.hero-benefits svg { flex: 0 0 auto; margin-top: 3px; color: var(--ink-soft); }
.hero-benefit-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  /* de/pl compounds ("Rechnungsempfänger") are longer than a third of this row
     and would otherwise push the grid wider than the column. */
  overflow-wrap: break-word;
}
.hero-benefit-text strong { font-size: var(--text-md); font-weight: 600; color: var(--ink); }
/* --text-sm, not --text-xs: at 12px with 1.4 leading this line was the smallest
   type on the page and the one most likely to be skipped. --ink-soft already
   measures 5.54:1 on the light background and 7.47:1 on the dark one, so the
   lift here is size and leading rather than colour — darkening it further would
   flatten the contrast step between a title and its own description without
   buying any compliance. */
.hero-benefit-text span { font-size: var(--text-sm); color: var(--ink-soft); line-height: 1.45; }

/* ===================== Custom extraction card ===================== */
.cx-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.cx-card-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-light);
}
.cx-card-icon { display: inline-flex; color: var(--accent); }
.cx-card-title { margin: 0; font-size: var(--text-lg); font-weight: 600; }
/* Two columns: the instruction on the left stays put while the field list on
   the right is edited, so a long proposal never scrolls away what produced it. */
.cx-card-body {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--space-6);
  padding: var(--space-5);
}
.cx-col { min-width: 0; display: flex; flex-direction: column; }
.cx-label { font-size: var(--text-xs); font-weight: 600; color: var(--ink-soft); }
/* The describe column has no flex gap because its steps are deliberately
   unequal — label→hint is one group, hint→textarea→attach→CTA is the rhythm.
   Every value below is a 4px multiple; the old 2/6px ones were eyeballed. */
.cx-hint {
  margin: var(--space-1) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  line-height: 1.45;
}
/* …except as the inline status beside a button, where any top margin drops it
   off the row's cross-axis centre (the same trap noted on .analyze-opt-hint). */
.tplmode-actions .cx-hint { margin-top: 0; }
/* Only this column's copy of the shared attach control: the template editor's
   describe block has its own denser rhythm. */
.cx-col-describe .example-file-attach { margin-top: var(--space-3); }
/* The main action gets the widest gap in the column — it is what the whole
   left side is building toward. */
.cx-generate-row { margin-top: var(--space-4); }
/* Once fields exist, "Generate again" in the action bar takes over and
   updateCustomExtractView() sets `hidden` on the button — but the wrapper
   stayed in flow at zero height, so its 16px margin survived as a phantom gap
   between the attach link and the settings rule. Keyed off the button's own
   state rather than the card's .has-fields class, so the two cannot desync. */
.cx-generate-row:has(> button[hidden]) { display: none; }
/* Optional secondary block, not the tail of the generate flow: a full 24px
   and a rule separate it from the CTA above, and the summary carries real
   padding on both sides so it reads as its own row rather than a caption
   wedged under the button. */
.cx-settings {
  margin-top: var(--space-6);
  padding-top: var(--space-1);
  /* The bare `details` element rule further up the sheet paints a full 1px
     box with a 10px radius on every disclosure. Inherited here it drew a
     card around "Extraction settings" — a second boxed block directly above
     the dashed empty state, which is most of why the accordion looked wedged
     between the CTA and the field list. Reset it: this block is separated by
     a rule, not boxed. --border rather than --border-light for that rule, for
     the same reason as the eyebrow's: the lighter token is invisible. */
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
}
.cx-settings-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 36px;
  padding: var(--space-2) 0;
  border-radius: var(--radius);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  list-style: none;
}
.cx-settings-summary::-webkit-details-marker { display: none; }
.cx-settings-summary:hover { color: var(--ink); }
.cx-settings-summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cx-settings-chevron { flex: 0 0 auto; color: var(--ink-soft); transition: transform 0.15s ease, color 0.15s ease; }
.cx-settings-summary:hover .cx-settings-chevron { color: var(--ink); }
.cx-settings[open] .cx-settings-chevron { transform: rotate(180deg); }
.cx-settings-body { padding: var(--space-2) 0 var(--space-1); }
/* A compact optional setting. The shared filename control is sized for the
   Automatic panel's four-column settings grid (44px, base type); at that size
   it is the heaviest thing in this column and competes with "Generate fields".
   Scoped down here only — the Automatic and template mounts keep their metrics. */
.cx-settings-body .filename-override { gap: var(--space-1); }
.cx-settings-body .filename-override input {
  min-height: 38px;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
}
.cx-settings-body .analyze-opt-hint { font-size: var(--text-xs); }

.cx-fields-heading {
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
  font-weight: 600;
}
.cx-fields-count { color: var(--ink-soft); font-weight: 500; }
/* A self-contained block: generous, symmetric padding and an 8px internal
   rhythm, so it reads as a placeholder card rather than three lines squeezed
   inside a dashed outline. */
.cx-fields-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: var(--space-6) var(--space-5);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  text-align: center;
}
.cx-fields-empty-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink);
}
.cx-fields-empty-hint {
  margin: 0;
  /* Wide enough that the sentence sits on one line in the empty card's
     single-column layout; still wraps to the container on narrow screens.
     34ch broke it across two lines inside a ~670px box. */
  max-width: 60ch;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  line-height: 1.45;
}
.cx-empty-add {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--space-3);
}

/* ── Empty vs populated ──
   With zero fields the two-column layout is mostly empty space: a short
   instruction box on the left and a dashed placeholder on the right, with a
   sticky bar underneath counting nothing. Collapse to one column until there
   is actually a field list to sit beside the instruction. The stacked gap is
   24px, not 16: the describe column ends on the settings rule and the fields
   column opens with the dashed empty state, and two borders 16px apart read as
   one crowded block. */
.cx-card.is-empty .cx-card-body {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
}
/* The empty state's own "No fields yet" is the label; the "(0)" heading above
   it would say the same thing twice. */
.cx-card.is-empty .cx-fields-heading { display: none; }
/* Hidden, not emptied: every control it holds is meaningless at zero fields,
   and "Add field" has a copy inside the empty state instead. */
.cx-card.is-empty .cx-actionbar { display: none; }
.cx-fields-loading { display: flex; flex-direction: column; gap: var(--space-2); }
.cx-skel {
  height: 52px;
  border-radius: var(--radius);
  background: linear-gradient(90deg, var(--surface-soft) 25%, var(--border-light) 50%, var(--surface-soft) 75%);
  background-size: 200% 100%;
  animation: cx-skel-shimmer 1.4s ease-in-out infinite;
}
@keyframes cx-skel-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cx-skel { animation: none; }
}
.cx-fields-note { margin-top: var(--space-4); }

/* Sticky action bar — stays reachable however long the field list gets, which
   is the whole reason the apply button exists down here. */
.cx-actionbar {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--border);
  background: var(--surface-soft);
}
.cx-actionbar-meta { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.cx-actionbar-count { font-size: var(--text-sm); font-weight: 600; color: var(--ink-soft); }
/* Empty when idle, so it must not reserve space or push the settings summary. */
.cx-generate-status:empty { display: none; }
.cx-generate-status {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  line-height: 1.45;
}
.cx-generate-status.is-error { color: var(--danger); }
.cx-actionbar-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.cx-bar-btn { display: inline-flex; align-items: center; gap: 6px; }

/* ===================== Saved template mode ===================== */
.scanmode-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  padding: var(--space-5);
}
.scanmode-title { margin: 0 0 var(--space-2); font-size: var(--text-lg); font-weight: 600; }
.scanmode-hint {
  margin: 0 0 var(--space-4);
  max-width: 62ch;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  line-height: 1.5;
}
.scanmode-controls { display: flex; flex-direction: column; gap: var(--space-1); max-width: 420px; }
/* Signed out: swap the controls for the gate, the same CSS-driven inversion
   the custom panel uses (.cx-signin above). */
.scanmode-signin { display: none; }
#analyze-form.is-anon-gated #mode-panel-scan .scanmode-signin { display: flex; }
#analyze-form.is-anon-gated #mode-panel-scan .scanmode-hint,
#analyze-form.is-anon-gated #mode-panel-scan .scanmode-controls { display: none; }

.tplmode-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  padding: var(--space-5);
}
/* 20px below, matching the custom card's head→body distance (that card's body
   padding), so the two mode cards open on the same rhythm. */
.tplmode-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.tplmode-title { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.tplmode-loading,
.tplmode-error { margin: 0; font-size: var(--text-sm); color: var(--ink-soft); }
.tplmode-error { color: var(--danger); }
.tplmode-chooser { display: flex; flex-direction: column; gap: var(--space-4); }
.tplmode-picker { display: flex; flex-direction: column; gap: var(--space-1); max-width: 420px; }
.tplmode-detail {
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  background: var(--surface-soft);
  padding: var(--space-3) var(--space-4);
}
.tplmode-detail-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.tplmode-detail-name { font-size: var(--text-base); font-weight: 600; }
/* Scope is not decoration: it decides whether the template resolves at all. */
.tplmode-scope-badge {
  font-size: var(--text-2xs);
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-soft);
}
.tplmode-scope-badge.is-workspace {
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: var(--accent-light);
  color: var(--accent-strong);
}
.tplmode-detail-desc { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--ink-soft); }
.tplmode-detail-fields { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: var(--space-2); }
.tplmode-settings { max-width: 420px; }
.tplmode-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.tplmode-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-4);
  text-align: center;
}
.tplmode-empty-icon,
.tpl-empty-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--accent-light);
  color: var(--accent);
}
.tplmode-empty-title { margin: var(--space-1) 0 0; font-size: var(--text-lg); font-weight: 600; }
.tplmode-empty-hint {
  margin: 0 0 var(--space-2);
  max-width: 42ch;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  line-height: 1.5;
}

.custom-instruction-input {
  width: 100%;
  resize: vertical;
  min-height: 64px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Same inset as the filename input, so the placeholder/typed text of every
     control in the card starts on one vertical line. */
  padding: 10px var(--space-3);
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  margin-top: var(--space-2);
}

.custom-extract-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.custom-extract-actions .analyze-opt-hint.is-error { color: var(--danger); }

/* Proposed-field rows: compact enough that 6–10 fit without scrolling, with the
   drag handle and the remove action pinned outside the editable middle. */
.custom-fields-list { display: flex; flex-direction: column; gap: var(--space-2); }

.custom-field-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-2);
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--space-2) 10px;
}
.custom-field-row.is-dragging { opacity: 0.45; }
.custom-field-row.is-drop-target { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.cf-drag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  min-height: 30px;
  margin-top: 2px;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--ink-soft);
  cursor: grab;
}
.cf-drag:hover { background: var(--surface-soft); color: var(--ink); }
.cf-drag:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.cf-body { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.cf-main { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cf-main .cf-label {
  flex: 1 1 150px;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 8px;
  font: inherit;
  font-size: var(--text-md);
  color: var(--ink);
  background: var(--surface-input);
}
.cf-main .cf-type,
.cf-main .cf-required {
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 6px;
  font-size: var(--text-sm);
  color: var(--ink-soft);
  background: var(--surface-input);
}
.custom-field-row .cf-desc {
  width: 100%;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 3px 8px;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  background: none;
}
.custom-field-row .cf-desc:hover { border-color: var(--border-light); }
.custom-field-row .cf-desc:focus {
  border-color: var(--border);
  background: var(--surface-input);
  color: var(--ink);
}
.cf-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-top: 3px;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.cf-remove:hover { background: var(--surface-soft); color: var(--danger); }
.cf-remove:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Optional example-document attach control, shared by the Custom Extract panel
   and the template editor's "Describe what you need" block. Link-styled
   trigger + a filename chip that replaces it once a file is picked. */
.example-file-attach {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.example-file-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  padding: 3px 6px 3px 10px;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  max-width: 100%;
}
.example-file-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
}
.example-file-clear {
  border: none;
  background: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: var(--text-sm);
  line-height: 1;
  padding: 2px 4px;
}
.example-file-clear:hover { color: var(--danger); }

/* Template editor's "Describe what you need" block: same shape as the Custom
   Extract panel's instruction box, but always visible (no .is-open gate) since
   the editor itself is only shown once the modal is open. */
.tpl-describe-block {
  border: 1px dashed var(--border-light);
  border-radius: var(--radius);
  background: var(--surface-soft);
  padding: 10px 12px;
  margin: 4px 0 2px;
}
.tpl-describe-block .custom-instruction-input { min-height: 48px; }

/* ── Review: grounding / origin chip on field cards ── */
.field-origin-chip {
  display: inline-flex;
  align-items: center;
  font-size: var(--text-xs);
  border-radius: 999px;
  padding: 1px 8px;
  border: 1px solid var(--border-light);
  color: var(--ink-soft);
  background: var(--surface-soft);
  white-space: nowrap;
}
.field-origin-chip.is-grounded { color: var(--success-text); border-color: color-mix(in srgb, var(--success) 35%, transparent); }
.field-origin-chip.is-approximate { color: var(--accent-warm); border-color: color-mix(in srgb, var(--accent-warm) 40%, transparent); }
.field-origin-chip.is-ungrounded { color: var(--accent-warm); border-color: color-mix(in srgb, var(--accent-warm) 55%, transparent); background: color-mix(in srgb, var(--accent-warm) 8%, transparent); }
.field-origin-chip.is-user-entered { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
.field-origin-chip.is-generated { color: var(--ink-soft); border-style: dashed; }

/* ── Review: type-preserving editors ── */
.list-field-editor .list-item-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}
.list-field-editor .list-item-input {
  flex: 1 1 auto;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 6px 8px;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
}
.value-textarea {
  width: 100%;
  resize: vertical;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
}
.object-field-read { margin: 0 0 8px; }
.object-field-read .object-field-row {
  display: flex;
  gap: 8px;
  padding: 3px 0;
  border-bottom: 1px dashed var(--border-light);
}
.object-field-read dt { flex: 0 0 40%; color: var(--ink-soft); font-size: var(--text-sm); }
.object-field-read dd { flex: 1 1 auto; margin: 0; font-size: var(--text-sm); overflow-wrap: anywhere; }

/* The shared custom-filename control sits in whichever mode's slot is mounted;
   the slot itself adds nothing, so the label keeps its own layout. */
.filename-slot { display: contents; }
/* …except inside the flex columns of the custom card and template panel, where
   `display: contents` would let the label's children escape into the parent
   flex flow. There the slot is a normal block. */
.cx-settings-body .filename-slot,
.tplmode-settings .filename-slot { display: block; }

/* ── Responsive: mode selector, custom card, template panel ── */

/* Two columns is not enough room for the field editor; stack below this. */
@media (max-width: 1024px) {
  /* Same 24px the empty layout uses, so stacking never changes the rhythm —
     only the column count. */
  .cx-card-body { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
}

/* Tablet: keep three columns but shrink the description so it stops wrapping
   to three lines in a ~33%-wide button. */
@media (max-width: 1100px) and (min-width: 901px) {
  .mode-option { gap: var(--space-2); padding: var(--space-3) 10px; }
  .mode-option-desc { font-size: 0.7rem; line-height: 1.3; }
}

@media (max-width: 900px) {
  /* Stacked mode options: the descriptions stay readable instead of wrapping
     to three lines inside a 33%-wide button. */
  .mode-selector { grid-template-columns: 1fr; }
  .hero-benefits { grid-template-columns: 1fr; gap: var(--space-3); }
}

/* Trust row on constrained layouts: shed the DESCRIPTIONS first, never the
   titles. Stacked into one column the three descriptions add ~90px of the
   least load-bearing text on the screen, and this row trails the mode panel —
   so the height it spends is height between the user and the end of the page.
   The titles alone still say what the product does.

   display: none, not the clip-to-keep-it-in-the-a11y-tree treatment used for
   the mode options: nothing here is an accessible name or description for a
   control (the row is informational, per its aria-label), so keeping the text
   for assistive tech while it costs sighted users nothing has no upside —
   and clipping would not reclaim the height, which is the entire point.

   Two triggers, because the problem is height on both axes: a narrow phone,
   and a short landscape/zoomed viewport where reflow (1.4.10) has already
   stacked everything. */
@media (max-width: 640px), (max-height: 640px) {
  .hero-benefit-text span { display: none; }
}

/* Mobile: names only. Three stacked two-line options pushed the dropzone —
   the actual first task — below the fold. The description is redundant once
   the options are full-width and read as a plain list, and it stays available
   to assistive tech (aria-describedby targets it; `display: none` would drop
   it from the accessibility tree, so it is clipped rather than hidden). */
@media (max-width: 640px) {
  .mode-selector { gap: 4px; }
  .mode-option { min-height: 46px; padding: 10px var(--space-3); }
  .mode-option-desc {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .mode-option-icon { width: 24px; height: 24px; }
}

@media (max-width: 700px) {
  .cf-main .cf-type,
  .cf-main .cf-required { flex: 1 1 40%; }
  .cf-main { row-gap: 6px; }
  .cx-card-head,
  .cx-card-body,
  .cx-actionbar { padding-left: var(--space-4); padding-right: var(--space-4); }
  .tplmode-card { padding: var(--space-4); }
  /* Full-width actions so the primary is a comfortable touch target rather
     than a narrow button squeezed beside two others. */
  .cx-actionbar { flex-direction: column; align-items: stretch; }
  .cx-actionbar-actions { justify-content: stretch; }
  .cx-actionbar-actions .cx-bar-btn,
  .cx-actionbar-actions .cx-apply-btn { flex: 1 1 auto; justify-content: center; min-height: 40px; }
  .tplmode-actions .primary-btn { width: 100%; }
  .tpl-scope-tabs { align-self: stretch; }
  .tpl-scope-tab { flex: 1 1 auto; text-align: center; }
}

/* Touch targets: a 22px drag handle is fine with a mouse, not with a thumb. */
@media (pointer: coarse) {
  .cf-drag { width: 30px; min-height: 38px; }
  .cf-remove { width: 34px; height: 34px; }
  .tpl-row-menu-btn { width: 38px; height: 38px; }
}

/* ============================================================
   Accessibility — Windows High Contrast Mode / forced colors
   HCM swaps author colours for a limited system palette and drops
   backgrounds, gradients and shadows. Controls whose shape came only from a
   background then read as flat text, so give them a system-coloured border
   and pin focus to the system Highlight. Placed at end-of-file so these win
   over the component base rules on source order; box-sizing:border-box
   (global) means the added border causes no layout shift.
   ============================================================ */
@media (forced-colors: active) {
  :focus-visible,
  .skip-link:focus,
  .dropzone-hero:has(> input:focus-visible) {
    outline-color: Highlight;
  }
  .primary-btn,
  .secondary-btn,
  .ghost-btn,
  .tiny-btn,
  .cancel-btn,
  .nav-btn,
  .export-menu-btn,
  .icon-btn,
  .dropzone-browse,
  .dropzone-camera,
  .wf-badge,
  .rd-conf-chip,
  .field-origin-chip,
  .sample-chip,
  .dropzone-hero {
    border: 1px solid;
  }
  /* State conveyed only by a colour tint needs a non-colour cue too. */
  .field-card.is-confirmed,
  .field-card.needs-review,
  .appnav-link--active {
    outline: 1px solid;
    outline-offset: -1px;
  }
}

/* ============================================================
   Accessibility — user "increase contrast" preference. Strengthen the
   faint border + secondary-text tokens and thicken the focus ring; mirrors
   the theme structure (explicit + OS dark) so dark mode stays correct.
   ============================================================ */
@media (prefers-contrast: more) {
  :root {
    --border: #6b7280;
    --border-light: #9ca3af;
    --ink-soft: #4b5563;
  }
  [data-theme="dark"] {
    --border: rgba(203, 213, 225, 0.55);
    --border-light: rgba(203, 213, 225, 0.35);
    --ink-soft: #cbd5e1;
  }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --border: rgba(203, 213, 225, 0.55);
      --border-light: rgba(203, 213, 225, 0.35);
      --ink-soft: #cbd5e1;
    }
  }
  :focus-visible { outline-width: 3px; }
}

/* ---- Shared FAQ (docs, pricing, security) + Glossary ---- */
.faq {
  margin-top: var(--space-6, 32px);
}
.faq > h2 {
  margin-bottom: 8px;
}
.faq-item > summary {
  list-style: none;
}
.faq-item > summary::-webkit-details-marker {
  display: none;
}
.faq-item[open] > summary {
  color: var(--accent);
}
.faq-item > p {
  padding: 0 var(--space-3) 12px;
  margin: 0;
  color: var(--muted);
}

.glossary-list {
  margin: 0;
  display: grid;
  gap: var(--space-4, 20px);
}
.glossary-item > dt {
  font-weight: 700;
  color: var(--ink);
  font-size: 1.05rem;
}
.glossary-item > dd {
  margin: 6px 0 0;
  color: var(--muted);
  max-width: 70ch;
}
.glossary-final {
  text-align: center;
}
.glossary-cta-row {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
}
