/* Setback console -- shared styling for the docket board and case page.
   Deliberately dependency-free (no build step, no framework): plain CSS
   custom properties for a light/dark-safe palette, served as a static
   file by console/app.py's StaticFiles mount.

   Wave 5 UI revamp (Package A -- style.css only, per UI-SPEC.md):
   - Section "Tokens" below is the §1 token triad (light on bare :root,
     dark redefinitions guarded under prefers-color-scheme, and again
     under [data-theme="dark"] so an explicit toggle always wins).
   - Legacy aliases keep every class already used by app.py/app.js
     working unchanged (they resolve, at render time, to whichever
     theme's tokens are in scope) -- B and C migrate markup to the new
     class names in their own lanes; nothing in this file renames or
     removes an existing selector.
   - Section "Components (wave 5)" adds the 14 component blocks from
     UI-SPEC.md §2, using exactly the class names documented there. */

:root {
  /* --- neutrals: warm, not clinical-grey --- */
  --bg:             #f7f5f2;
  --surface:        #ffffff;
  --surface-sunken: #efece7;   /* chat transcript well, docked evidence rail */
  --border:         #e4dfd7;
  --border-strong:  #cfc8bb;
  --text:           #201d19;
  --text-muted:     #6b6459;

  /* --- brand accent: ONE hue, interactive affordances only, never status --- */
  --accent:          #1d4ed8;
  --accent-hover:    #1739ab;
  --accent-contrast: #ffffff;
  --accent-soft:     #e8eefc;  /* chip/link hover backgrounds */

  /* --- semantic status: 4 meanings, each pre-checked to 4.5:1 text /
     3:1 non-text contrast (WCAG 1.4.3 / 1.4.11). The ONLY colours
     allowed to mean something -- never reused decoratively. --- */
  --status-shipped:        #0f6b3f;  /* ground survived: supported, ready to submit */
  --status-shipped-bg:     #e4f5ea;
  --status-shipped-border: #0f6b3f;

  --status-refused:        #8a3a12;  /* statutory gate refused it -- NOT an error,
                                         so this is a warm brown, not red */
  --status-refused-bg:     #fbe9db;
  --status-refused-border: #b8571c;

  --status-flagged:        #8a6a00;  /* split reviewers / needs resident input */
  --status-flagged-bg:     #fbf1d4;
  --status-flagged-border: #b8901a;

  --status-pending:        #45505c;  /* queued / under review / not started */
  --status-pending-bg:     #eaedf1;
  --status-pending-border: #8a94a1;

  /* true system/input errors ONLY -- red is reserved exclusively for this */
  --error:        #b3261e;
  --error-bg:     #fdecea;
  --error-border: #b3261e;

  /* --- type scale: video-legibility floor (~18-20px effective body text
     at 1080p capture); WCAG AA (4.5:1) is a floor, AAA (~7:1) the target
     for anything that must read on a compressed recording --- */
  --text-xs:   0.8125rem;  /* 13px -- metadata lines only, never a verdict */
  --text-sm:   0.9375rem;  /* 15px -- secondary copy */
  --text-base: 1.0625rem;  /* 17px -- body / chat bubbles */
  --text-lg:   1.25rem;    /* 20px -- section headings, ground claims */
  --text-xl:   1.75rem;    /* 28px -- verdict stamps, refusal headline */
  --text-2xl:  2.25rem;    /* 36px -- gate REFUSED/SHIPPED stamp, video hero moments */

  /* --- spacing scale (4px base) --- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-8: 3rem;

  /* Real `.topbar` height, measured and kept live by app.js's
     `initChatPaneHeight` (the case-meta line/QR code/wrapping at narrow
     widths all change it) -- `.case-layout__chat` below subtracts this,
     not a fixed guess, to size itself to exactly what's left of the
     viewport under the header. This fallback only matters for the brief
     window before that JS runs (or with JS disabled), sized to the
     one-line-header case. */
  --header-height: 88px;

  --radius-sm:   6px;
  --radius:      10px;
  --radius-lg:   16px;   /* wide disruption cards (split/refusal) */
  --radius-pill: 999px;

  --shadow:    0 1px 2px rgba(32, 29, 25, 0.06), 0 1px 1px rgba(32, 29, 25, 0.04);
  --shadow-lg: 0 8px 24px rgba(32, 29, 25, 0.12), 0 2px 6px rgba(32, 29, 25, 0.08);

  /* --- legacy aliases: every selector below this token block that was
     already shipping (docket rows, gate-decision, review-verdict,
     badges, create-case-error, chat bubbles, buttons...) keeps working
     unchanged -- these resolve lazily to whichever theme's tokens are
     active, so no dark-mode redefinition is needed for the aliases
     themselves, only for the canonical tokens above. Package B/C: new
     markup should reach for the canonical --status-*/--error tokens or
     the .tag/.ground-card/etc. components directly rather than these
     aliases; they exist for zero-risk backward compatibility only. */
  --success:    var(--status-shipped);
  --success-bg: var(--status-shipped-bg);
  --warn:       var(--status-flagged);
  --warn-bg:    var(--status-flagged-bg);
  --danger:     var(--error);
  --danger-bg:  var(--error-bg);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:             #1b1815;
    --surface:        #242019;
    --surface-sunken: #171410;
    --border:         #362f27;
    --border-strong:  #4a4136;
    --text:           #f0ece5;
    --text-muted:     #a89f92;

    --accent:          #6d93ff;
    --accent-hover:    #86a6ff;
    --accent-contrast: #0b0d10;
    --accent-soft:     #1c2740;

    --status-shipped:        #7ad9a5;
    --status-shipped-bg:     #12301f;
    --status-shipped-border: #7ad9a5;

    --status-refused:        #e8996b;
    --status-refused-bg:     #3a2214;
    --status-refused-border: #e8996b;

    --status-flagged:        #e0b64a;
    --status-flagged-bg:     #332a10;
    --status-flagged-border: #e0b64a;

    --status-pending:        #c3ccd6;
    --status-pending-bg:     #262b33;
    --status-pending-border: #8a94a1;

    --error:        #ff8f87;
    --error-bg:     #3a1613;
    --error-border: #ff8f87;

    --shadow:    0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.4);
  }
}

:root[data-theme="dark"] {
  --bg:             #1b1815;
  --surface:        #242019;
  --surface-sunken: #171410;
  --border:         #362f27;
  --border-strong:  #4a4136;
  --text:           #f0ece5;
  --text-muted:     #a89f92;

  --accent:          #6d93ff;
  --accent-hover:    #86a6ff;
  --accent-contrast: #0b0d10;
  --accent-soft:     #1c2740;

  --status-shipped:        #7ad9a5;
  --status-shipped-bg:     #12301f;
  --status-shipped-border: #7ad9a5;

  --status-refused:        #e8996b;
  --status-refused-bg:     #3a2214;
  --status-refused-border: #e8996b;

  --status-flagged:        #e0b64a;
  --status-flagged-bg:     #332a10;
  --status-flagged-border: #e0b64a;

  --status-pending:        #c3ccd6;
  --status-pending-bg:     #262b33;
  --status-pending-border: #8a94a1;

  --error:        #ff8f87;
  --error-bg:     #3a1613;
  --error-border: #ff8f87;

  --shadow:    0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.4);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: var(--text-base);
  line-height: 1.5;
}

/* Baseline focus-visible for every interactive element that doesn't
   define its own (component blocks below override this with a
   tighter/contextual ring where documented; this is the fallback so
   nothing interactive in the app is ever focus-invisible). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.topbar {
  padding: 1.5rem 2rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.topbar h1 {
  margin: 0;
  font-size: 1.4rem;
  letter-spacing: -0.01em;
}

.tagline {
  margin: 0.15rem 0 0;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.container {
  max-width: 900px;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.1rem 1.3rem;
  margin-bottom: 1rem;
}

.card h3 {
  margin: 0 0 0.6rem;
  font-size: 1rem;
}

.empty {
  color: var(--text-muted);
  font-style: italic;
  margin: 0;
}

.badge {
  display: inline-block;
  font-size: 0.75rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-muted);
  white-space: nowrap;
}

.docket-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.docket-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
  box-shadow: var(--shadow);
}

.docket-row:hover {
  border-color: var(--accent);
}

.docket-row__main {
  display: flex;
  flex-direction: column;
}

.docket-row__app {
  font-weight: 600;
}

.docket-row__id {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.event-list,
.ground-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.event-list li {
  font-size: 0.85rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text-muted);
  word-break: break-word;
}

.event-seq {
  color: var(--accent);
  margin-right: 0.4rem;
}

/* Direct children only (`>`) -- deliberately scoped so this pre-accordion
   rule never reaches a nested `<li>` several levels inside a ground card's
   own `<details>` body (e.g. `.ground-card__opinions`'s reviewer-verdict
   list). A bare `.ground-list li` descendant selector matched those too,
   flexing each reviewer opinion's own text into unreadable, squeezed
   `justify-content: space-between` columns -- found live on the deployed
   console, wave-9 QA smoke loop. */
.ground-list > li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.35rem 0;
  border-bottom: 1px dashed var(--border);
}

.ground-list > li:last-child {
  border-bottom: none;
}

.ground--supported .badge {
  background: var(--success-bg);
  color: var(--success);
  border-color: transparent;
}

.ground--flagged .badge {
  background: var(--warn-bg);
  color: var(--warn);
  border-color: transparent;
}

.ground--refused .badge {
  background: var(--danger-bg);
  color: var(--danger);
  border-color: transparent;
}

.chat-card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.chat-transcript {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-height: 320px;
  overflow-y: auto;
  padding-right: 0.25rem;
}

.chat-turn {
  padding: 0.55rem 0.8rem;
  border-radius: var(--radius);
  max-width: 80%;
  font-size: 0.92rem;
}

.chat-turn--system {
  align-self: flex-start;
  background: var(--bg);
  border: 1px solid var(--border);
}

.chat-turn--resident {
  align-self: flex-end;
  background: var(--accent);
  color: var(--accent-contrast);
}

.chat-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.chat-form input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
}

button {
  padding: 0.5rem 0.9rem;
  border-radius: 6px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  cursor: pointer;
}

button:hover {
  filter: brightness(1.05);
}

#start-tribunal {
  align-self: flex-start;
  background: var(--surface);
  color: var(--accent);
}

/* --- create-case flow (docket board) ------------------------------------
   `app.js` injects this form's markup into the docket board on load --
   the board's server-rendered HTML has no case-creation UI of its own
   (only the JSON `POST /api/cases` route existed before this pass), so the
   form and its styling both live entirely in this work package's lane. */

.create-case-card {
  border: 1px solid var(--accent);
}

.create-case-card h3 {
  margin-bottom: 0.3rem;
}

.create-case-hint {
  margin: 0.5rem 0 0;
  color: var(--text-muted);
  font-size: 0.85rem;
}

.create-case-form {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.create-case-form input[type="text"] {
  flex: 1;
  min-width: 220px;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}

.create-case-error {
  margin: 0.6rem 0 0;
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  background: var(--danger-bg);
  color: var(--danger);
  font-size: 0.85rem;
}

/* --- refusal / gate-decision cards: status must read at a glance -------- */

.gate-decision {
  list-style: none;
  padding: 0.7rem 0.9rem;
  border-radius: 8px;
  border-left: 4px solid var(--border);
  background: var(--bg);
}

.gate-decision strong {
  text-transform: capitalize;
  letter-spacing: 0.01em;
}

.gate-decision--shipped {
  border-left-color: var(--success);
  background: var(--success-bg);
}

.gate-decision--shipped strong {
  color: var(--success);
}

.gate-decision--refused-irrelevant,
.gate-decision--refused-unsubstantiated {
  border-left-color: var(--status-refused-border);
  background: var(--status-refused-bg);
}

.gate-decision--refused-irrelevant strong,
.gate-decision--refused-unsubstantiated strong {
  color: var(--status-refused);
}

.gate-decision--flagged {
  border-left-color: var(--warn);
  background: var(--warn-bg);
}

.gate-decision--flagged strong {
  color: var(--warn);
}

/* --- reviewer opinions: agree/disagree at a glance ----------------------- */

.review-verdict {
  list-style: none;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  border-left: 3px solid var(--border);
}

.review-verdict--support {
  border-left-color: var(--success);
}

.review-verdict--support strong {
  color: var(--success);
}

.review-verdict--reject {
  border-left-color: var(--danger);
}

.review-verdict--reject strong {
  color: var(--danger);
}

.review-verdict--voided {
  border-left-color: var(--warn);
  color: var(--text-muted);
  font-style: italic;
}

/* --- annotated evidence overlay ------------------------------------------ */

.annotated-overlay {
  list-style: none;
}

.annotated-overlay img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* --- submitted documents / downloads -------------------------------------- */

.document-preview {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.9rem 1.1rem;
  background: var(--bg);
  margin-bottom: 0.5rem;
  overflow-x: auto;
}

.document-downloads {
  margin: 0 0 1rem;
  font-size: 0.85rem;
}

.document-downloads a {
  color: var(--accent);
}

/* =========================================================================
   Components (wave 5) -- UI-SPEC.md §2. Class names and states are copied
   verbatim from the spec; every one of the 14 components lives here so it
   is defined exactly once and reused everywhere, per founder requirement
   #5. Nothing above this line was renamed or removed.
   ========================================================================= */

/* --- §2.1 Message bubble (role-differentiated) --------------------------
   `--ai` (plain text, left, labelled "Setback"), `--resident` (filled
   accent bubble, right, unlabelled -- the fill itself signals "you"),
   `--ai-system` (italic, centered, muted, no bubble -- non-conversational
   log lines like "Evidence added: north-elevation.pdf"). `chat-turn--system`
   above is the pre-wave-5 alias for this same AI-turn concept; both are
   safe to use as ".chat-turn--system, .chat-turn--ai" selectors migrate. */

.chat-turn { max-width: 78%; font-size: var(--text-base); }

.chat-turn--ai {
  align-self: flex-start;
  background: transparent;
  border: none;
  padding: var(--space-1) 0;
}

.chat-turn--ai .chat-turn__label {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-muted);
  margin-bottom: 2px;
  letter-spacing: 0.01em;
}

.chat-turn__text {
  margin: 0;
}

.chat-turn--resident {
  align-self: flex-end;
  background: var(--accent);
  color: var(--accent-contrast);
  border-radius: var(--radius) var(--radius) var(--space-1) var(--radius);
  padding: var(--space-2) var(--space-3);
  box-shadow: var(--shadow);
}

.chat-turn--ai-system {
  align-self: center;
  background: transparent;
  border: none;
  padding: var(--space-1) 0;
  font-style: italic;
  color: var(--text-muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* --- §2.2 Quick-reply chip row -------------------------------------------
   Renders under the latest AI turn when its stage has a closed answer
   set. Clicking a chip must call the same submit path as typed input
   (app.js's lane); the row is removed once acted on, never left stale. */

.quick-replies {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0 var(--space-3);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  cursor: pointer;
}

.chip--reply {
  border: 1px solid var(--accent);
  background: var(--surface);
  color: var(--accent);
  padding: var(--space-2) var(--space-4);
  font-weight: 600;
}

.chip--reply:hover {
  background: var(--accent-soft);
}

.chip--reply:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* --- §2.3 Stage stepper ---------------------------------------------------
   Minimal by design (GOV.UK question-pages pattern): a text counter +
   optional back-link, never a multi-segment progress bar. */

.stage-stepper {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-bottom: var(--space-2);
}

.stage-stepper__back {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--text-base);
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
}

.stage-stepper__back:hover {
  color: var(--accent);
}

.stage-stepper__back:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.stage-stepper__count {
  font-weight: 500;
}

/* --- §2.4 Document card ----------------------------------------------------
   Replaces the raw-JSON `document_uploaded` line. `.doc-card__thumb--
   placeholder` is the kind-specific placeholder icon variant for when the
   evidence pipeline hasn't produced a rendered first page yet (per this
   wave's explicit non-goal: no new thumbnail pipeline). */

.doc-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.doc-card__thumb {
  width: 56px;
  height: 56px;
  flex: none;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface-sunken);
}

.doc-card__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.doc-card__thumb--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  color: var(--text-muted);
}

.doc-card__thumb--placeholder::before {
  content: "\1F4C4"; /* generic document glyph; no per-kind icon set built this wave */
}

.doc-card__body {
  flex: 1;
  min-width: 0;
}

.doc-card__title {
  margin: 0;
  font-weight: 600;
}

.doc-card__meta {
  margin: 2px 0 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* Google's Street View attribution requirement, made genuinely visible on
   the page rather than left only in a hover-only title="..." tooltip. */
.doc-card__attribution {
  margin: 2px 0 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-style: italic;
}

/* Provenance-grade badge: a trust signal, not a status, so it is a plain
   neutral tag -- never the --status-* palette (colour-discipline rule 2). */

.tag--grade-a,
.tag--grade-b {
  background: var(--surface-sunken);
  color: var(--text-muted);
  border: 1px solid var(--border-strong);
}

/* --- Overshadowing-simulation card (wave 12 Veo integration) ---------------
   Deliberately the hazard-flagged palette (--status-flagged-*), never the
   neutral grey used for the real Street View/provenance badges above --
   this card must never be visually mistaken for real evidence. The
   mandatory label/explainer are plain paragraphs, always visible, never
   a dismissible toast. */

.simulation-card {
  border: 2px solid var(--status-flagged-border);
  background: var(--status-flagged-bg);
}

.tag--simulation {
  display: inline-block;
  margin-bottom: var(--space-2);
  background: var(--status-flagged-border);
  color: var(--surface);
}

.simulation-card video {
  display: block;
  width: 100%;
  max-width: 480px;
  border-radius: var(--radius);
  background: #000;
}

.simulation-card__label {
  margin: var(--space-2) 0 0;
  font-weight: 700;
  color: var(--status-flagged);
}

.simulation-card__explainer {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* --- §2.5 Citation chip -----------------------------------------------------
   Three variants (--clause / --doc / --region) sharing one base class,
   per the NotebookLM two-pane pattern. Behaviour lives in app.js's lane. */

.citation-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: 1px solid var(--border-strong);
  background: var(--surface-sunken);
  color: var(--text);
  border-radius: var(--radius-pill);
  padding: 2px var(--space-2);
  font-size: var(--text-xs);
  font-family: ui-monospace, Menlo, monospace;
  cursor: pointer;
}

.citation-chip:hover,
.citation-chip:focus-visible {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.citation-chip:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* Popover shell for the --clause variant's statute-text reveal. */

.citation-chip__popover {
  position: absolute;
  z-index: 20;
  max-width: 320px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: var(--space-3);
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: var(--text-sm);
  color: var(--text);
}

/* --- §2.6 Ground card -------------------------------------------------------
   Extends the bare `.ground-list li` into the full spec'd card. Status
   modifier covers all 5 GroundStatus values via the same 4-token map
   the .tag component uses (§2.11) -- proposed/under_review -> --pending,
   supported -> --shipped, refused -> --refused, flagged -> --flagged. */

.ground-card {
  display: flex;
  border: 1px solid var(--border);
  border-left: 5px solid var(--status-pending-border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  margin-bottom: var(--space-3);
  box-shadow: var(--shadow);
  list-style: none;
}

/* Wave 9: the accordion (`.ground-card__accordion`) is the card's one
   flexed child now -- the status colour that used to live on a separate
   `.ground-card__stripe` div moves to the card's own left border instead,
   keeping the same "colour = meaning" cue with one fewer DOM node. */
.ground-card--shipped { border-left-color: var(--status-shipped-border); }
.ground-card--refused { border-left-color: var(--status-refused-border); }
.ground-card--flagged { border-left-color: var(--status-flagged-border); }
.ground-card--pending { border-left-color: var(--status-pending-border); }

.ground-card__accordion {
  flex: 1;
  min-width: 0;
}

.ground-card__body {
  padding: 0 var(--space-4) var(--space-3);
  min-width: 0;
}

.ground-card__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
}

.ground-card__claim {
  margin: 0;
  font-size: var(--text-lg);
}

.ground-card__basis {
  margin: var(--space-1) 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.ground-card__explanation {
  margin: var(--space-2) 0;
  font-size: var(--text-sm);
}

.ground-card__evidence {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.ground-card__thumb {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  margin-right: var(--space-2);
}

/* --- §2.7 Tribunal live view ("courtroom sitting") -------------------------
   3-column grid per ground; each row expands while active and collapses
   to a verdict chip once resolved. Driven entirely by existing SSE
   events -- app.js's lane, no new backend events required. */

.tribunal-run__plan {
  margin: 0 0 var(--space-3);
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.tribunal-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

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

.tribunal-row--collapsed {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.tribunal-row__claim {
  font-weight: 600;
}

.tribunal-columns {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.reviewer-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.reviewer-card__name {
  font-weight: 600;
  font-size: var(--text-sm);
}

.reviewer-card__state {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.reviewer-card--active {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.reviewer-card--dim {
  opacity: 0.5;
}

.reviewer-card--done {
  border-color: var(--status-shipped-border);
}

/* Small responsive floor: 3 equal columns get cramped below ~480px --
   the tribunal row is the widest live-updating component in the app. */
@media (max-width: 480px) {
  .tribunal-columns {
    grid-template-columns: 1fr;
  }
}

/* --- §2.8 Disruption card (reviewer split) ---------------------------------
   Full-width, breaks out of the timeline grid entirely -- deliberately
   the widest, most visually weighted card on the page besides the final
   verdict stamps (research: highest-drama beat). Sequential fade for the
   video-choreography "Clause -> Evidence -> Ruling" reveal (§7 item 2);
   reduced-motion drops straight to the settled state. */

.disruption-card {
  border: 1px solid var(--status-flagged-border);
  background: var(--status-flagged-bg);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  margin: var(--space-4) 0;
  box-shadow: var(--shadow-lg);
}

.disruption-card__eyebrow {
  text-transform: uppercase;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--status-flagged);
}

.disruption-card h4 {
  margin: var(--space-1) 0 var(--space-3);
  font-size: var(--text-lg);
}

.disruption-card__opinions p {
  margin: 0 0 var(--space-2);
}

.disruption-card__ruling {
  margin: var(--space-3) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--status-flagged-border);
}

@keyframes disruption-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.disruption-card__opinions p,
.disruption-card__ruling {
  opacity: 0;
  animation: disruption-fade-in 400ms ease forwards;
}

.disruption-card__opinions p:nth-child(1) { animation-delay: 0ms; }
.disruption-card__opinions p:nth-child(2) { animation-delay: 150ms; }
.disruption-card__ruling { animation-delay: 300ms; }

@media (prefers-reduced-motion: reduce) {
  .disruption-card__opinions p,
  .disruption-card__ruling {
    animation: none;
    opacity: 1;
  }
}

/* --- §2.9 Refusal card ------------------------------------------------------
   Structurally distinct from a ground card -- no status stripe, an info
   icon not a stamp: "a thing we correctly declined to submit," not "a
   ground that lost." role="region" in the markup (informational,
   non-interrupting) -- never role="alert", that's reserved for §2.13. */

.refusal-card {
  display: flex;
  gap: var(--space-3);
  border: 1px solid var(--status-refused-border);
  background: var(--status-refused-bg);
  border-radius: var(--radius);
  padding: var(--space-4);
}

.refusal-card__icon {
  font-size: var(--text-lg);
  color: var(--status-refused);
  flex: none;
}

.refusal-card__heading {
  margin: 0 0 var(--space-1);
  font-weight: 700;
}

.refusal-card__claim {
  margin: 0 0 var(--space-2);
  font-style: italic;
  color: var(--text-muted);
}

.refusal-card__reason {
  margin: 0;
  font-size: var(--text-sm);
}

/* --- §2.10 Verdict banner / stamp -------------------------------------------
   Deliberately the single largest text element in the app (36px) -- the
   likely judge-screenshot frame. Colour uses the same 4-token status map
   as ground cards; both refused-irrelevant and refused-unsubstantiated
   map to --status-refused (warm brown, never red -- colour-discipline
   rule 3), differing only in their sub-label text. */

.verdict-stamp {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius);
  font-weight: 700;
}

.verdict-stamp__word {
  font-size: var(--text-2xl);
  line-height: 1.1;
}

.verdict-stamp__basis {
  font-size: var(--text-sm);
  font-weight: 500;
  opacity: 0.85;
}

.verdict-stamp--shipped {
  background: var(--status-shipped-bg);
  color: var(--status-shipped);
}

.verdict-stamp--refused-irrelevant,
.verdict-stamp--refused-unsubstantiated {
  background: var(--status-refused-bg);
  color: var(--status-refused);
}

@keyframes verdict-stamp-in {
  from { opacity: 0; transform: scale(1.4); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes verdict-stamp-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.verdict-stamp--animating {
  animation: verdict-stamp-in 320ms cubic-bezier(.2, .9, .3, 1.2);
}

@media (prefers-reduced-motion: reduce) {
  .verdict-stamp--animating {
    animation: none;
  }
  .verdict-stamp--animating.verdict-stamp--fade {
    animation: verdict-stamp-fade 320ms ease;
  }
}

/* --- §2.11 Tag (shared status component, single source of truth) ----------
   Non-interactive by contract (GOV.UK rule: tags are never links/
   buttons). Reused verbatim by the docket board, evidence cards, ground
   cards, and the tribunal timeline's collapsed-row verdict chip -- every
   other status-bearing element derives its colour from these same 4
   tokens, never a locally-invented shade (founder requirement #5). */

.tag {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

.tag--shipped { background: var(--status-shipped-bg); color: var(--status-shipped); }
.tag--refused { background: var(--status-refused-bg); color: var(--status-refused); }
.tag--flagged { background: var(--status-flagged-bg); color: var(--status-flagged); }
.tag--pending { background: var(--status-pending-bg); color: var(--status-pending); }

/* --- §2.12 Document viewer with overlay + legend ---------------------------
   Chrome around the concurrent wave's bbox overlay work: a docked stage
   plus the mandatory legend (colour-discipline rule 4: any time ≥1
   overlay colour is on screen, its legend must be too). Bbox border
   colours drawn by the overlay pipeline should use these same
   --status-*-border hex values for consistency (that pipeline renders
   pixels server-side in Python, outside this file's reach -- flagged in
   this package's handover notes). */

.doc-viewer {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.doc-viewer__stage {
  position: relative;
  background: var(--surface-sunken);
}

.doc-viewer__stage img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
}

.doc-viewer__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border-top: 1px solid var(--border);
  font-size: var(--text-xs);
}

.legend-item {
  display: inline-flex;
  align-items: center;
  color: var(--text-muted);
}

.legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-right: 4px;
}

.legend-swatch--shipped { background: var(--status-shipped-border); }
.legend-swatch--refused { background: var(--status-refused-border); }
.legend-swatch--flagged { background: var(--status-flagged-border); }
.legend-swatch--pending { background: var(--status-pending-border); }

/* --- §2.13 Empty / loading / error states -----------------------------------
   One reusable card, three content variants. --error is the only variant
   using --error/role="alert"; --empty and --loading use neutral tokens
   only. Every failure path states (a) what happened, (b) that entered
   data + deadline are unaffected, (c) one concrete next action. */

.state-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  background: var(--surface);
}

.state-card--empty,
.state-card--loading {
  color: var(--text-muted);
  text-align: center;
}

.state-card--loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

.state-card--error {
  border-color: var(--error-border);
  background: var(--error-bg);
  color: var(--error);
}

.state-card--error .state-card__heading {
  margin: 0 0 var(--space-1);
  font-weight: 700;
}

.state-card--error p:not(.state-card__heading) {
  color: var(--text);
}

.spinner {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spinner-spin 0.8s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
    opacity: 0.6;
  }
}

/* --- §2.14 Disclaimer footer -------------------------------------------------
   Present on every page (docket board + case page), never a dismissible
   modal. Persistent non-affiliation + not-legal-advice statement -- see
   UI-SPEC.md §5 for the exact civic-genre-not-NSW-brand line this
   supports. */

.disclaimer-footer {
  padding: var(--space-4) var(--space-5);
  color: var(--text-muted);
  font-size: var(--text-xs);
  border-top: 1px solid var(--border);
  text-align: center;
}

/* --- §3.8 Check-answers summary list ----------------------------------------
   GOV.UK summary-list pattern for the interview CONFIRMING/DONE recap. */

.check-answers h3 {
  font-size: var(--text-lg);
}

.summary-list {
  /* Two columns (key, value) -- `_render_check_answers_section` (app.py)
     emits exactly one global "Change something" link *after* the `<dl>`,
     never a per-row action cell, so a third `max-content` column here was
     a mismatch with the real DOM: `.summary-list__row { display: contents }`
     flattens each row's two children (`dt`, `dd`) straight into the grid,
     and against a 3-column template that desyncs every row after the
     first -- confirmed live on the deployed check-answers screen ("Ground
     2"'s label landing in row 1's third column, its answer text forced
     into a narrow max-content column of its own on row 2, wildly
     overflowing the card). Fixed to match the actual 2-cells-per-row DOM. */
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-4);
}

.summary-list__row {
  display: contents;
}

.summary-list dt {
  color: var(--text-muted);
  font-weight: 500;
}

.summary-list dd {
  margin: 0;
}

.summary-list__change {
  color: var(--accent);
  font-size: var(--text-sm);
  text-decoration: none;
}

.summary-list__change:hover {
  text-decoration: underline;
}

.summary-list__change:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* --- Buttons: primary action + outline variant -----------------------------
   .button--primary is the explicit affirmative-action class the
   check-answers "Start the tribunal check" button uses (§3.8); the bare
   `button` rule above stays the default for every existing untouched
   form. .button--outline is for de-emphasised actions that still need a
   visible boundary -- e.g. the §3.7 document-download links, which
   should read as buttons, not lost mid-sentence as inline text links. */

.button--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.button--primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.button--outline {
  display: inline-block;
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  margin-right: var(--space-2);
}

.button--outline:hover {
  background: var(--accent-soft);
}

.button--outline:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* --- §3.1 Docket card ---------------------------------------------------
   Replaces the bare docket-row text line with the shared .tag component
   for an overall case status; the raw case-id hash moves to a title
   attribute / monospace footer rather than the primary line. */

.docket-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
  box-shadow: var(--shadow);
}

.docket-card:hover {
  border-color: var(--accent);
}

.docket-card:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.docket-card__main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.docket-card__app {
  font-weight: 600;
}

.docket-card__address {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.docket-card__id {
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.docket-card__earlier {
  font-size: var(--text-xs);
  color: var(--text-muted);
  white-space: nowrap;
}

/* =============================================================================
   Wave 9 (LEO-FEEDBACK-UIUX.md) -- founder feedback round.
   Landing page, two-pane case layout, ground accordion, theme toggle,
   typing indicator, copy-link/copy-text actions, and a lightbox for the
   annotated overlay. Extends every token/component above; nothing here
   redefines an existing selector's meaning. */

/* --- shared small utilities ------------------------------------------------ */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.button--secondary {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
  font-weight: 600;
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.button--secondary:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* --- landing page (§1): public, minimal, one DA-number input -------------- */

.landing {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.landing__theme-toggle {
  display: flex;
  justify-content: flex-end;
  padding: var(--space-4) var(--space-5) 0;
}

.landing__main {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
}

.landing__title {
  margin: 0;
  font-size: 3rem;
  letter-spacing: -0.02em;
}

.landing__tagline {
  margin: 0 0 var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-lg);
}

.landing__form {
  display: flex;
  gap: var(--space-2);
  width: 100%;
  max-width: 440px;
}

.landing__form input[type="text"] {
  flex: 1;
  padding: var(--space-3) var(--space-4);
  border: 2px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-size: var(--text-base);
}

.landing__form input[type="text"]:focus-visible {
  border-color: var(--accent);
  outline: none;
}

.landing__form button {
  white-space: nowrap;
}

.landing__error {
  color: var(--error);
  font-size: var(--text-sm);
  max-width: 440px;
}

.previous-cases {
  margin-top: var(--space-6);
  width: 100%;
  max-width: 440px;
}

.previous-cases h2 {
  font-size: var(--text-sm);
  color: var(--text-muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.previous-cases__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.previous-cases__list a {
  display: block;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
}

.previous-cases__list a:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* --- header actions shared by all three pages ------------------------------ */

.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.topbar h1 a {
  color: inherit;
  text-decoration: none;
}

.topbar__actions,
.case-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.case-actions__qr {
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
}

/* --- two-pane case layout (§2/§9): chat fixed left, sections scroll right - */

.case-layout {
  display: grid;
  grid-template-columns: minmax(320px, 38%) 1fr;
  align-items: start;
  gap: var(--space-5);
  max-width: 1400px;
  margin: 0 auto;
  padding: var(--space-5) var(--space-5) var(--space-8);
}

.case-layout__chat {
  position: sticky;
  top: var(--space-5);
  /* `height` (not just `max-height`) is load-bearing here: a percentage
     height on `.chat-card` below only resolves against a *definite*
     ancestor height, and a flex child's `flex-grow` only fills a
     *bounded* container -- with `max-height` alone (wave-11) this box's
     own height stayed auto/content-driven, so an ever-growing transcript
     just kept pushing this box (and the whole page) taller instead of
     scrolling internally.

     The subtraction is `var(--header-height)` (app.js-measured real
     `.topbar` height) plus two `--space-5` gaps -- one above the pane
     (this `top`), one below it -- NOT the old wave-11 `--space-8` guess:
     this pane sits in normal document flow *below* the header, not at
     the viewport top, until the page is scrolled far enough for `top:
     var(--space-5)` to engage `position: sticky`. Sizing against a
     constant that ignores the header's real height left the pane taller
     than the space actually available on first load, pushing its own
     input row and buttons below the fold (caught live, wave-12 browser
     QA). `100dvh` (with a `100vh` fallback for browsers that don't parse
     the `dvh` unit) is the "based on the screen" the founder asked for --
     it tracks the real visible viewport, mobile browser chrome included. */
  height: calc(100vh - var(--header-height) - var(--space-5) * 2);
  height: calc(100dvh - var(--header-height) - var(--space-5) * 2);
  max-height: calc(100vh - var(--header-height) - var(--space-5) * 2);
  max-height: calc(100dvh - var(--header-height) - var(--space-5) * 2);
  display: flex;
  min-height: 0;
}

.case-layout__chat .chat-card {
  width: 100%;
  height: 100%;
  min-height: 0;
}

.case-layout__chat .chat-transcript {
  flex: 1 1 auto;
  max-height: none;
  /* Flex items default to `min-height: auto`, which resolves to the
     content's intrinsic height and silently defeats `overflow-y: auto`
     (the item just grows to fit every message instead of scrolling) --
     `min-height: 0` is what actually lets this pane's fixed height above
     cap the transcript and hand the overflow to its own scrollbar. */
  min-height: 0;
}

.case-layout__sections {
  min-width: 0;
}

.chat-card__export {
  align-self: flex-start;
  font-size: var(--text-sm);
  color: var(--accent);
  text-decoration: none;
}

.chat-card__export:hover {
  text-decoration: underline;
}

@media (max-width: 860px) {
  .case-layout {
    display: block;
    padding: var(--space-4);
  }

  .case-layout__chat {
    position: static;
    /* Undoes the desktop `height`/`max-height` pairing above -- single-
       column mobile has no sticky pane to bound, so the chat card is
       free to size to its own content again; `.chat-transcript` gets its
       own bounded `max-height: 50vh` + scroll further down instead. */
    height: auto;
    max-height: none;
    margin-bottom: var(--space-5);
  }
}

/* --- real tabs (round 2, item 1): replaces the sticky ref-link nav -------
   Was a `<nav>` of `<a href="#id">` anchors -- every section always
   rendered, "selected" was purely a matter of scroll position, and there
   was no visible selected state at all (the founder's own bug report).
   `.tab`/`.section-tabs` implement a real WAI-ARIA tablist: exactly one
   `[role="tabpanel"]` visible at a time (`app.js` toggles `hidden`), a
   clear selected state via `aria-selected`, built from the same design
   tokens as every other component on this page. */

.section-tabs {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) 0 var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--bg);
}

.tab {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  min-height: 44px;
}

.tab:hover {
  color: var(--accent);
  border-color: var(--border-strong);
}

.tab[aria-selected="true"] {
  background: var(--accent);
  color: var(--accent-contrast);
  border-color: var(--accent);
}

.tab:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.section-tabpanels {
  min-width: 0;
}

.section-tabpanels > [role="tabpanel"]:focus-visible {
  outline: none;
}

/* --- typing indicator (§2): "I don't know if the chat is broken" fix ------ */

.typing-indicator {
  align-self: flex-start;
  display: flex;
  gap: 4px;
  padding: var(--space-2) 0;
}

/* Found live (wave-9 browser smoke check): an author `display: flex` on
   the base selector always beats the UA stylesheet's `[hidden] { display:
   none }`, so the dots stayed visible even while `app.js` correctly set
   the `hidden` attribute -- the resident would see a permanent "thinking"
   animation regardless of the real pending state. */
.typing-indicator[hidden] {
  display: none;
}

.typing-indicator span {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-muted);
  animation: setback-typing-bounce 1.2s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) { animation-delay: 0.15s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.3s; }

@keyframes setback-typing-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
  30% { transform: translateY(-4px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .typing-indicator span {
    animation: none;
    opacity: 0.8;
  }
}

#interview-input:disabled,
#interview-form button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* --- ground accordion (§3): clamped one-liner + status pill, merged
   reviewer opinions + gate decision inside the expansion ------------------ */

.ground-card__accordion {
  width: 100%;
}

.ground-card__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  cursor: pointer;
  list-style: none;
  padding: var(--space-3) var(--space-4);
}

.ground-card__summary::-webkit-details-marker {
  display: none;
}

.ground-card__summary::before {
  content: "\25B8"; /* ▸ */
  color: var(--text-muted);
  margin-right: var(--space-2);
  transition: transform 150ms ease;
  flex: none;
}

.ground-card__accordion[open] > .ground-card__summary::before {
  transform: rotate(90deg);
}

.ground-card__claim {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-lg);
}

.ground-card__body {
  padding-top: var(--space-3);
}

.ground-card__opinions {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

.ground-card__opinions h5 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

/* --- document actions (§6): Copy text + Email this primary, HTML
   download secondary; Markdown removed from the UI entirely ----------------*/

.document-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 var(--space-4);
}

.document-downloads__html {
  font-size: var(--text-sm);
  color: var(--text-muted);
  text-decoration: underline;
}

.document-downloads__html:hover {
  color: var(--accent);
}

/* --- clickable evidence cards (§4) ----------------------------------------*/

.doc-card--clickable {
  text-decoration: none;
  cursor: pointer;
}

.doc-card--clickable:hover {
  border-color: var(--accent);
}

/* --- lightbox (§5): click the annotated overlay to see it at full size --- */

.doc-viewer__stage-image--clickable {
  cursor: zoom-in;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  background: rgba(10, 9, 7, 0.85);
  padding: var(--space-6);
}

/* Same author-vs-UA-stylesheet bug as `.typing-indicator[hidden]` above,
   found in the same browser smoke pass: `display: flex` on the base
   selector otherwise beats `[hidden] { display: none }` regardless of the
   `hidden` property JS sets, leaving the scrim visibly stuck on screen. */
.lightbox[hidden] {
  display: none;
}

.lightbox__image {
  max-width: 100%;
  max-height: 80vh;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
}

/* Mandatory attribution text (the Street View fallback's Google credit)
   shown under the image -- absent/hidden for a card with nothing to
   cite (a resident's own photo), see `openLightbox` in app.js. */
.lightbox__caption {
  margin: 0;
  max-width: 60ch;
  text-align: center;
  color: rgba(255, 255, 255, 0.85);
  font-size: var(--text-sm);
}

.lightbox__caption[hidden] {
  display: none;
}

.lightbox__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-5);
  background: transparent;
  border: none;
  color: #fff;
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  padding: var(--space-2);
}

/* =============================================================================
   Round 2 (founder feedback, this morning): real tabs (above), the case
   header's meta line, a genuinely one-line chat input row, and a mobile
   pass across the case page + landing page using only the tokens already
   defined above -- no new colours, no new type scale. */

/* --- case header meta line (item 4) ---------------------------------------
   Replaces the standalone "Tribunal" tab/section: the tribunal-start
   timestamp (exact DD/MM/YYYY HH:MM AM/PM format) and, once non-zero, the
   run-cost figure -- both demoted here rather than dropped. */

.case-meta {
  margin: 0.15rem 0 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
  word-break: break-word;
}

/* --- one-line chat input row (item 3) --------------------------------------
   "User answer input text | Send button | Upload button", all in one
   `<form>`, one flex row. The native file input stays in the DOM (for the
   browser's own file picker) but is `.visually-hidden` -- its default
   "Choose file / No file chosen" text is never shown; `#upload-trigger` is
   the only upload control a resident actually sees. */

.chat-form__send,
.chat-form__upload {
  flex: none;
  white-space: nowrap;
  min-height: 44px;
}

.chat-form__upload {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding-inline: var(--space-3);
}

.upload-chip {
  margin: var(--space-1) 0 0;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text-muted);
  font-size: var(--text-xs);
  display: inline-block;
}

.upload-chip--error {
  background: var(--error-bg);
  color: var(--error);
}

/* --- case notes card (item 4) -----------------------------------------------
   The Grounds tab's small "Notes" card: the live-ingest-source line and
   the tribunal-rerun-ignored notice, relocated from the removed
   "Tribunal" tab/section. Plain `.event-list li` styling already covers
   `.case-note` -- no bespoke rule needed beyond a touch of breathing room
   ahead of the grounds list itself. */

.case-notes {
  margin-bottom: var(--space-4);
}

/* --- mobile pass (item 2): 390px / 768px / desktop -------------------------
   Uses only the existing spacing/type tokens; the case-layout breakpoint
   above already stacks chat-first (the chat `<aside>` sits before the
   sections `<div>` in document order, so `display: block` alone puts the
   interview first on a narrow screen, which is the primary mobile
   activity per the founder's own framing). This block rounds out the rest:
   comfortable tap targets, no page-level horizontal scroll, and a bounded
   (rather than ever-growing) transcript height once the layout is a
   single column. */

@media (max-width: 860px) {
  .case-layout__chat .chat-transcript {
    max-height: 50vh;
    max-height: 50dvh;
  }
}

@media (max-width: 480px) {
  .landing__title {
    font-size: 2.25rem;
  }

  .landing__form {
    flex-direction: column;
  }

  .landing__form button {
    width: 100%;
  }

  .tagline,
  .case-meta {
    word-break: break-word;
  }

  .case-actions__qr {
    width: 48px;
    height: 48px;
  }

  /* Compact the one-liner's two buttons just enough that input + Send +
     Upload always hold on one line at this width, per the founder's own
     "the three controls on one flex line that holds at 390px" spec --
     the text input (flex: 1 1 auto, min-width: 0) absorbs the remaining
     shrink, never the buttons themselves. */
  .chat-form__send,
  .chat-form__upload {
    padding-inline: var(--space-2);
    font-size: var(--text-sm);
  }

  .chat-form__upload-label {
    display: inline;
  }

  button,
  .chip,
  .tab,
  .button--secondary,
  .button--outline,
  .chat-form input[type="text"],
  .landing__form input[type="text"],
  .create-case-form input[type="text"] {
    min-height: 44px;
  }
}
