/* Integra Workbench — component styles.
 * Consumes semantic tokens from tokens.css only. No raw hex here
 * (enforced by tools/css_gate.sh). */

/* ── Vendored type: IBM Plex Sans (UI/prose) + IBM Plex Mono (technical data).
 * Self-hosted under /static/vendor/fonts so the workbench renders offline;
 * font-display:swap falls back to the system stack until the file loads. ── */
@font-face { font-family: "IBM Plex Sans"; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/vendor/fonts/ibm-plex-sans-400.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Sans"; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/vendor/fonts/ibm-plex-sans-500.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Sans"; font-style: normal; font-weight: 600; font-display: swap; src: url("/static/vendor/fonts/ibm-plex-sans-600.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/static/vendor/fonts/ibm-plex-mono-400.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("/static/vendor/fonts/ibm-plex-mono-500.woff2") format("woff2"); }

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

/* ── Base layer ── */
body {
  font-family: var(--font-sans);
  font-size: var(--font-size-md);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-primary);
  background: var(--color-bg-page);
  margin: 0;
  overflow-wrap: break-word;
}

h1, h2, h3, h4 { line-height: 1.3; }
h1 { font-size: 18px; font-weight: 650; }
h2 { font-size: 16px; font-weight: 650; }
h3 { font-size: 15px; font-weight: 600; }
h4 { font-size: var(--font-size-sm); font-weight: 600; }

.code-token {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: var(--color-bg-code);
  padding: 1px var(--space-xs);
  border-radius: var(--radius-sm);
}

pre, .code-block {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: var(--color-bg-code);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  max-width: 100%;
}

.overflow-anywhere { overflow-wrap: anywhere; }

/* Tabular numerals (narrative brief U6): codes, counts, ids, and ages align
 * digit-for-digit. body sets the default; these surfaces restate it so the
 * alignment survives any future font shorthand reset on an ancestor. */
.fault-entry, .chip-gist, .dive-cite, .dive-ticket-meta,
.ticket-id, .conv-meta {
  font-variant-numeric: tabular-nums;
}

/* ════════════════════════════════════════════════════════════════════
 * Component layer (redesign U4)
 * Cards, headers, badges, chips. Every session surface composes these;
 * legacy class names (.panel, .delta-badge, .conv-flag) keep
 * their JS contracts and pick up the shared shape via grouped selectors.
 * This layer sits before the section styles so per-surface rules can
 * override the shared shape where they need to.
 * ════════════════════════════════════════════════════════════════════ */

/* Card: the one surface shape. Neutral at rest — no accent edges. */
.card {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
}
.card h4 { margin: var(--space-md) 0 var(--space-sm); font-size: var(--font-size-sm); }

/* Card header: section-title register — small, heavy, tracked-out. */
.card-header {
  margin: 0 0 var(--space-md);
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-secondary);
}

/* Badge / chip: one pill shape; color comes only from a status modifier
 * (tint background + matching text), never structure. Grayscale at rest. */
.badge, .chip, .delta-badge, .conv-flag {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  font-size: 11px;
  font-weight: 600;
  padding: 1px var(--space-sm);
  border-radius: 999px;
  white-space: nowrap;
  background: var(--color-bg-code);
  color: var(--color-text-secondary);
}
.badge-fault    { background: var(--color-status-fault-bg);    color: var(--color-status-fault); }
.badge-warning  { background: var(--color-status-warning-bg);  color: var(--color-status-warning); }
.badge-waiting  { background: var(--color-status-waiting-bg);  color: var(--color-status-waiting); }
.badge-resolved { background: var(--color-status-resolved-bg); color: var(--color-status-resolved); }

/* Diagnostic / preformatted content scrolls sideways, never wraps the layout.
 * Shared shape comes from the grouped `pre, .code-block` rule above. */
.code-block {
  white-space: pre;
  margin-top: var(--space-xs);
}

/* Shared focus treatment for interactive elements. Summary elements keep the
   loud ring for keyboard focus only: some browsers leave a details summary
   focused (and :focus-visible) after a mouse click, which parked a persistent
   accent box around the case-memory bar (IW-53); chat.js blurs mouse clicks. */
button:focus-visible, summary:focus-visible, a:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}
summary:focus:not(:focus-visible) { outline: none; }

/* ════════════════════════════════════════════════════════════════════
 * App shell (redesign U3) — grid: 48px header row over a 280px queue
 * rail plus the case pane. Rail collapses to 56px icon mode by swapping
 * the column width var (wired in rail.js, IW-48).
 * ════════════════════════════════════════════════════════════════════ */
.shell {
  display: grid;
  grid-template-columns: var(--rail-width) minmax(0, 1fr);
  grid-template-rows: var(--header-height) minmax(0, 1fr);
  height: 100dvh;
  position: relative;
}
.shell.rail-collapsed { --rail-width: var(--rail-width-collapsed); }
.shell.shell-no-rail { grid-template-columns: minmax(0, 1fr); }

/* ── Header ── */
/* The 2px keyline is the shell's one deliberate brand moment (IW-50). */
.site-header {
  grid-column: 1 / -1;
  background: var(--color-bg-inverse);
  color: var(--color-text-inverse);
  border-bottom: 2px solid var(--color-accent);
  padding: 0 20px;
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  min-height: 0;
}
.site-logo {
  font-weight: 600;
  font-size: 15px;
  flex: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  letter-spacing: -0.01em;
}
.site-logo-mark { width: 22px; height: 22px; display: block; flex: none; color: var(--color-accent); }
.site-logo-mark svg { width: 100%; height: 100%; display: block; }
.header-user { font-size: var(--font-size-sm); opacity: 0.8; }
.logout-form { display: inline; }

/* Theme toggle (U5): compact glyph button in the header */
.theme-toggle {
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--color-text-inverse);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 4px 7px;
  opacity: 0.8;
}
.theme-toggle:hover { opacity: 1; border-color: var(--color-border-subtle); }
.theme-toggle svg { width: 16px; height: 16px; display: block; }

/* ── Queue rail ── */
.queue-rail {
  grid-row: 2;
  grid-column: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--color-bg-surface);
  border-right: 1px solid var(--color-border-subtle);
  font-size: var(--font-size-sm);
  line-height: 1.4;
}

/* Rail header: open-by-number + archive search (redesign U6).
   position: relative anchors the floating search-results panel. */
.rail-header {
  position: relative;
  padding: var(--space-md);
  border-bottom: 1px solid var(--color-border-subtle);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  flex: none;
}
.rail-open-row { display: flex; gap: var(--space-xs); }
.rail-open-row input { flex: 1; min-width: 0; }
.rail-open-row .btn-primary { padding: var(--space-sm) var(--space-md); flex: none; }
.rail-header .alert { margin-bottom: 0; }
.shell.rail-collapsed .rail-header { display: none; }

/* Filter and sort chips + popover (IW-49). Two compact pills under the
   search field; the popover floats over the rail list like the search
   results panel. Chips turn accent when the view is narrowed. */
.rail-filters { position: relative; display: flex; gap: var(--space-xs); }
.rail-fchip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 1px solid var(--color-border-subtle);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: var(--font-size-xs);
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  cursor: pointer;
  white-space: nowrap;
}
.rail-fchip:hover { background: var(--color-bg-hover); }
.rail-fchip.rail-fchip-active { border-color: var(--color-accent); color: var(--color-accent); }
.rail-fchip svg { width: 12px; height: 12px; flex: none; }
.rail-pop {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: var(--space-xs);
  z-index: 21;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-raised);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-sm);
}
.pop-h {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: var(--space-sm) 0 2px;
}
.pop-h:first-child { margin-top: 0; }
.pop-opt {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 2px 0;
  cursor: pointer;
  color: var(--color-text-secondary);
}
.pop-opt input { accent-color: var(--color-accent); margin: 0; }
.pop-reset {
  margin-top: var(--space-sm);
  padding: 0;
  background: none;
  border: none;
  color: var(--color-accent);
  font-size: var(--font-size-xs);
  text-decoration: underline;
  cursor: pointer;
}
.rail-filter-empty {
  padding: var(--space-md);
  color: var(--color-text-muted);
  font-style: italic;
  font-size: var(--font-size-sm);
}

/* Search results: floating panel over the rail list. Anchored below the
   search input; rail rows sit inert beneath it (clicks land on the panel).
   Escape / click-outside dismissal lives in rail.js. */
.rail-search-results {
  display: none;
  position: absolute;
  top: 100%;
  left: var(--space-sm);
  right: var(--space-sm);
  margin-top: var(--space-xs);
  z-index: 20;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-raised);
  padding: var(--space-sm) var(--space-md);
  max-height: 60vh;
  overflow-y: auto;
  font-size: var(--font-size-sm);
}
.rail-search-results.open { display: block; }

.rail-list { flex: 1 0 auto; padding: var(--space-xs) 0; }

/* Rail states: skeleton (shimmer-free dim blocks), empty, refreshed-at */
.rail-skeleton-row {
  height: 18px;
  margin: 9px var(--space-md);
  border-radius: var(--radius-sm);
  background: var(--color-bg-hover);
}
.rail-empty {
  padding: var(--space-md);
  color: var(--color-text-muted);
  font-style: italic;
}
.rail-footer {
  flex: none;
  padding: var(--space-sm) var(--space-md);
  border-top: 1px solid var(--color-border-subtle);
  position: sticky;
  bottom: 0;
  background: var(--color-bg-surface);
}
.shell.rail-collapsed .rail-footer { display: none; }
.rail-refreshed { font-size: 11px; color: var(--color-text-muted); }
.rail-refreshed-failed { color: var(--color-status-fault); }

/* Legend: the icon language, learnable without hovering (IW-48). */
.rail-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  padding-bottom: var(--space-xs);
  font-size: 10.5px;
  color: var(--color-text-muted);
}
.rail-legend span { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.rail-legend svg { width: 11px; height: 11px; flex: none; }

/* Collapse chevron, pinned at the rail/pane boundary (wired in rail.js, IW-48) */
.rail-collapse-btn {
  position: absolute;
  top: 64px;
  left: var(--rail-width);
  transform: translateX(-50%);
  z-index: 5;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--color-border-subtle);
  background: var(--color-bg-raised);
  color: var(--color-text-secondary);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.rail-collapse-btn:hover { background: var(--color-bg-hover); }
.shell.shell-no-rail .rail-collapse-btn { display: none; }

/* ── Case pane ── */
.case-pane {
  grid-row: 2;
  grid-column: 2;
  overflow-y: auto;
  min-height: 0;
  padding: 20px;
}
.shell.shell-no-rail .case-pane { grid-column: 1; }
.case-pane-inner { max-width: 960px; margin: 0 auto; }
.pane-empty {
  padding: 48px 20px;
  text-align: center;
  color: var(--color-text-muted);
}
/* Empty state (IW-50): the blank pane teaches the two fastest paths in. */
.empty-card { max-width: 420px; margin: 10vh auto 0; }
.empty-mark {
  display: inline-block;
  width: 40px;
  height: 40px;
  color: var(--color-accent);
  opacity: 0.85;
  margin-bottom: var(--space-sm);
}
.empty-mark svg { width: 100%; height: 100%; display: block; }
.empty-title {
  font-weight: 600;
  font-size: var(--font-size-md);
  color: var(--color-text-primary);
  margin-bottom: var(--space-xs);
}
.empty-sub { font-size: var(--font-size-sm); line-height: 1.6; }
.empty-sub kbd {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  border: 1px solid var(--color-border-subtle);
  border-bottom-width: 2px;
  border-radius: var(--radius-sm);
  padding: 0 5px;
  color: var(--color-text-secondary);
}

/* ── Session warning banner ── */
.session-warning {
  background: var(--color-status-warning-bg);
  border-bottom: 1px solid var(--color-status-warning);
  padding: 6px 20px;
  font-size: var(--font-size-xs);
  color: var(--color-status-warning);
}

/* ── Layout ── */
.workbench { display: flex; flex-direction: column; gap: var(--space-lg); }

/* ── Ticket header ── */
.ticket-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
}
.ticket-id { font-weight: 700; font-size: 16px; color: var(--color-text-primary); }
.ticket-subject { font-size: 15px; color: var(--color-text-secondary); }

/* ── Resume card (case state) ── */
/* A plain card: the accent is reserved for selection and interaction, so
   the old accent edge stripe is gone (anomaly-restraint rule). */
.resume-wait {
  font-weight: 400;
  font-size: var(--font-size-xs);
  color: var(--color-status-warning);
  margin-left: var(--space-sm);
}
.resume-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px var(--space-xl);
}
.state-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: var(--font-size-sm);
  min-width: 0;
}
.state-label {
  font-weight: 600;
  color: var(--color-text-secondary);
  font-size: var(--font-size-xs);
  white-space: nowrap;
}
.state-value { cursor: pointer; border-bottom: 1px dashed var(--color-border-subtle); min-width: 40px; }
.state-value:hover { background: var(--color-bg-hover); }
.state-empty { color: var(--color-text-muted); font-style: italic; }
.state-unsaved { color: var(--color-status-fault); border-bottom-color: var(--color-status-fault); }
.state-editor { flex: 1; max-width: 220px; }
.state-error { font-size: var(--font-size-xs); color: var(--color-status-fault); }

/* ── State proposals (resume card + in-session strip) ── */
.proposal-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin: var(--space-xs) 0 var(--space-sm);
}
/* The standalone strip is its own card; the copy inside the resume card
   stays a bare row (no nested cards). display toggling is app.js's. */
.proposal-strip.card { margin: 0; padding: var(--space-md) var(--space-lg); }
/* Proposal chip: the uniform pill, accent-tinted (a proposal is an
   interaction waiting on the user, not an anomaly). */
.proposal-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: var(--font-size-xs);
  padding: var(--space-xs) var(--space-md);
  border-radius: 999px;
  background: var(--color-bg-selected);
  border: 1px solid var(--color-border-subtle);
  color: var(--color-text-primary);
}
.proposal-text { font-weight: 500; }
.proposal-spinner { color: var(--color-text-secondary); font-style: italic; }
.proposal-error { color: var(--color-status-fault); }

/* ── Brief sections ── */
details {
  margin-bottom: var(--space-sm);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
}
/* Brief section summaries share the card-header register. */
details summary {
  padding: var(--space-sm) var(--space-md);
  cursor: pointer;
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-secondary);
  background: var(--color-bg-page);
  border-radius: var(--radius-sm);
  list-style: none;
}
/* Transient status text inside a summary stays plain. */
details summary span {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}
details summary::-webkit-details-marker { display: none; }
details summary::before { content: "▶ "; font-size: 10px; }
details[open] summary::before { content: "▼ "; font-size: 10px; }

.section-body { padding: var(--space-md); font-size: var(--font-size-sm); }
.section-body.loading { color: var(--color-text-muted); font-style: italic; }

/* ── Dive card (narrative brief U4) ── */
/* The brief's front door: cited claims at a comfortable prose measure under
   the absorbed ticket header (typography registers per U6: claims read as
   prose, chips and drawers keep the denser data register). min-height holds
   the card's footprint from pane open, so the skeleton-to-content swap
   causes no layout shift. */
.dive-card-region { margin-bottom: var(--space-md); }
.dive-card {
  min-height: 180px;
  padding: var(--space-md) var(--space-lg);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
}
.dive-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}
.dive-ticket { min-width: 0; }
.dive-ticket.loading { color: var(--color-text-muted); font-style: italic; }
.dive-ticket-subject {
  font-size: var(--font-size-md);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.dive-ticket-meta {
  margin-top: var(--space-xs);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
.dive-controls {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
/* Freshness chip: always visible, recolored through status tokens only
   (the delta-badge honesty convention). Building/regenerating ride the
   waiting tint; stale and failed add a border so the state reads even at
   a glance. */
.dive-state-loading, .dive-state-building, .dive-state-regenerating {
  background: var(--color-status-waiting-bg);
  color: var(--color-status-waiting);
  font-style: italic;
}
.dive-state-current {
  background: var(--color-status-resolved-bg);
  color: var(--color-status-resolved);
}
.dive-state-stale {
  background: var(--color-status-warning-bg);
  color: var(--color-status-warning);
  border: 1px solid var(--color-status-warning);
}
.dive-state-unknown {
  background: var(--color-status-warning-bg);
  color: var(--color-status-warning);
}
.dive-state-failed {
  background: var(--color-status-fault-bg);
  color: var(--color-status-fault);
  border: 1px solid var(--color-status-fault);
}
/* Skeleton: fixed-shape placeholder lines, visibly not content. */
.dive-skeleton-lines { display: grid; gap: var(--space-sm); }
.dive-skeleton-line {
  height: 14px;
  border-radius: var(--radius-sm);
  background: var(--color-bg-code);
}
.dive-skeleton-line-short { width: 60%; }
/* Regenerating: the prior dive stays readable under the marker, dimmed. */
.dive-card.dive-regenerating .dive-body { opacity: 0.6; }
/* Claims: the dive's facts, plain prose at a comfortable reading register
 * (U6): ~68ch measure, 1.55 line height, regular weight. overflow-wrap
 * guards hostile unbroken runs (120-char subjects, 300-char URLs) routed
 * through a claim; the stored text is never shortened. */
.dive-claims { max-width: 68ch; }
.dive-claim {
  margin: 0 0 var(--space-sm);
  font-size: var(--font-size-md);
  font-weight: 400;
  line-height: 1.55;
  overflow-wrap: anywhere;
  min-width: 0;
}
/* Citation affordance: superscript-style chip after the claim text. */
.dive-cite {
  border: 1px solid var(--color-border-subtle);
  border-radius: 999px;
  background: var(--color-bg-code);
  color: var(--color-text-secondary);
  font-size: 10px;
  font-weight: 600;
  line-height: 1.4;
  padding: 0 var(--space-xs);
  vertical-align: super;
  cursor: pointer;
}
.dive-cite:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
/* Suggestions: a visually distinct, clearly machine-marked module. The AI
   marker chip plus its own tinted-border surface keep model proposals
   distinguishable from the claims at a glance. Read-only. */
.dive-suggestions {
  margin-top: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  max-width: 68ch;
  border-left: 3px solid var(--color-status-waiting);
  background: var(--color-bg-page);
  border-radius: var(--radius-sm);
}
.dive-suggestions-header {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-sm);
}
.dive-ai-marker {
  background: var(--color-status-waiting-bg);
  color: var(--color-status-waiting);
}
.dive-suggestion { margin-bottom: var(--space-sm); min-width: 0; }
.dive-suggestion-text {
  font-size: var(--font-size-sm);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.dive-reasoning-toggle { margin-top: var(--space-xs); }
.dive-reasoning {
  margin-top: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
  background: var(--color-bg-code);
  border-radius: var(--radius-sm);
}
.dive-failed-note { color: var(--color-text-secondary); }

/* ── Brief chip strip (narrative brief U3) ── */
/* Auxiliary sections render as chips; anomaly tints recolor through the
   shared badge-* modifiers. The strip wraps to additional rows at narrow
   widths, never overflows. */
.section-chip-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}
/* Chips are real buttons riding the shared pill shape (grouped .chip rule);
   reset the button chrome on top of it. */
.section-chip {
  border: 1px solid var(--color-border-subtle);
  cursor: pointer;
  font-family: inherit;
  padding: var(--space-xs) var(--space-md);
  max-width: 100%;
  min-width: 0;
}
.section-chip:hover { background: var(--color-bg-hover); }
/* Gist: dense data register, CSS-only truncation. The chip keeps the pill's
 * nowrap; an oversized gist (hostile 120-char subject) ellipsizes here while
 * applySectionState mirrors the full text into the chip's title attribute. */
.section-chip .chip-gist {
  font-weight: 400;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Loading state: italic, muted, distinct from the empty state in the drawer. */
.section-chip.chip-loading { color: var(--color-text-muted); font-style: italic; }
/* Nominal sections render muted; anomalous chips recolor via badge-warning /
   badge-waiting (tint background + matching text). */
.section-chip.chip-nominal { color: var(--color-text-muted); }
/* Failed (stored copy incomplete): the chip is the retry affordance. */
.section-chip.chip-failed { border-style: dashed; border-color: var(--color-status-warning); }

/* ── Section drawer (narrative brief U3) ── */
/* One drawer: full pane height, internal scroll, overlays the case pane
   from the right. Raised-surface treatment like the search panel. */
.section-drawer {
  display: none;
  position: fixed;
  top: var(--header-height);
  right: 0;
  bottom: 0;
  width: min(440px, 85vw);
  z-index: 30;
  flex-direction: column;
  background: var(--color-bg-raised);
  border-left: 1px solid var(--color-border-subtle);
  box-shadow: var(--shadow-raised);
}
.section-drawer.open { display: flex; }
.drawer-header {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--color-border-subtle);
}
.drawer-header .card-header { margin: 0; }
.drawer-close { flex: none; }
.drawer-body {
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
  padding: var(--space-md) var(--space-lg);
  font-size: var(--font-size-sm);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
/* Loading vs empty: loading is italic muted, the empty state is upright
   secondary text under its section heading. Visually distinct. */
.drawer-loading { color: var(--color-text-muted); font-style: italic; }
.drawer-empty { color: var(--color-text-secondary); }
.drawer-empty-heading {
  display: block;
  font-weight: 600;
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-xs);
}
.drawer-note { color: var(--color-text-secondary); }

/* ── Decoded faults (drawer content) ── */
.fault-entry {
  max-width: 72ch;
  margin-bottom: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--color-bg-page);
  border-radius: var(--radius-sm);
  overflow-wrap: anywhere;
  min-width: 0;
}
.fault-matched {
  margin-top: var(--space-xs);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

/* ── Conversations ── */
/* Bordered message blocks at a readable measure; metadata line above the
   body. Calm: borders, not zebra fills. */
.conv-entry {
  max-width: 72ch;
  margin-bottom: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
}
.conv-meta { font-size: var(--font-size-xs); color: var(--color-text-muted); margin-bottom: var(--space-xs); }
.conv-date { margin-left: var(--space-sm); }
/* Bodies wrap hostile unbroken runs (300-char URLs) in place; the show-more
 * remainder span (U5) inherits the same guard, so expansion never overruns. */
.conv-body {
  white-space: pre-wrap;
  font-size: var(--font-size-md);
  line-height: 1.5;
  overflow-wrap: anywhere;
  min-width: 0;
}
/* Delta anomalies: the only chromatic message blocks. */
.conv-new {
  background: var(--color-status-resolved-bg);
  border-color: var(--color-status-resolved);
}
.conv-edited {
  background: var(--color-status-warning-bg);
  border-color: var(--color-status-warning);
}
.conv-flag { text-transform: uppercase; }
/* Show-earlier header (U5): quiet full-width toggle above the thread. The
   badge surfaces new/edited counts hidden inside the collapsed region. */
.conv-show-earlier {
  display: block;
  width: 100%;
  max-width: 72ch;
  margin-bottom: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  background: none;
  border: 1px dashed var(--color-border-subtle);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  cursor: pointer;
}
.conv-show-earlier:hover {
  color: var(--color-text-secondary);
  background: var(--color-bg-hover);
}
.conv-earlier-badge {
  display: inline-flex;
  margin-left: var(--space-sm);
  padding: 1px var(--space-sm);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  background: var(--color-status-warning-bg);
  color: var(--color-status-warning);
}
/* Per-message show-more (U5): the body cap is display-only; the hidden
   remainder span carries the rest of the stored text. */
.conv-show-more { margin-top: var(--space-xs); }
.conv-flag-new { background: var(--color-status-resolved-bg); color: var(--color-status-resolved); }
.conv-flag-edited { background: var(--color-status-warning-bg); color: var(--color-status-warning); }

/* ── Delta badge (fetch state after replay) ── */
/* Pill shape from the badge base; states recolor via status tokens only. */
.delta-badge { cursor: pointer; }
.delta-fetching, .delta-current { background: var(--color-status-waiting-bg); color: var(--color-status-waiting); }
.delta-has-new {
  background: var(--color-status-resolved-bg);
  color: var(--color-status-resolved);
  border: 1px solid var(--color-status-resolved);
}
.delta-failed {
  background: var(--color-status-fault-bg);
  color: var(--color-status-fault);
  border: 1px solid var(--color-status-fault);
}

/* Brief panel showing stored content after a failed delta fetch */
.panel.stale { outline: 1px dashed var(--color-status-fault); }

/* ── Rebuild-needed marker (failed/pending stored section) ── */
/* Generation status is an anomaly: warning tint block. */
.rebuild-needed {
  color: var(--color-status-warning);
  font-size: var(--font-size-sm);
  background: var(--color-status-warning-bg);
  border: 1px solid var(--color-status-warning);
  border-radius: var(--radius-sm);
  padding: var(--space-sm) var(--space-md);
}
.rebuild-note { font-size: var(--font-size-xs); color: var(--color-text-muted); }

/* ── Image / similar ── */
.img-entry, .similar-entry {
  max-width: 72ch;
  margin-bottom: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--color-bg-page);
  border-radius: var(--radius-sm);
}
.similar-reply { margin-top: var(--space-xs); color: var(--color-text-secondary); }

/* ── Hardware warnings ── */
/* Warning tint ONLY when warnings exist (app.js adds .hw-warnings to the
   list); the empty state stays a neutral line. */
.hw-warnings {
  margin: 0;
  padding: var(--space-sm) var(--space-md) var(--space-sm) 28px;
  background: var(--color-status-warning-bg);
  border: 1px solid var(--color-status-warning);
  border-radius: var(--radius-sm);
  color: var(--color-status-warning);
}

/* ── Chat ── */
.chat-history { min-height: 60px; max-height: 300px; overflow-y: auto; margin-bottom: var(--space-md); }
.chat-turn {
  max-width: 72ch;
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--color-border-subtle);
  font-size: var(--font-size-md);
  line-height: 1.5;
}
.chat-turn .chat-meta, .chat-turn strong { font-size: var(--font-size-xs); }
.chat-turn.user strong { color: var(--color-text-primary); }
.chat-turn.assistant strong { color: var(--color-text-secondary); }
.chat-input-row { display: flex; gap: var(--space-sm); }
.chat-input-row input { flex: 1; }

/* ── Draft / note textareas ── */
.draft-textarea {
  width: 100%;
  min-height: 140px;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--font-size-sm);
  resize: vertical;
  margin-bottom: var(--space-sm);
}
.draft-controls { margin-bottom: var(--space-sm); display: flex; gap: var(--space-sm); flex-wrap: wrap; }
.draft-actions { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; }
.streaming-indicator { font-size: var(--font-size-xs); color: var(--color-text-muted); margin-bottom: 6px; }
.copy-confirm { font-size: var(--font-size-xs); color: var(--color-status-resolved); }
.copy-fallback { font-size: var(--font-size-xs); color: var(--color-text-muted); margin-top: var(--space-sm); }
.note-status { font-size: var(--font-size-xs); color: var(--color-text-secondary); }

/* ── RMA ── */
.rma-result {
  margin-top: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: var(--color-status-resolved-bg);
  border-radius: var(--radius-sm);
}
.rma-result a { color: var(--color-accent); }

/* ── Forms ── */
.field { margin-bottom: var(--space-md); }
/* Explanatory text under an input, on the standalone login/setup cards where
   there is no console panel to carry an .admin-note (IW-128). */
.field-hint {
  margin: var(--space-xs) 0 0;
  font-size: var(--font-size-xs);
  line-height: 1.5;
  color: var(--color-text-muted);
}
.field-hint code { word-break: break-all; }
.field label {
  display: block;
  font-size: var(--font-size-xs);
  font-weight: 600;
  margin-bottom: var(--space-xs);
  color: var(--color-text-secondary);
}
input[type=text], input[type=number], input[type=password], input[type=search],
textarea, select {
  width: 100%;
  padding: 7px 9px;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--font-size-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
}
input:focus, textarea:focus { outline: 2px solid var(--color-accent); border-color: transparent; }

/* ── Buttons ── */
.btn-primary {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
  border: none;
  padding: var(--space-sm) 18px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-size-sm);
  font-weight: 500;
}
.btn-primary:hover { background: var(--color-accent-hover); }
/* Send flips to Stop while a turn runs: an amber, quieter affordance to interrupt. */
.btn-primary.btn-stop { background: var(--color-status-warning); color: var(--color-text-on-accent); }
.btn-primary.btn-stop:hover { filter: brightness(0.95); }
.btn-secondary {
  background: var(--color-bg-page);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-subtle);
  padding: 7px var(--space-lg);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-size-sm);
}
.btn-secondary:hover { background: var(--color-bg-hover); }
.btn-danger {
  background: var(--color-status-fault);
  color: var(--color-text-on-status);
  border: none;
  padding: var(--space-sm) 18px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-size-sm);
}
.btn-danger:hover { filter: brightness(0.9); }
.btn-danger:disabled {
  background: var(--color-border-subtle);
  color: var(--color-text-muted);
  cursor: default;
  filter: none;
}
.btn-small {
  background: none;
  border: 1px solid var(--color-text-muted);
  color: var(--color-text-primary);
  padding: 2px var(--space-sm);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-size-xs);
}
.btn-small:hover { background: var(--color-bg-hover); }
.btn-link {
  background: none;
  border: none;
  color: var(--color-text-inverse);
  opacity: 0.7;
  cursor: pointer;
  font-size: var(--font-size-sm);
  padding: 0;
  text-decoration: underline;
}
.btn-link:hover { opacity: 1; }

/* ── Alerts ── */
.alert {
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-md);
  font-size: var(--font-size-sm);
}
.alert-error {
  background: var(--color-status-fault-bg);
  color: var(--color-status-fault);
  border: 1px solid var(--color-status-fault);
}
.alert-warn {
  background: var(--color-status-warning-bg);
  color: var(--color-status-warning);
  border: 1px solid var(--color-status-warning);
}
/* Success flash (settings consoles: "credential saved", "setting saved"). */
.alert-ok {
  background: var(--color-status-resolved-bg);
  color: var(--color-status-resolved);
  border: 1px solid var(--color-status-resolved);
}

/* ── Login page ── */
/* Bebas Neue lives on this page only (IW-50): the display face for the brand
   lockup over the charcoal ground; the working UI stays IBM Plex. */
@font-face {
  font-family: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/vendor/fonts/bebas-neue-400.woff2") format("woff2");
}
.login-page body, body.login-page { background: var(--color-bg-inverse); }
.login-lockup { text-align: center; margin: 12vh auto 28px; }
.login-mark {
  display: inline-block;
  width: 72px;
  height: 72px;
  color: var(--color-accent);
  margin-bottom: var(--space-md);
}
.login-mark svg { width: 100%; height: 100%; display: block; }
.login-wordmark {
  font-family: "Bebas Neue", var(--font-sans);
  font-size: 38px;
  letter-spacing: 0.04em;
  line-height: 1;
  color: var(--color-text-inverse);
}
.login-card {
  max-width: 380px;
  margin: 0 auto 60px;
  background: var(--color-bg-surface);
  padding: 32px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

/* ── Archive search results (rendered inside the rail panel) ── */
.search-hit {
  padding: 10px 0;
  border-top: 1px solid var(--color-border-subtle);
}
.search-hit:first-child { border-top: none; }
.search-ticket-link { font-weight: 600; text-decoration: none; color: var(--color-accent); }
.search-ticket-link:hover { text-decoration: underline; }
.search-match { margin: var(--space-xs) 0 0 var(--space-md); color: var(--color-text-secondary); }
.search-type {
  display: inline-block;
  background: var(--color-bg-code);
  color: var(--color-text-secondary);
  border-radius: 3px;
  padding: 1px 6px;
  margin-right: 6px;
  font-size: 11px;
}
.search-snippet { color: var(--color-text-secondary); }

/* ── Requester recall (session page) ── */
.requester-cases {
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
  margin-bottom: var(--space-md);
}
.requester-cases a { font-weight: 600; text-decoration: none; color: var(--color-accent); }
.requester-cases a:hover { text-decoration: underline; }

/* ── Chat-primary shell (U5) ─────────────────────────────────────────────── */
/* Chat is the one full-height view: the pane itself must not scroll (the transcript does),
   so the composer can pin to the bottom like Claude.ai. Scoped with :has() so every other
   case-pane view keeps its normal scrolling behavior. */
.case-pane:has(.chat-workbench) {
  overflow: hidden;
  padding: 0;
}
.case-pane:has(.chat-workbench) .case-pane-inner {
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 0 20px;
}

.chat-workbench {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}
.chat-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--color-border-subtle);
}
.chat-header .ticket-id { font-weight: 700; color: var(--color-accent); flex: none; }
.chat-header .ticket-customer { color: var(--color-text-primary); font-weight: 500; flex: none; }
.chat-header .ticket-subject {
  color: var(--color-text-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Collapse the placeholders before /ticket-meta resolves so the gap stays tight. */
.chat-header .ticket-customer:empty,
.chat-header .ticket-subject:empty { display: none; }
.ticket-fd-link {
  margin-left: auto;
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-decoration: none;
  white-space: nowrap;
}
.ticket-fd-link:hover { color: var(--color-accent); text-decoration: underline; }

.chat-timeline {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-md) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.msg { max-width: 90%; }
.msg-user {
  align-self: flex-end;
  background: var(--color-bg-selected);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
}
/* ── The work log (IW-102) ────────────────────────────────────────────────────
 *
 * One assistant turn is one logged operation: passages of prose interrupted by quiet,
 * precise records of work. A hairline rail runs down the turn and ticks at every block
 * on it, so the shape of the turn — how much the agent said, how often it stopped to do
 * something — reads at a glance without opening anything.
 *
 * The rail is where this surface spends its boldness. The tick is the only place the
 * brand accent appears inside the transcript, and it carries a meaning rather than an
 * emphasis: a magenta tick is the agent acting, a hairline tick is the agent writing.
 * Everything else on the turn stays disciplined. */
.msg-assistant {
  align-self: flex-start;
  width: 100%;
  padding-left: var(--space-lg);
  border-left: 1px solid var(--color-ledger-rail);
}
.msg-text { overflow-wrap: anywhere; color: var(--color-text-primary); }

/* Ticks mark WORK, not every block. An earlier pass ticked the prose segments too, which
   made the tick a bullet rather than a signal: if every block has one, the rail stops
   telling you anything you could not already see. Unbroken rail beside prose, a tick
   where the agent did something, is the whole reading. */
.msg-assistant > .tool-group,
.msg-assistant > .artifact-card,
.msg-assistant > .approval-card { position: relative; }
.msg-assistant > .tool-group::before,
.msg-assistant > .artifact-card::before,
.msg-assistant > .approval-card::before {
  content: "";
  position: absolute;
  left: calc(var(--space-lg) * -1);
  width: var(--space-sm);
  height: 2px;
  background: var(--color-ledger-tick);
}
.msg-assistant > .tool-group::before { top: 0.85em; }
.msg-assistant > .artifact-card::before,
.msg-assistant > .approval-card::before { top: calc(var(--space-md) + 0.6em); }
/* An interrupted turn's rail goes dashed: the operation stopped partway rather than
   finishing, and the rail is the element that says so about the turn as a whole. */
.msg-assistant.msg-interrupted { border-left-style: dashed; }

/* Chronological segments: one turn holds a passage of prose per stretch of writing
   between tool calls, so the gap that used to sit only between turns now has to sit
   between the blocks inside one. */
.msg-assistant > * + * { margin-top: var(--space-md); }

/* /help output: a readable command list, one row per command, name aligned left. */
.help-block { display: flex; flex-direction: column; gap: var(--space-xs); }
.help-heading { font-weight: 600; margin-bottom: var(--space-xs, 6px); color: var(--color-text-primary); }
.help-row {
  display: grid;
  grid-template-columns: minmax(9rem, max-content) 1fr;
  gap: var(--space-sm, 10px);
  align-items: baseline;
}
.help-cmd {
  font-family: var(--font-mono, ui-monospace, monospace);
  color: var(--color-accent, var(--color-text-primary));
  white-space: nowrap;
}
.help-desc { color: var(--color-text-secondary, var(--color-text-primary)); }
/* User turns are escaped plain text, so preserve their line breaks. Assistant turns are
   rendered markdown (real block elements), so they manage their own spacing. */
.msg-user .msg-text { white-space: pre-wrap; }

/* Rendered markdown inside assistant turns. This is the one surface an operator reads
   for minutes at a time, so it takes a reading treatment rather than the chat-chrome
   scale: a step up in size, a looser leading, and a capped measure on running text.
   The measure is set on the paragraph-level children rather than on the block, so a
   wide table or a fenced code sample still uses the full pane. */
.msg-assistant .msg-text {
  font-size: var(--font-size-prose);
  line-height: var(--line-height-prose);
}
.msg-assistant .msg-text > p,
.msg-assistant .msg-text > ul,
.msg-assistant .msg-text > ol,
.msg-assistant .msg-text > blockquote,
.msg-assistant .msg-text > h1,
.msg-assistant .msg-text > h2,
.msg-assistant .msg-text > h3,
.msg-assistant .msg-text > h4 { max-width: var(--measure-prose); }
.msg-assistant .msg-text > pre,
.msg-assistant .msg-text > table { max-width: var(--measure-wide); }
.msg-assistant .msg-text > :first-child { margin-top: 0; }
.msg-assistant .msg-text > :last-child { margin-bottom: 0; }
.msg-assistant .msg-text p { margin: 0 0 var(--space-md); }
.msg-assistant .msg-text h1,
.msg-assistant .msg-text h2,
.msg-assistant .msg-text h3,
.msg-assistant .msg-text h4 {
  margin: var(--space-lg) 0 var(--space-sm);
  line-height: 1.25;
  letter-spacing: -0.01em;
}
.msg-assistant .msg-text h1 { font-size: 1.4em; }
.msg-assistant .msg-text h2 { font-size: 1.25em; }
.msg-assistant .msg-text h3 { font-size: 1.1em; }
.msg-assistant .msg-text h4 { font-size: 1em; }
.msg-assistant .msg-text ul,
.msg-assistant .msg-text ol { margin: 0 0 var(--space-md); padding-left: 1.4em; }
.msg-assistant .msg-text li { margin: var(--space-xs) 0; }
.msg-assistant .msg-text li > p { margin: 0; }
.msg-assistant .msg-text li > ul,
.msg-assistant .msg-text li > ol { margin: var(--space-xs) 0 0; }
/* List markers recede: the item is the content, the bullet is punctuation. */
.msg-assistant .msg-text li::marker { color: var(--color-text-muted); }
.msg-assistant .msg-text a { color: var(--color-accent); text-decoration: underline; }
.msg-assistant .msg-text code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background: var(--color-bg-code);
  padding: 1px 4px;
  border-radius: var(--radius-sm);
}
.msg-assistant .msg-text pre {
  margin: 0 0 var(--space-md);
  padding: var(--space-md);
  background: var(--color-bg-code);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  line-height: 1.5;
}
.msg-assistant .msg-text pre code { background: none; padding: 0; font-size: var(--font-size-sm); }
/* A quotation is someone else's voice: set in, lighter, and marked by a hairline rather
   than the heavy rule it used to carry, which competed with the ledger rail beside it. */
.msg-assistant .msg-text blockquote {
  margin: 0 0 var(--space-md);
  padding-left: var(--space-md);
  border-left: 1px solid var(--color-border-subtle);
  color: var(--color-text-secondary);
  font-style: italic;
}
/* Data tables read as instrument output: mono figures, tabular numerals so columns of
   readings line up, and horizontal rules only. Vertical borders on a small table make a
   grid the eye has to parse before it can read a value. */
.msg-assistant .msg-text table {
  border-collapse: collapse;
  margin: 0 0 var(--space-md);
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
}
.msg-assistant .msg-text th,
.msg-assistant .msg-text td {
  border: none;
  border-bottom: 1px solid var(--color-border-subtle);
  padding: var(--space-xs) var(--space-md) var(--space-xs) 0;
  text-align: left;
}
.msg-assistant .msg-text th {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.msg-assistant .msg-text td { font-variant-numeric: tabular-nums; }
.msg-assistant .msg-text hr {
  border: none;
  border-top: 1px solid var(--color-border-subtle);
  margin: var(--space-md) 0;
}
.msg-error {
  margin-top: var(--space-xs);
  color: var(--color-status-fault);
  background: var(--color-status-fault-bg);
  border-radius: var(--radius-sm);
  padding: var(--space-xs) var(--space-sm);
  font-size: var(--font-size-sm);
}
/* System notes (e.g. "Chat cleared"): a quiet, centered divider, not a chat bubble. */
.msg-system {
  align-self: center;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  text-align: center;
  padding: var(--space-xs) 0;
}
/* Rehydration divider (case-memory U4): a full-width rule marking where the
   conversation restarted on injected case memory; persisted, so it replays in place. */
.msg-system.msg-rehydrated {
  align-self: stretch;
  border-top: 1px dashed var(--color-border-subtle);
  margin-top: var(--space-sm);
  padding-top: var(--space-xs);
}

/* Backstage tool activity: one quiet, recessive group per turn. The group head is a muted
   summary line; the individual tool entries sit indented behind a thin rail, collapsed by
   default so the brief and the recommendation carry the visual weight. */
/* The group head reads as a LOG LINE, not a widget. Mono is what separates work from
   prose here; the two faces carry the distinction so colour barely has to participate.
   The caret appears on hover, focus, or while expanded, so a settled turn is a clean
   column of records rather than a row of controls waiting to be clicked. */
.tool-group { margin-top: var(--space-xs); }
.tool-group-head {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  padding: 2px 0;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  letter-spacing: 0.01em;
  color: var(--color-text-muted);
}
.tool-group-head:hover { color: var(--color-text-secondary); }
.tg-caret {
  color: var(--color-text-muted);
  opacity: 0;
}
.tool-group-head:hover .tg-caret,
.tool-group-head:focus-visible .tg-caret,
.tool-group-head[aria-expanded="true"] .tg-caret { opacity: 1; }
.tool-group-body {
  margin: var(--space-xs) 0 0 var(--space-xs);
  padding-left: var(--space-md);
  border-left: 1px solid var(--color-ledger-rail);
  flex-direction: column;
  gap: var(--space-xs);
}
/* display only when shown: a bare `display: flex` would beat the [hidden] attribute's
   display:none, so the collapsed group would never actually hide its entries. */
.tool-group-body:not([hidden]) { display: flex; }
/* Entries are the ledger's line items: tool names are identifiers and elapsed times are
   figures, so both take the mono face at the ledger's size. */
.tool-entry { font-size: var(--font-size-xs); font-family: var(--font-mono); }
.tool-head {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  padding: 2px 0;
  font-family: inherit;
  font-size: inherit;
  color: var(--color-text-secondary);
}
.tool-name { color: var(--color-text-primary); }
.tool-elapsed { color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.tool-caret { color: var(--color-text-muted); }
.tool-running .tool-name::after { content: " …"; color: var(--color-text-muted); }
.tool-entry.tool-error .tool-name { color: var(--color-status-fault); }
.tool-body, .tool-stderr {
  margin: 0;
  padding: var(--space-sm);
  background: var(--color-bg-code);
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  overflow-x: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.tool-stderr { color: var(--color-status-fault); }

/* An interrupted turn (IW-102 U3). The session died before the turn's terminal event, so
   the end-of-replay sweep settles it here; without this the entries spin and the summary
   reads "Working…" on every reopen, forever. The treatment is a record, not an alarm:
   muted throughout, no fault colour, because a stream that died upstream is a fact about
   the session and not a failure the operator caused or can act on. */
.tool-stalled .tool-name::after { content: " (stopped)"; color: var(--color-text-muted); }
.tool-group-interrupted .tool-group-head { color: var(--color-text-muted); }
.msg-interrupted-note {
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
}

/* ── Motion (IW-102 U6) ───────────────────────────────────────────────────────
 *
 * One orchestrated gesture, not a collection of effects: a block arrives with a short
 * fade and a small rise, and the group head cross-fades when its state changes.
 * Streaming text itself never animates; the text arriving IS the animation.
 *
 * The entrance class is applied by chat.js only on LIVE events. Replay applies none, so
 * a reopened case paints at once rather than cascading dozens of elements, and the
 * scroll-to-end pinning stays exact (animated late height changes would unpin it). */
@media (prefers-reduced-motion: no-preference) {
  .wb-enter { animation: wb-rise var(--motion-enter) var(--ease-out) both; }
  .tool-group-head,
  .tg-caret { transition: color var(--motion-enter) var(--ease-out),
                          opacity var(--motion-enter) var(--ease-out); }
}
@keyframes wb-rise {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* Approval cards */
.approval-card {
  margin-top: var(--space-sm);
  border: 1px solid var(--color-status-warning);
  background: var(--color-status-warning-bg);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  max-width: var(--measure-wide);
}
/* The pending card is the one place a title must carry weight: it is a question, and the
   operator has to see they are being asked something. A settled card drops back to the
   quiet label above, because a decided gate is a record of what happened. */
.approval-card.approval-pending .approval-title {
  font-size: var(--font-size-md);
  font-weight: 700;
  color: var(--color-text-primary);
}
.approval-desc { color: var(--color-text-secondary); margin: var(--space-xs) 0; }
.approval-input {
  margin: var(--space-xs) 0;
  padding: var(--space-sm);
  background: var(--color-bg-code);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  overflow-x: auto;
}
.approval-actions { display: flex; align-items: center; gap: var(--space-sm); margin-top: var(--space-sm); }
.approval-status { color: var(--color-text-muted); font-size: var(--font-size-sm); }
.approval-historical { opacity: 0.65; border-style: dashed; }
.approval-note { color: var(--color-text-muted); font-size: var(--font-size-sm); margin-top: var(--space-xs); }
/* The exact tool input, collapsed only where the lead line above it already names
   the whole change (IW-103). Everything else shows its payload outright. */
.approval-details { margin: var(--space-xs) 0; }
.approval-details > summary {
  cursor: pointer;
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}
/* A settled card is a record, not a request: the amber warning ground goes away with
   the controls, and the border alone carries which outcome it holds. IW-102's U5
   owns the finished treatment of all four states; this is the minimum that stops a
   resolved card reading as open. */
.approval-settled { background: var(--color-bg-surface); }
.approval-decision {
  margin-top: var(--space-sm);
  font-weight: 600;
  font-size: var(--font-size-sm);
}
/* The settled outcome takes focus when the operator's own click removed the button
   they were on, so it needs the shared ring the global rule only gives to genuinely
   interactive elements. */
.approval-decision:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.approval-allowed { border-color: var(--color-status-resolved); }
.approval-allowed .approval-decision { color: var(--color-status-resolved); }
.approval-denied { border-color: var(--color-status-fault); }
.approval-denied .approval-decision { color: var(--color-status-fault); }
/* Gate nobody decided: its turn died or its session was torn down. Muted and inert,
   deliberately NOT the fault tone, because no one refused anything. */
.approval-stale { opacity: 0.6; border-style: dashed; border-color: var(--color-status-waiting); }
.approval-stale .approval-decision { color: var(--color-text-muted); font-weight: 400; }

/* Artifact and approval cards sit on the same rail as everything else in the turn, so
   they share one padding, one radius, and one heading treatment (IW-102 U5). The card
   title is an eyebrow, not a headline: the card's CONTENT is what the operator reads,
   and a bold heading above it competes with the prose it interrupted. Card internals
   (brief sections, evidence tables, draft controls) are untouched. */
.artifact-card {
  margin-top: var(--space-sm);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-bg-raised);
  padding: var(--space-md);
  max-width: var(--measure-wide);
}
.artifact-title,
.approval-title {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-sm);
}
.brief-body { margin-top: var(--space-xs); }
.draft-edit {
  width: 100%;
  resize: vertical;
  margin-top: var(--space-xs);
  font-family: var(--font-sans);
  font-size: var(--font-size-md);
  padding: var(--space-sm);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
}
.draft-actions { display: flex; align-items: center; gap: var(--space-sm); margin-top: var(--space-xs); }
.draft-status { color: var(--color-text-muted); font-size: var(--font-size-sm); }

/* Rich (html) draft: rendered formatting + inline images, edited in place (no raw HTML). */
.draft-html {
  width: 100%;
  margin-top: var(--space-xs);
  padding: var(--space-sm);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--font-size-md);
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.draft-html:focus { outline: 2px solid var(--color-accent); outline-offset: -1px; }
.draft-html img { max-width: 100%; height: auto; display: block; margin: var(--space-sm) 0; border-radius: var(--radius-sm); }
.draft-html p { margin: 0 0 var(--space-sm); }
.draft-html ul, .draft-html ol { margin: 0 0 var(--space-sm); padding-left: 1.4em; }
.draft-html > :first-child { margin-top: 0; }
.draft-html > :last-child { margin-bottom: 0; }
.brief-evidence { margin-top: var(--space-sm); }
.brief-evidence summary { cursor: pointer; color: var(--color-text-secondary); }

/* Judged P-code evidence tables on the brief card (IW-55). Server-rendered data:
   a flagged miss reads loud, a recognized benign condition reads calm, a row the
   layer had nothing to say about stays quiet. */
.evidence-block { margin-top: var(--space-sm); }
.evidence-head { font-weight: 600; font-size: var(--font-size-sm); }
.evidence-meta {
  margin-left: var(--space-sm);
  font-weight: 400;
  color: var(--color-text-muted);
}
.evidence-table {
  width: 100%;
  margin-top: var(--space-xs);
  border-collapse: collapse;
  font-size: var(--font-size-sm);
}
.evidence-table th, .evidence-table td {
  border: 1px solid var(--color-border-subtle);
  padding: 2px var(--space-sm);
  text-align: left;
  vertical-align: top;
}
.evidence-table th { color: var(--color-text-secondary); font-weight: 600; }
.evidence-scroll { overflow-x: auto; }
.evidence-class {
  margin-top: var(--space-sm);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
}
.evidence-meaning { color: var(--color-text-muted); max-width: 26em; }
/* Decoded enum/bitmask reading (IW-64): the meaning sits under the raw value. */
.evidence-decoded {
  color: var(--color-text-muted);
  font-size: var(--font-size-xs);
  max-width: 16em;
}
/* Per-device cell flags (IW-61): loud miss, calm named condition, quiet match. */
.evidence-cell-outside { background: var(--color-status-fault-bg); color: var(--color-status-fault); font-weight: 600; }
.evidence-cell-condition { background: var(--color-status-waiting-bg); color: var(--color-status-waiting); }
.evidence-cell-ok { color: var(--color-status-resolved); }
.tier-chip {
  display: inline-block;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border-subtle);
  vertical-align: middle;
}
.tier-documented { color: var(--color-status-resolved); border-color: var(--color-status-resolved); }
.tier-confirmed { color: var(--color-status-waiting); border-color: var(--color-status-waiting); }
.evidence-note {
  margin-top: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  border-left: 3px solid var(--color-status-warning);
  background: var(--color-status-warning-bg);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}

/* Completeness readout on the brief card (IW-74, IW-83). Server-rendered
   verdict: complete reads quiet, incomplete reads loud, not computed and not
   applicable read muted; the as-of line reuses the evidence-note styling with
   a loud stale marker. renderCompleteness builds this chip's class as
   completeness-${status}, so every member of Core's READOUT_STATUSES needs a
   rule here or its chip renders unstyled. */
.completeness-chip {
  display: inline-block;
  margin-left: var(--space-sm);
  padding: 0 var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  border: 1px solid var(--color-border-subtle);
  vertical-align: middle;
}
.completeness-complete { color: var(--color-status-resolved); border-color: var(--color-status-resolved); }
.completeness-incomplete { color: var(--color-status-fault); border-color: var(--color-status-fault); }
.completeness-not_computed { color: var(--color-text-muted); }
.completeness-not_applicable { color: var(--color-text-muted); }
.completeness-asof-stale {
  border-left-color: var(--color-status-fault);
  background: var(--color-status-fault-bg);
}
.completeness-item, .completeness-gap {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.completeness-status { font-weight: 600; }
.completeness-item-present .completeness-status { color: var(--color-status-resolved); }
.completeness-item-missing .completeness-status,
.completeness-item-illegible .completeness-status { color: var(--color-status-fault); }
.completeness-gap-kind { font-weight: 600; color: var(--color-status-warning); }
.completeness-proposal { color: var(--color-text-muted); padding-left: var(--space-md); }
.completeness-line {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* Draft completeness stamp (IW-76, R16, IW-83): the four-state badge on the
   draft card header, the tier-chip pattern. Flagged reads loud, override reads
   warning-calm, clean reads quiet, not-applicable reads muted. Muted rather
   than green: the bar not applying is not the bar being met, and an operator
   scanning by colour has to be able to tell those apart. */
.draft-stamp {
  display: inline-block;
  margin-left: var(--space-sm);
  padding: 0 var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-xs);
  font-weight: 400;
  border: 1px solid var(--color-border-subtle);
  vertical-align: middle;
}
.stamp-flagged {
  color: var(--color-status-fault);
  border-color: var(--color-status-fault);
  background: var(--color-status-fault-bg);
}
.stamp-override { color: var(--color-status-waiting); border-color: var(--color-status-waiting); }
.stamp-clean { color: var(--color-status-resolved); border-color: var(--color-status-resolved); }
.stamp-not-applicable { color: var(--color-text-muted); }

/* Completeness override control in the memory panel (IW-76). */
.override-line {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.override-toggle { margin-top: var(--space-xs); }

/* Product confirmation control in the memory panel (IW-94). Sits above the
   override block: it is asked on every ticket, the override on almost none. */
.product-block { margin-bottom: var(--space-sm); }
.product-select {
  margin-top: var(--space-xs);
  margin-right: var(--space-xs);
  max-width: 22rem;
}
.product-line {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
/* The operator's answer differs from the extractor's: shown in both places
   the identity is read, and styled as a live disagreement rather than a note. */
.product-disagreement,
.brief-product-note {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-status-waiting);
}
.brief-product-note { margin-bottom: var(--space-xs); }

/* Ticket reading confirmation control (IW-99 U3): the two selects share the
   product select's shape; the disagreement shares its live-warning colour. */
.reading-block { margin-bottom: var(--space-sm); }
.reading-select {
  margin-top: var(--space-xs);
  margin-right: var(--space-xs);
  max-width: 22rem;
}
.reading-line {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.reading-disagreement,
.brief-reading-note {
  margin-top: var(--space-xs);
  font-size: var(--font-size-sm);
  color: var(--color-status-waiting);
}
.brief-reading-note { margin-bottom: var(--space-xs); }

/* Ticket-open quick-look: condensed customer email with the verbatim original one click away. */
.inbound-card { border-left: 3px solid var(--color-accent); }
.inbound-condensed { margin-top: var(--space-xs); }
.inbound-original { margin-top: var(--space-sm); }
.inbound-original summary { cursor: pointer; color: var(--color-text-secondary); font-size: var(--font-size-sm); }
.inbound-original-body {
  margin-top: var(--space-xs);
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--font-size-sm);
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.rma-row { display: flex; align-items: center; gap: var(--space-sm); padding: 2px 0; font-size: var(--font-size-sm); }
.rma-key { color: var(--color-text-muted); min-width: 11em; }
.rma-input {
  flex: 1 1 auto;
  font-family: var(--font-sans);
  font-size: var(--font-size-sm);
  padding: 2px var(--space-xs);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
}

/* Case memory panel (case memory U6): operator summary edit + proposal review. */
.memory-panel {
  flex: none;
  margin: var(--space-sm) 0 0;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-bg-raised);
}
.memory-head {
  cursor: pointer;
  padding: var(--space-sm) var(--space-md);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  list-style: none; /* the chevron replaces the default disclosure marker */
}
.memory-head::-webkit-details-marker { display: none; }
.memory-head::marker { content: ""; }
/* The generic brief-section summary injects a text triangle (style.css
   "Brief sections"); the memory head's rotating chevron replaces it. */
.memory-panel .memory-head::before,
.memory-panel[open] .memory-head::before { content: none; }
.memory-head:hover { color: var(--color-text-primary); }
.memory-chev { width: 12px; height: 12px; flex: none; transition: transform 0.12s ease; }
@media (prefers-reduced-motion: reduce) { .memory-chev { transition: none; } }
.memory-panel[open] .memory-chev { transform: rotate(90deg); }
.memory-count { color: var(--color-accent); }
.memory-facts { color: var(--color-text-muted); font-weight: 400; }
/* Bounded body (IW-53): a well-fed memory scrolls inside its frame instead of
   shoving the conversation and composer off screen. */
.memory-body {
  padding: 0 var(--space-md) var(--space-md);
  max-height: 40vh;
  overflow-y: auto;
}
.memory-label {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin-top: var(--space-sm);
}
.memory-summary-edit { font-size: var(--font-size-sm); }
.memory-distilled {
  margin-top: var(--space-sm);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}
/* Section heads read in the card-header register, not as bold body text. */
.memory-list-head {
  font-weight: 600;
  color: var(--color-text-secondary);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: var(--space-md);
}
.memory-list { margin: var(--space-xs) 0 0; padding-left: 1.3em; line-height: 1.5; }
.memory-list li { margin-bottom: 3px; }
.memory-list b { color: var(--color-text-primary); font-weight: 600; }
.proposal-card {
  margin-top: var(--space-xs);
  border: 1px solid var(--color-border-subtle);
  border-left: 3px solid var(--color-status-warning);
  border-radius: var(--radius-sm);
  padding: var(--space-sm);
  font-size: var(--font-size-sm);
}
.proposal-field {
  font-family: var(--font-mono);
  font-weight: 600;
  margin-right: var(--space-sm);
}
.proposal-value { overflow-wrap: anywhere; }
.proposal-trigger { color: var(--color-text-muted); margin-top: 2px; }
.proposal-actions { display: flex; align-items: center; gap: var(--space-sm); margin-top: var(--space-xs); }

/* Statusline + composer dock — the transcript scrolls above this; the dock stays put. */
.chat-statusline,
.command-chips,
.composer-wrap { flex: none; }
.chat-statusline {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-xs) 0;
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}
.composer-wrap { padding-bottom: var(--space-md); }

/* ── Technical data reads in mono ──────────────────────────────────────────────
 * Ticket ids, fault codes, the token/cost readout, the tool summary, and command
 * names are codes and instrument readings, so they render in IBM Plex Mono. This
 * is the workbench's instrument-panel character; prose stays in Plex Sans. */
.chat-header .ticket-id,
.status-usage,
.tool-group-head, .tg-summary,
.cmd-chip {
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}
.status-usage { font-size: var(--font-size-xs); }
.model-select {
  font-size: var(--font-size-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-xs);
}
.backstage-toggle {
  margin-left: auto;
  background: none;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  padding: 2px var(--space-sm);
  cursor: pointer;
}
.backstage-toggle:hover { color: var(--color-text-secondary); }
.backstage-toggle[aria-pressed="true"] { color: var(--color-text-secondary); border-color: var(--color-text-muted); }
.status-usage { margin-left: var(--space-md); }

/* Active-tier badge (R10): a quiet chip; the full tier and any manual override read at a
   glance. Empty until the first turn sets a tier. */
.status-tier {
  font-size: var(--font-size-xs);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  padding: 1px var(--space-xs);
  color: var(--color-text-muted);
}
.status-tier:empty { border: none; padding: 0; }
.status-tier.tier-full { color: var(--color-text-primary); border-color: var(--color-text-muted); }
.status-tier.tier-manual { color: var(--color-text-secondary); border-style: dashed; }

/* Start-mode badge (case-memory U4): quiet chip, same family as the tier badge; shown
   only when the open rehydrated from case memory instead of resuming. */
.status-mode {
  font-size: var(--font-size-xs);
  border: 1px dashed var(--color-border-subtle);
  border-radius: var(--radius-sm);
  padding: 1px var(--space-xs);
  color: var(--color-text-muted);
}
.status-mode:empty { border: none; padding: 0; }
.status-mode.mode-rehydrated-degraded { color: var(--color-text-secondary); }

.chat-composer {
  display: flex;
  gap: var(--space-sm);
  padding-top: var(--space-sm);
}
.composer-input {
  flex: 1 1 auto;
  resize: none;
  font-family: var(--font-sans);
  font-size: var(--font-size-md);
  padding: var(--space-sm);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-primary);
}

/* Attachments: paperclip button, staged tray, chips + thumbnails, drag-drop, error. */
.btn-attach {
  flex: none;
  align-self: flex-end;
  padding: var(--space-sm);
  font-size: var(--font-size-md);
  line-height: 1;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  cursor: pointer;
}
.btn-attach:hover { color: var(--color-text-primary); border-color: var(--color-accent); }
.btn-attach svg { width: 16px; height: 16px; display: block; }
.attachment-tray {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  padding: var(--space-xs) 0;
}
.attachment {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  max-width: 16rem;
  padding: 2px var(--space-xs);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-bg-surface);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}
.attachment-thumb {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  display: block;
}
.attachment-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.attachment-remove {
  flex: none;
  border: none;
  background: none;
  color: var(--color-text-secondary);
  font-size: var(--font-size-md);
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
}
.attachment-remove:hover { color: var(--color-text-primary); }
/* Attachments shown inside a sent user bubble sit above its text. */
.msg-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-bottom: var(--space-xs);
}
.composer-wrap.drag-over {
  outline: 2px dashed var(--color-accent);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}
.upload-error {
  color: var(--color-status-fault, var(--color-text-primary));
  font-size: var(--font-size-sm);
  padding: var(--space-xs) 0;
}

/* Command palette: chips (R14) + slash autocomplete (R13) */
.command-chips { display: flex; flex-wrap: wrap; gap: var(--space-xs); padding: var(--space-xs) 0; }
.cmd-chip {
  font-size: var(--font-size-sm);
  padding: 2px var(--space-sm);
  border: 1px solid var(--color-border-subtle);
  border-radius: 999px;
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  cursor: pointer;
}
.cmd-chip-suggested { border-color: var(--color-accent); color: var(--color-accent); }
.cmd-chip-disabled { opacity: 0.45; cursor: not-allowed; }
.composer-wrap { position: relative; }
.cmd-autocomplete {
  position: absolute;
  bottom: 100%;
  left: 0;
  right: 0;
  margin-bottom: var(--space-xs);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-raised);
  max-height: 16em;
  overflow-y: auto;
  z-index: 20;
}
.cmd-option {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: var(--space-xs) var(--space-sm);
  cursor: pointer;
}
.cmd-option:hover { background: var(--color-bg-hover); }
.cmd-name { font-weight: 600; color: var(--color-text-primary); }
.cmd-desc { color: var(--color-text-muted); font-size: var(--font-size-sm); }

/* Scoped queue rail entries (U10, status icons IW-48): presence + precedence only */
.rail-group {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  padding: var(--space-sm) var(--space-md) 2px;
}
.rail-entry {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-xs) var(--space-sm);
  cursor: pointer;
  color: var(--color-text-secondary);
}
.rail-entry:hover { background: var(--color-bg-hover); }
.rail-entry-mine { color: var(--color-text-primary); }
.rail-glyph { flex: none; width: 15px; height: 15px; }
.rail-glyph svg { width: 100%; height: 100%; display: block; }
/* Whose-move state colors: chromatic only where action lives. Two states ask
   something of the operator and both carry a hue: amber for a customer waiting
   on a reply, the accent for a ticket nobody has answered at all. The rest stay
   quiet. `new` used to sit in the muted grey with `resolved` back when it only
   meant the operator had no local case row and half the queue wore it; now that
   it means genuinely unanswered it is the rail's rarest and sharpest state and
   must not read as finished (IW-101). */
.st-reply  { color: var(--color-status-warning); }
.st-wait   { color: var(--color-status-waiting); }
.st-pm     { color: var(--color-status-resolved); }
.st-done   { color: var(--color-text-muted); }
.st-fresh  { color: var(--color-accent); }
.rail-num {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}
.rail-cust { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-age {
  flex: none;
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}
.rail-entry-unread .rail-num { font-weight: 700; }

/* Collapsed 56px icon mode: status icon and unread dot only. */
.shell.rail-collapsed .rail-num,
.shell.rail-collapsed .rail-cust,
.shell.rail-collapsed .rail-age,
.shell.rail-collapsed .rail-group { display: none; }
.shell.rail-collapsed .rail-entry { justify-content: center; padding: var(--space-sm) 0; }
.rail-unread {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--color-accent);
  flex: 0 0 auto;
}
.rail-search-divider {
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  padding: var(--space-xs) var(--space-sm) 2px;
}
.rail-search-empty { color: var(--color-text-muted); font-size: var(--font-size-sm); padding: var(--space-xs) var(--space-sm); }
.rail-archive-hit {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 2px var(--space-sm);
  cursor: pointer;
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}
.rail-archive-hit:hover { background: var(--color-bg-hover); }

/* ── Boat review queue (IW-65 U13) ────────────────────────────────────────────
 * Cards reuse the memory panel's proposal-card look; the page is a narrow
 * reading column inside the case pane. The header link + badge live in the
 * inverse-colored site header. */
.header-nav { display: inline-flex; align-items: center; flex: none; }
.header-nav-link {
  color: var(--color-text-inverse);
  opacity: 0.85;
  text-decoration: none;
  font-size: var(--font-size-sm);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.header-nav-link:hover { opacity: 1; text-decoration: underline; }
.nav-badge {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
  border-radius: 999px;
  padding: 0 7px;
  font-size: var(--font-size-xs);
  line-height: 1.6;
  font-weight: 600;
}
.registry-queue { max-width: 760px; margin: 0 auto; padding: var(--space-lg) 0; }
.rq-header { display: flex; align-items: baseline; gap: var(--space-md); }
.rq-title { font-size: 1.25rem; margin: 0; }
.rq-count { color: var(--color-text-muted); font-size: var(--font-size-sm); }
.rq-pii-note {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin: var(--space-xs) 0 var(--space-md);
}
.rq-empty { color: var(--color-text-muted); padding: var(--space-md) 0; }
.rq-group { margin-bottom: var(--space-lg); }
.rq-boat {
  font-size: var(--font-size-sm);
  font-family: var(--font-mono);
  text-transform: none;
  letter-spacing: 0.03em;
  color: var(--color-text-secondary);
  margin: 0 0 var(--space-xs);
}
.rq-card { background: var(--color-bg-surface); }
.rq-status { color: var(--color-text-muted); font-size: var(--font-size-sm); }
.rq-error { color: var(--color-status-fault); }
/* The collapsed-address toggle sits on a card, not the inverse header, so it
 * overrides .btn-link's inverse color. */
.rq-address-toggle { color: var(--color-text-secondary); opacity: 1; }
.rq-address-toggle:hover { color: var(--color-text-primary); }
.rq-address-value { display: block; margin-top: 2px; }

/* ── Settings and operator console (IW-68) ────────────────────────────────────
 * "My settings" + /admin: a centered stack of instrument panels inside the
 * case pane. Each .admin-section echoes .card (the one surface shape); section
 * titles and column headers use the card-header tracked-uppercase register.
 * Tables drop the browser chrome: hairline row separators only, roomy rows,
 * ids / keys / timestamps / counts in Plex Mono so digits align.
 * The one deliberate moment is the breaker-panel status readout (state dot +
 * mono label): filled + tint halo when energized (set / OK / FAIL), hollow
 * ring when idle (not set / not tested). Status hues come from the existing
 * status token pairs; magenta stays reserved for the primary actions the
 * templates already mark with .btn-primary. */
.admin-console {
  max-width: 820px;
  margin: 0 auto;
  padding: var(--space-md) 0 48px;
}
.admin-header { margin-bottom: var(--space-xl); }
.admin-header h1 { margin: 0 0 var(--space-xs); }
.admin-sub {
  margin: 0;
  max-width: 60ch;
  font-size: var(--font-size-sm);
  line-height: 1.6;
  color: var(--color-text-muted);
}

/* Panels: quiet cards, generous rhythm between them. */
.admin-section {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-lg) var(--space-xl) var(--space-xl);
  margin-bottom: var(--space-xl);
}
.admin-section h2 {
  margin: 0 0 var(--space-lg);
  font-size: var(--font-size-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-secondary);
}
/* Optional right-aligned action shares the title baseline. */
.admin-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  margin-bottom: var(--space-lg);
}
.admin-section-head h2 { margin: 0; }
.admin-note {
  margin: var(--space-md) 0 0;
  max-width: 60ch;
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

/* .btn-link is inverse-colored for the dark site header; on a console panel it
 * must read on the surface in both themes (it was white-on-white in light).
 * Same treatment .rq-address-toggle already uses on cards. */
.admin-console .btn-link { color: var(--color-text-secondary); opacity: 1; }
.admin-console .btn-link:hover { color: var(--color-text-primary); }
/* Destructive link actions (Deactivate, Sign out everywhere) read as fault. */
.admin-console .btn-link.admin-danger { color: var(--color-status-fault); }

/* Tables: no grid, no chrome. Hairline separators, first column flush with the
 * panel edge, trailing actions/amount column right-aligned. */
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-sm);
}
.admin-table th {
  padding: 0 var(--space-lg) var(--space-sm) 0;
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.admin-table td {
  padding: 10px var(--space-lg) 10px 0;
  border-top: 1px solid var(--color-border-subtle);
  vertical-align: middle;
}
.admin-table th:last-child, .admin-table td:last-child {
  text-align: right;
  padding-right: 0;
}
/* Spend caps (IW-20 U4): the enforced caps read beside the section title, and an
 * over-cap row carries the word "over cap" next to its amount, so the blocked
 * state survives greyscale and colour-blind viewing. */
.admin-spend-caps {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}
.admin-spend-flag { margin-left: var(--space-sm); }
.admin-table tbody tr { transition: background-color 0.12s ease; }
.admin-table tbody tr:hover { background: var(--color-bg-hover); }
@media (prefers-reduced-motion: reduce) {
  .admin-table tbody tr { transition: none; }
}

/* Instrument readout cells: ids, setting keys, timestamps, turns, and costs in
 * mono with tabular numerals (the JS renders bare cells, so these are keyed to
 * each section's fixed column order). */
#my-credentials td:first-child,
#admin-users td:first-child,
#admin-settings td:first-child,
#admin-sessions td:first-child,
#admin-sessions td:nth-child(2),
#admin-sessions td:nth-child(3),
#admin-spend td:first-child,
#admin-spend td:nth-child(3),
#admin-spend td:nth-child(4) {
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}
/* Turns is numeric: right-align toward the cost column. */
#admin-spend th:nth-child(3), #admin-spend td:nth-child(3) { text-align: right; }
/* Quiet metadata columns: role/status, setting source and apply mode. */
#admin-users td:nth-child(2), #admin-users td:nth-child(3),
#admin-settings td:nth-child(2), #admin-settings td:nth-child(3) {
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
}
/* Deactivated accounts dim as a whole row, actions included. */
.admin-table tr.admin-row-inactive { opacity: 0.55; }

/* Breaker-panel readout: 8px state lamp + mono label. Filled with a tint halo
 * when the circuit is live, a hollow ring when idle. Shared by credential
 * status (.cred-*) and connection health (.health-*). */
.cred-set, .cred-unset, .health-status {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  letter-spacing: 0.04em;
}
.cred-set::before, .cred-unset::before, .health-status::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  margin-right: var(--space-sm);
  vertical-align: 1px;
}
.cred-set { color: var(--color-status-resolved); text-transform: uppercase; }
.cred-set::before {
  background: var(--color-status-resolved);
  box-shadow: 0 0 0 3px var(--color-status-resolved-bg);
}
.cred-unset { color: var(--color-text-muted); text-transform: uppercase; }
.cred-unset::before { box-shadow: inset 0 0 0 1.5px var(--color-text-muted); }
.cred-when {
  margin-left: var(--space-sm);
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}
/* Health default covers "not tested" and the transient "testing..." state;
 * admin.js swaps in health-ok / health-bad after each probe. */
.health-status { color: var(--color-text-muted); }
.health-status::before { box-shadow: inset 0 0 0 1.5px var(--color-text-muted); }
.health-status.health-ok { color: var(--color-status-resolved); }
.health-status.health-ok::before {
  background: var(--color-status-resolved);
  box-shadow: 0 0 0 3px var(--color-status-resolved-bg);
}
.health-status.health-bad { color: var(--color-status-fault); }
.health-status.health-bad::before {
  background: var(--color-status-fault);
  box-shadow: 0 0 0 3px var(--color-status-fault-bg);
}

/* Inline value forms (credential + instance-setting rows): input and Save on
 * one row, right-justified into the trailing column; the input keeps a fixed
 * working width instead of stretching, and the pair wraps on narrow panes. */
.my-cred-form, .admin-setting-form {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
}
.my-cred-form input, .admin-setting-form input {
  width: 200px;
  min-width: 120px;
  flex: 0 1 200px;
  padding: 5px 8px;
}

/* Action cluster in table rows (Edit role / Deactivate). */
.admin-actions .btn-link + .btn-link { margin-left: var(--space-lg); }

/* Enroll / edit form: a footnote panel under the users table. The hairline
 * spans the panel; the fields keep a working width. */
.admin-form {
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border-subtle);
}
.admin-form-row { max-width: 380px; margin-bottom: var(--space-md); }
.admin-form-row label {
  display: block;
  margin-bottom: var(--space-xs);
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--color-text-secondary);
}
.admin-optional { font-weight: 400; color: var(--color-text-muted); }
.admin-form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  margin-top: var(--space-lg);
}

/* ── Candidate review (IW-108) ────────────────────────────────────────────────
 * The parked candidate pool. Same narrow reading column and .proposal-card
 * shape as the boat-review queue, in its own .cr-* namespace rather than
 * extending .rq-*, which is that page's private set.
 * The erase control is the product's first irreversible action, so it reads
 * as a fault-colored link at rest and the confirm step gets weight. */
.candidate-review { max-width: 760px; margin: 0 auto; padding: var(--space-lg) 0; }
.cr-header { display: flex; align-items: baseline; gap: var(--space-md); }
.cr-title { font-size: 1.25rem; margin: 0; }
.cr-count { color: var(--color-text-muted); font-size: var(--font-size-sm); }
.cr-note {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  margin: var(--space-xs) 0 var(--space-md);
}
.cr-empty { color: var(--color-text-muted); padding: var(--space-md) 0; }
.cr-notice {
  color: var(--color-status-fault);
  padding: var(--space-md) 0;
  font-size: var(--font-size-sm);
}
.cr-card { background: var(--color-bg-surface); margin-bottom: var(--space-sm); }
.cr-line {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.cr-name { font-weight: 600; }
.cr-owner { color: var(--color-text-secondary); font-size: var(--font-size-sm); }
.cr-meta { color: var(--color-text-muted); font-size: var(--font-size-sm); margin-top: 2px; }
.cr-trail a { margin-right: var(--space-xs); }
.cr-status { color: var(--color-text-muted); font-size: var(--font-size-sm); }
/* .btn-link is colored for the inverse header; these sit on a card. */
.cr-danger { color: var(--color-status-fault); opacity: 1; }
.cr-danger:hover { text-decoration: underline; }
.cr-confirm { font-weight: 600; }
