/**
 * UI Components — hazzard-api operator console
 *
 * Plain-CSS ports of the web app's components/ui primitives (../web), one
 * class per component, consuming tokens.css only. `storm-ink/N` in web is
 * --ink-N token here. Tones follow web's lib/status.ts: neutral, pending,
 * info, positive, caution, critical (+ signal, map data only).
 *
 * Requires tokens.css first. No colour literals (test_console_templates).
 */

/* ============================================================================
 * BUTTONS (web Button)
 * Bare <button>, input[type=submit] and .btn are the primary (solid
 * gauge-blue) action. data-variant = primary | outline | plain |
 * destructive; data-size = sm. Legacy aliases: .secondary/.btn-secondary
 * = outline, .btn-ghost = plain, .btn-danger = destructive, .btn-link =
 * outline anchor.
 * ============================================================================ */

button,
input[type="submit"],
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.3125rem 0.6875rem;
  background: var(--color-gauge-blue);
  color: var(--color-on-accent);
  border: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  font-size: var(--text-sm);
  line-height: 1.5rem;
  font-weight: 600;
  font-family: inherit;
  text-decoration: none;
  white-space: nowrap;
  transition:
    background var(--transition-base),
    border-color var(--transition-base),
    color var(--transition-base);
}

button:hover,
input[type="submit"]:hover,
.btn:hover {
  background: color-mix(in oklab, var(--color-gauge-blue) 88%, var(--color-on-accent));
  text-decoration: none;
}

button:disabled,
input[type="submit"]:disabled,
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Outline — the secondary action. */
[data-variant="outline"],
button.secondary,
.btn-secondary,
.btn-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.3125rem 0.6875rem;
  background: transparent;
  color: var(--color-storm-ink);
  border: 1px solid var(--ink-10);
  border-radius: var(--radius-lg);
  box-shadow: none;
  font-size: var(--text-sm);
  line-height: 1.5rem;
  font-weight: 600;
  text-decoration: none;
}

[data-variant="outline"]:hover,
button.secondary:hover,
.btn-secondary:hover,
.btn-link:hover {
  background: var(--ink-3);
  border-color: var(--ink-15);
  color: var(--color-storm-ink);
  text-decoration: none;
}

/* Plain — the tertiary action. */
[data-variant="plain"],
.btn-ghost {
  background: transparent;
  color: var(--color-storm-ink);
  border: 1px solid transparent;
  box-shadow: none;
}

.btn-ghost { color: var(--color-storm-muted); }

[data-variant="plain"]:hover,
.btn-ghost:hover {
  background: var(--ink-5);
  color: var(--color-storm-ink);
}

/* Destructive — the irreversible action. */
[data-variant="destructive"],
.btn-danger {
  background: var(--color-status-red);
  color: var(--color-on-accent);
  border: none;
}

[data-variant="destructive"]:hover,
.btn-danger:hover {
  background: color-mix(in oklab, var(--color-status-red) 88%, var(--color-on-accent));
}

/* Primary, stated explicitly (wins over a legacy class on the same node). */
[data-variant="primary"],
.btn-link.btn {
  background: var(--color-gauge-blue);
  color: var(--color-on-accent);
  border: none;
}

[data-variant="primary"]:hover,
.btn-link.btn:hover {
  background: color-mix(in oklab, var(--color-gauge-blue) 88%, var(--color-on-accent));
  color: var(--color-on-accent);
}

/* Dense rows and toolbars. */
:is(button, input[type="submit"], .btn, .btn-link, a[data-variant])[data-size="sm"] {
  padding: 0.1875rem 0.5625rem;
  font-size: var(--text-sm);
  line-height: 1.25rem;
}

/* ============================================================================
 * SHEET + SECTION (web SYSTEM.md "Page primitives")
 * A sheet is one surface on the gray page; a section inside it is a heading
 * and a top rule, never a card. .sheet-head / .sheet-row are the
 * SettingsSection title band and its rows.
 * ============================================================================ */

.sheet {
  background: var(--color-surface);
  color: var(--color-storm-ink);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs), 0 0 0 1px var(--ink-5);
  margin-bottom: var(--space-5);
}

[data-theme="dark"] .sheet { box-shadow: inset 0 0 0 1px var(--ink-10); }

.sheet > :not(.sheet-head):not(.sheet-row):not(.sheet-body):not(.sheet-foot):not(table):not(.table-frame):not(.table-scroll) {
  margin-left: var(--space-5);
  margin-right: var(--space-5);
}

.sheet-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--ink-5);
}

.sheet-head h2,
.sheet-head h3 {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.5rem;
  font-weight: 600;
}

.sheet-head p {
  margin: 0.125rem 0 0;
  font-size: var(--text-sm);
  line-height: 1.25rem;
  color: var(--color-storm-muted);
}

.sheet-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--ink-5);
  font-size: var(--text-sm);
  line-height: 1.25rem;
}

.sheet-head + .sheet-row { border-top: none; }

.sheet-head-text { min-width: 0; }
.sheet-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; justify-content: flex-end; }
.sheet > .sheet-body { padding: var(--space-4) var(--space-5); }
.sheet > table, .sheet > .table-scroll > table { border: none; border-radius: 0 0 var(--radius-lg) var(--radius-lg); }

.sheet-row .sheet-label { font-weight: 500; color: var(--color-storm-ink); }
.sheet-row .sheet-value { font-weight: 500; color: var(--color-storm-ink); text-align: right; }
.sheet-row .muted { font-size: var(--text-sm); }

.section {
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--ink-10);
}

.section:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.section > h2,
.section-title {
  margin: 0 0 var(--space-1);
  font-size: var(--text-sm);
  line-height: 1.5rem;
  font-weight: 600;
  letter-spacing: normal;
}

.section > .section-desc,
.section > h2 + p {
  margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
  line-height: 1.25rem;
  color: var(--color-storm-muted);
}

/* ============================================================================
 * STATS (web StatTile / TileGrid)
 * One lead figure per tile; "—" for not measured, never 0.
 * ============================================================================ */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.stat {
  display: block;
  min-width: 0;
  padding: var(--space-4);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs), 0 0 0 1px var(--ink-5);
  color: var(--color-storm-ink);
  text-decoration: none;
}

[data-theme="dark"] .stat { box-shadow: inset 0 0 0 1px var(--ink-10); }

a.stat:hover { background: var(--color-surface-2); text-decoration: none; }

.stat-label {
  display: block;
  font-size: var(--text-sm);
  line-height: 1.25rem;
  color: var(--color-storm-muted);
}

.stat-value {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-2xl);
  line-height: 2rem;
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
  color: var(--color-storm-ink);
}

.stat-delta {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  color: var(--color-storm-muted);
}

.stat-delta[data-tone="positive"] { color: var(--color-status-green); }
.stat-delta[data-tone="caution"] { color: var(--color-status-amber); }
.stat-delta[data-tone="critical"] { color: var(--color-status-red); }
.stat-delta[data-tone="info"] { color: var(--color-gauge-blue); }

/* ============================================================================
 * CARD (web Card — only for a thing you can open as a unit)
 * ============================================================================ */

.card {
  background: var(--color-surface);
  color: var(--color-storm-ink);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5) var(--space-6);
  box-shadow: var(--shadow-xs);
  margin-bottom: var(--space-5);
  position: relative;
  overflow: hidden;
}

.card-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.card-title {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--color-storm-ink);
  margin: 0;
}

/* ============================================================================
 * TABLE (web Table, `frame` geometry)
 * A bare table is a framed object: surface ground, ink-10 edge, a header
 * band at ink-3. th rule ink-10, td rule ink-5, row hover ink-3. td.num is
 * tabular and right-aligned; td.id is mono. .table-frame is the bordered
 * scroll box for a wide table; tr.row-link makes its row a link (put
 * data-row-link on the anchor in the first cell).
 * ============================================================================ */

table {
  border-collapse: collapse;
  width: 100%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  font-size: var(--text-sm);
  line-height: 1.5rem;
  color: var(--color-storm-ink);
}

th, td {
  text-align: left;
  padding: 0.625rem 0.75rem;
  font-size: var(--text-sm);
  color: var(--color-storm-ink);
}

th:first-child, td:first-child { padding-left: 1rem; }
th:last-child, td:last-child { padding-right: 1rem; }

th {
  background: var(--ink-3);
  border-bottom: 1px solid var(--ink-10);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  font-weight: 500;
  color: var(--color-storm-muted);
}

td { border-bottom: 1px solid var(--ink-5); }

tr:last-child td { border-bottom: none; }

/* Dense: a wide operational table (jobs) — tighter rows, same rules. */
table[data-variant="dense"] th,
table[data-variant="dense"] td { padding: 0.375rem 0.5rem; }
table[data-variant="dense"] th:first-child,
table[data-variant="dense"] td:first-child { padding-left: 0.75rem; }
table[data-variant="dense"] th:last-child,
table[data-variant="dense"] td:last-child { padding-right: 0.75rem; }

/* ui.table_empty — the honest empty row. */
tr.table-empty td { padding: 2rem 1rem; text-align: center; }
tr.table-empty:hover { background: transparent; }
tr.table-empty p { margin: 0 auto; max-width: 28rem; }
.table-empty-title { font-weight: 500; color: var(--color-storm-ink); }
.table-empty-detail { margin-top: var(--space-1) !important; font-size: var(--text-xs); line-height: 1.25rem; color: var(--color-storm-muted); }

tbody tr { transition: background var(--transition-base); }
tbody tr:hover { background: var(--ink-3); }

/* On a phone a wide table scrolls inside itself; the page never scrolls
 * sideways. */
@media (max-width: 640px) {
  table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
  }
}

/* ui.table_start's wrapper: the box scrolls, the table stays a table, so
 * its frame and row rules span the full width on a phone too. */
.table-scroll { max-width: 100%; overflow-x: auto; }
.table-scroll > table { display: table; overflow: visible; }
/* A cell that may break anywhere (a storage path, a hash) has no
 * min-content width, so a phone squeezed it to one character per line;
 * give it a floor and let the box scroll instead. */
.table-scroll td:has(> code) { min-width: 14rem; }

td.num, th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

td.id {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-storm-muted);
}

.table-frame {
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  margin-bottom: var(--space-5);
}

.table-frame > table,
.table-frame > .table-scroll > table {
  border: none;
  border-radius: 0;
  overflow: visible;
}

tr.row-link { position: relative; cursor: pointer; }
tr.row-link [data-row-link]::after {
  content: "";
  position: absolute;
  inset: 0;
}
tr.row-link:focus-within { outline: 2px solid var(--color-ring); outline-offset: -2px; }
tr.row-link [data-row-link]:focus-visible { outline: none; }

/* ============================================================================
 * BADGE (web Badge / StatusBadge)
 * <span class="badge" data-tone="positive" data-dot>label</span>.
 * A tone is a 15% wash of the hue with the hue as text; neutral/pending are
 * an ink-10 wash. data-dot adds a 6px dot in the text colour.
 * ============================================================================ */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.125rem 0.375rem;
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  font-weight: 500;
  white-space: nowrap;
  color: var(--color-storm-muted);
  background: var(--ink-10);
  border: none;
}

.badge[data-dot]::before,
.badge:not([data-tone])::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: currentColor;
  flex-shrink: 0;
}

.badge[data-outline] {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--ink-10);
  color: var(--color-storm-muted);
}

.badge[data-tone="neutral"] { color: var(--color-storm-muted); background: var(--ink-10); }
.badge[data-tone="pending"] { color: var(--ink-70); background: var(--ink-10); }
.badge[data-tone="info"] {
  color: var(--color-gauge-blue);
  background: color-mix(in oklab, var(--color-gauge-blue) 15%, transparent);
}
.badge[data-tone="positive"] {
  color: var(--color-status-green);
  background: color-mix(in oklab, var(--color-status-green) 15%, transparent);
}
.badge[data-tone="caution"] {
  color: var(--color-status-amber);
  background: color-mix(in oklab, var(--color-status-amber) 15%, transparent);
}
.badge[data-tone="critical"] {
  color: var(--color-status-red);
  background: color-mix(in oklab, var(--color-status-red) 15%, transparent);
}
.badge[data-tone="signal"] {
  color: var(--color-signal);
  background: color-mix(in oklab, var(--color-signal) 15%, transparent);
}

/* A running status pulses its dot; a stage that has not started is an
 * outline (it is not a state yet, only a place). */
.badge[data-status="running"]::before { animation: pulse-dot 1.4s ease-in-out infinite; }
.badge[data-status="not_started"] { background: transparent; box-shadow: inset 0 0 0 1px var(--ink-10); }

@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* ============================================================================
 * TABS (web Tabs / RouteTabs) — .doc-tabs is the legacy name
 * Plain links with ?tab=; the current one carries .active or
 * aria-current="page".
 * ============================================================================ */

.tabs,
.doc-tabs {
  display: flex;
  align-items: stretch;
  gap: var(--space-6);
  margin: var(--space-4) 0 var(--space-5);
  border-bottom: 1px solid var(--ink-10);
  overflow-x: auto;
  scrollbar-width: none;
}

.tabs a,
.tabs .tab-disabled,
.doc-tabs a,
.doc-tabs .doc-tab-disabled {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin-bottom: -1px;
  padding: 0 0 0.625rem;
  border-bottom: 2px solid transparent;
  font-size: var(--text-sm);
  line-height: 1.5rem;
  font-weight: 500;
  color: var(--color-storm-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--transition-base), border-color var(--transition-base);
}

.tabs a:hover,
.doc-tabs a:hover {
  color: var(--color-storm-ink);
  text-decoration: none;
}

.tabs a.active,
.tabs a[aria-current="page"],
.doc-tabs a.active,
.doc-tabs a[aria-current="page"] {
  color: var(--color-storm-ink);
  border-bottom-color: var(--color-gauge-blue);
}

.tabs .tab-disabled,
.doc-tabs .doc-tab-disabled {
  opacity: 0.5;
  cursor: default;
}

/* ============================================================================
 * SEGMENTED (web Segmented) — one answer out of a short ordered set.
 * Children are links, buttons or labels wrapping a radio. The selected one
 * is .is-on, aria-checked="true", aria-current, or holds a checked input.
 * ============================================================================ */

.segmented {
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 0.375rem;
}

.segmented > a,
.segmented > button,
.segmented > label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-width: 0;
  margin: 0;
  padding: 0.375rem 0.75rem;
  border: 1px solid var(--ink-15);
  border-radius: var(--radius-lg);
  background: transparent;
  box-shadow: none;
  color: color-mix(in oklab, var(--color-storm-ink) 75%, transparent);
  font-size: var(--text-sm);
  line-height: 1.25rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.segmented > label input { position: absolute; opacity: 0; pointer-events: none; }

.segmented > :hover {
  border-color: color-mix(in oklab, var(--color-storm-ink) 35%, transparent);
  background: var(--ink-3);
  text-decoration: none;
}

.segmented > .is-on,
.segmented > [aria-checked="true"],
.segmented > [aria-current],
.segmented > label:has(input:checked) {
  border-color: var(--color-gauge-blue);
  background: color-mix(in oklab, var(--color-gauge-blue) 15%, transparent);
  color: var(--color-storm-ink);
}

.segmented > [data-tone="positive"]:is(.is-on, [aria-checked="true"], [aria-current]) {
  border-color: var(--color-status-green);
  background: color-mix(in oklab, var(--color-status-green) 15%, transparent);
}
.segmented > [data-tone="caution"]:is(.is-on, [aria-checked="true"], [aria-current]) {
  border-color: var(--color-status-amber);
  background: color-mix(in oklab, var(--color-status-amber) 15%, transparent);
}
.segmented > [data-tone="critical"]:is(.is-on, [aria-checked="true"], [aria-current]) {
  border-color: var(--color-status-red);
  background: color-mix(in oklab, var(--color-status-red) 15%, transparent);
}
.segmented > [data-tone="neutral"]:is(.is-on, [aria-checked="true"], [aria-current]) {
  border-color: var(--ink-40);
  background: var(--ink-10);
}

/* A filter with more segments than fit a phone: wrap instead of
 * squeezing, each segment its natural width. */
.segmented[data-wrap] { display: flex; flex-wrap: wrap; margin: 0 0 var(--space-3); }
.segmented-count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-storm-muted);
}

/* ============================================================================
 * CALLOUT (web Callout) — addresses the reader about something that just
 * happened or that they should know before acting.
 * <div class="callout" data-tone="caution"><p class="callout-title">…</p>…</div>
 * ============================================================================ */

.callout {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-4);
  padding: 0.625rem var(--space-4);
  border: 1px solid var(--ink-10);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--ink-70);
  font-size: var(--text-sm);
  line-height: 1.5rem;
}

.callout > :first-child { min-width: 0; flex: 1; }
.callout p { margin: 0; }
.callout .callout-title { font-weight: 500; }
.callout .callout-title + * {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  opacity: 0.85;
}

.callout[data-tone="info"] {
  border-color: color-mix(in oklab, var(--color-gauge-blue) 25%, transparent);
  background: color-mix(in oklab, var(--color-gauge-blue) 5%, transparent);
  color: color-mix(in oklab, var(--color-storm-ink) 80%, transparent);
}
.callout[data-tone="positive"] {
  border-color: color-mix(in oklab, var(--color-status-green) 30%, transparent);
  background: color-mix(in oklab, var(--color-status-green) 5%, transparent);
  color: var(--color-status-green);
}
.callout[data-tone="caution"] {
  border-color: color-mix(in oklab, var(--color-status-amber) 40%, transparent);
  background: color-mix(in oklab, var(--color-status-amber) 10%, transparent);
  color: var(--color-status-amber);
}
.callout[data-tone="critical"] {
  border-color: color-mix(in oklab, var(--color-status-red) 25%, transparent);
  background: color-mix(in oklab, var(--color-status-red) 5%, transparent);
  color: var(--color-status-red);
}

/* ============================================================================
 * DRAWER (web Drawer) — an edge panel that annotates the page. A native
 * <dialog class="drawer"> opened with showModal().
 * ============================================================================ */

dialog.drawer {
  position: fixed;
  inset: 0 0 0 auto;
  margin: 0;
  width: min(28rem, 100vw);
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  padding: var(--space-6);
  overflow-y: auto;
  border: none;
  background: var(--color-surface);
  color: var(--color-storm-ink);
  box-shadow: var(--shadow-lg), 0 0 0 1px var(--ink-10);
}

dialog.drawer[data-size="lg"] { width: min(32rem, 100vw); }
dialog.drawer[data-size="xl"] { width: min(36rem, 100vw); }

dialog.drawer::backdrop { background: color-mix(in oklab, var(--color-scrim) 25%, transparent); }
[data-theme="dark"] dialog.drawer::backdrop { background: color-mix(in oklab, var(--color-scrim) 50%, transparent); }

dialog.drawer .drawer-title {
  margin: 0;
  padding-right: var(--space-8);
  font-size: var(--text-md);
  line-height: 1.5rem;
  font-weight: 600;
}

dialog.drawer .drawer-close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
}

dialog.drawer .drawer-desc { margin: var(--space-1) 0 0; font-size: var(--text-sm); }
dialog.drawer .drawer-body { margin-top: var(--space-6); }
dialog.drawer .drawer-body dl.drawer-fields { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-5); font-size: var(--text-sm); }
dialog.drawer .drawer-body dl.drawer-fields dt { color: var(--color-storm-muted); }
dialog.drawer .drawer-body dl.drawer-fields dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
dialog.drawer .drawer-body h3 { margin: var(--space-5) 0 var(--space-2); font-size: var(--text-sm); font-weight: 600; }
dialog.drawer .drawer-body pre { margin: 0; padding: var(--space-3); max-height: 20rem; overflow: auto; border-radius: var(--radius-md); background: var(--ink-3); font-size: var(--text-xs); white-space: pre-wrap; overflow-wrap: anywhere; }

dialog.drawer .drawer-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-8);
}

/* ============================================================================
 * EMPTY STATE (web EmptyState) — .empty-state is the legacy name.
 * The title is the claim, the detail the why, the action the way out.
 * ============================================================================ */

.empty,
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: 2.5rem var(--space-6);
  border: 1px dashed var(--ink-15);
  border-radius: var(--radius-lg);
  text-align: center;
}

.empty > :first-child,
.empty-state h3 {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.5rem;
  font-weight: 500;
  color: var(--color-storm-ink);
}

.empty p,
.empty-state p {
  max-width: 24rem;
  margin: 0 auto;
  font-size: var(--text-xs);
  line-height: 1.25rem;
  color: var(--color-storm-muted);
}

/* ============================================================================
 * SKELETON (web Skeleton) — data exists and is on its way. If a source
 * might legitimately have nothing, render an honest .empty instead.
 * ============================================================================ */

.skeleton {
  display: block;
  min-height: 1rem;
  border-radius: var(--radius-lg);
  background: var(--ink-5);
  animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes skeleton-pulse {
  50% { opacity: 0.5; }
}

/* ============================================================================
 * PROVENANCE (web Provenance) — a mono key–value strip answering "how do
 * we know this?". <dl class="provenance"><div><dt>…</dt><dd>…</dd></div></dl>
 * ============================================================================ */

.provenance {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-4);
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  text-transform: uppercase;
  letter-spacing: 0.025em;
}

.provenance > div { display: flex; align-items: baseline; gap: 0.375rem; }
.provenance dt { color: var(--color-storm-muted); }
.provenance dd { margin: 0; color: var(--ink-70); }
.provenance a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--ink-25);
  text-underline-offset: 2px;
}
.provenance a:hover { color: var(--color-storm-ink); }

/* ============================================================================
 * BREADCRUMB + BACKLINK
 * ============================================================================ */

.breadcrumb {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  line-height: 1.25rem;
  color: var(--color-storm-muted);
}

.breadcrumb a {
  color: var(--color-storm-muted);
  text-decoration: none;
}

.breadcrumb a:hover {
  color: var(--color-storm-ink);
  text-decoration: none;
}

.breadcrumb [aria-current="page"],
.breadcrumb > :last-child:not(a) { color: var(--color-storm-ink); }

.backlink {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  line-height: 1.5rem;
  color: var(--color-storm-muted);
  text-decoration: none;
}

.backlink::before { content: "←"; }

.backlink:hover {
  color: var(--color-storm-ink);
  text-decoration: none;
}

/* ============================================================================
 * CHIP — quiet value chips for tags, sources, etc.
 * ============================================================================ */

.chip {
  display: inline-block;
  padding: 0 var(--space-2);
  margin: 0 var(--space-1) 0.2rem 0;
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  color: var(--color-storm-muted);
  background: var(--ink-5);
  border: 1px solid var(--ink-10);
  white-space: nowrap;
}

a.chip { text-decoration: none; }

a.chip:hover {
  border-color: var(--color-gauge-blue);
  color: var(--color-storm-ink);
}

.kind-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.375rem;
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  font-weight: 500;
  text-transform: capitalize;
  background: color-mix(in oklab, var(--color-gauge-blue) 15%, transparent);
  color: var(--color-gauge-blue);
}

/* ============================================================================
 * OBJECT LINK — the primary drill-in action on a list row.
 * ============================================================================ */

.object-link {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  min-height: 2rem;
  padding: 0.25rem 0.375rem 0.25rem 0.75rem;
  border: 1px solid var(--color-primary-border);
  border-radius: var(--radius-lg);
  background: var(--color-primary-muted);
  color: var(--color-gauge-blue);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition:
    background var(--transition-base),
    border-color var(--transition-base),
    color var(--transition-base);
}

.object-link::after {
  content: "›";
  display: inline-grid;
  place-items: center;
  width: 1.375rem;
  height: 1.375rem;
  flex: 0 0 1.375rem;
  border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--color-gauge-blue) 14%, transparent);
  color: var(--color-gauge-blue);
  font-size: 1rem;
  line-height: 1;
  transition: background var(--transition-base), color var(--transition-base), transform var(--transition-base);
}

.object-link:hover {
  border-color: var(--color-gauge-blue);
  background: color-mix(in oklab, var(--color-gauge-blue) 18%, transparent);
  text-decoration: none;
}

.object-link:hover::after {
  background: var(--color-gauge-blue);
  color: var(--color-on-accent);
  transform: translateX(2px);
}

@media (prefers-reduced-motion: reduce) {
  .object-link,
  .object-link::after { transition: none; }
  .object-link:hover::after { transform: none; }
}

/* ============================================================================
 * PAGE HERO — page header with title, description, optional stats.
 * ============================================================================ */

.page-hero {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-6);
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
}

.page-hero .eyebrow {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  font-weight: 500;
  color: var(--color-storm-muted);
}

.page-hero h1 { margin: 0 0 var(--space-1); overflow-wrap: anywhere; }
.page-hero-text { min-width: 0; flex: 1 1 20rem; }
.page-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

.page-hero .hero-sub {
  margin: var(--space-1) 0 0;
  max-width: 42rem;
  font-size: var(--text-sm);
  line-height: 1.5rem;
  color: var(--color-storm-muted);
}

.hero-stats {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.stat-pill {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 0.625rem 1rem;
  min-width: 5.5rem;
  box-shadow: var(--shadow-xs);
}

.stat-pill .stat-value {
  display: block;
  margin: 0;
  font-size: var(--text-xl);
  line-height: 1.75rem;
  font-weight: 600;
  color: var(--color-storm-ink);
}

.stat-pill .stat-label {
  display: block;
  font-size: var(--text-xs);
  line-height: 1.25rem;
  font-weight: 500;
  color: var(--color-storm-muted);
}

.stat-pill.stat-warn .stat-value { color: var(--color-status-red); }

/* ============================================================================
 * FILTER BAR
 * ============================================================================ */

.filter-bar { margin-bottom: var(--space-4); }

.filter-grid {
  display: grid;
  grid-template-columns: minmax(9rem, 12rem) minmax(12rem, 1fr) minmax(9rem, 11rem) minmax(9rem, 12rem);
  gap: var(--space-3);
  align-items: end;
}

@media (max-width: 900px) {
  .filter-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 520px) {
  .filter-grid { grid-template-columns: 1fr; }
}

.filter-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-xs);
  line-height: 1.25rem;
  font-weight: 500;
  color: var(--color-storm-muted);
}

.filter-field select,
.filter-field input {
  font-weight: 400;
  width: 100%;
}

.filter-grow { min-width: 0; }

.filter-actions { display: flex; gap: var(--space-3); align-items: center; }

/* A long value (an error, an event type) clipped to one line; the full
 * text is in the title and the row's drawer. */
td.cell-clip { max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The documents list's search row: a field that grows and its actions. */
.doc-search {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-end;
  margin: 0 0 var(--space-3);
}
.doc-search-field { flex: 1 1 18rem; min-width: 0; max-width: 32rem; }
.doc-filter-note { margin: 0 0 var(--space-3); font-size: var(--text-sm); }

/* ============================================================================
 * PAGINATION
 * ============================================================================ */

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-4);
  border-top: 1px solid var(--ink-10);
}

.pagination-actions {
  display: flex;
  gap: var(--space-2);
}

/* ============================================================================
 * UPLOAD ROW
 * ============================================================================ */

.upload-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-end;
}

.upload-row > input[type=file] {
  flex: 1 1 14rem;
  min-width: 0;
}

.upload-row .upload-file { flex: 2 1 16rem; min-width: 0; }
.upload-row .upload-type { flex: 1 1 12rem; min-width: 0; }

/* ============================================================================
 * BULK TOOLBAR — select / act on a table's checked rows. It sits outside
 * the refreshing fragment so its dialogs survive a refresh.
 * ============================================================================ */

.bulk-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin: 0 0 var(--space-3);
}

.bulk-toolbar-end { margin-left: auto; }

td.nowrap, th.nowrap, .nowrap { white-space: nowrap; }
td.doc-name { overflow-wrap: anywhere; min-width: 12rem; }
/* A free-text cell (a vintage note, a description) keeps a readable width
 * and lets the table scroll instead of wrapping word by word. */
td.prose { min-width: 12rem; }

/* ============================================================================
 * PROGRESS BAR (web Progress) — determinate when it has a basis, else
 * indeterminate; never a time-based percentage.
 * ============================================================================ */

.progress-track {
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--ink-10);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  width: 0%;
  background: var(--color-gauge-blue);
  transition: width 0.3s ease;
}

.progress-track.is-indeterminate .progress-fill {
  width: 30%;
  transition: none;
  animation: progress-indeterminate 1.6s ease-in-out infinite;
}

@keyframes progress-indeterminate {
  0% { margin-left: -30%; }
  100% { margin-left: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .progress-track.is-indeterminate .progress-fill {
    animation: none;
    width: 100%;
    opacity: 0.35;
  }
}

/* ============================================================================
 * TOAST — action failure feedback.
 * ============================================================================ */

#action-toasts {
  position: fixed;
  top: var(--space-4);
  right: var(--space-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: min(22rem, calc(100vw - 2rem));
}

.toast {
  background: var(--color-surface);
  border: 1px solid var(--ink-10);
  border-left: 3px solid var(--color-status-red);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-storm-ink);
  animation: toast-in 0.15s ease;
}

.toast .toast-title {
  font-weight: 600;
  color: var(--color-status-red);
  margin-bottom: 0.125rem;
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}

/* ============================================================================
 * ELAPSED TIME TICKER
 * ============================================================================ */

.elapsed-warn { color: var(--color-status-amber); font-weight: 600; }
.elapsed-danger { color: var(--color-status-red); font-weight: 600; }

/* ============================================================================
 * DOCUMENT HEADER (was inline in base.html)
 * ============================================================================ */

.doc-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.doc-header > :first-child { min-width: 0; }
.doc-header h1,
.doc-header .muted { overflow-wrap: anywhere; }
.doc-header .muted { margin: 0.125rem 0 0; }
.doc-header .doc-actions { display: flex; gap: var(--space-2); align-items: center; flex-shrink: 0; flex-wrap: wrap; }

/* ============================================================================
 * STAGE PROGRESS (was inline in base.html) — the honest-progress readout
 * on a running stage (docs/observability.md).
 * ============================================================================ */

.stage-progress { margin: var(--space-2) 0 0.875rem; }
.sp-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); margin-bottom: 0.3rem; }
.sp-label { font-size: var(--text-sm); color: var(--color-storm-ink); }
.sp-count { font-family: var(--font-mono); font-weight: 600; }
.sp-pct { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 600; color: var(--color-gauge-blue); }
.sp-pct.sp-muted { color: var(--color-text-dim); font-weight: 400; font-size: var(--text-xs); }
.sp-caption { font-size: var(--text-xs); color: var(--color-text-dim); margin-top: 0.35rem; }
.sp-basis { border-bottom: 1px dotted var(--ink-20); cursor: help; }
.sp-last { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-text-dim); margin-top: 0.3rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.si-preview { font-size: var(--text-xs); color: var(--color-text-dim); padding: 0 0.7rem 0.35rem 2.4rem; margin-top: -0.15rem; }

/* ============================================================================
 * SIDEBAR LAYOUT
 * Master-detail sidebar pattern.
 * ============================================================================ */

.pipeline-layout,
.portal-layout {
  display: flex;
  gap: var(--space-5);
  align-items: flex-start;
}

.pipeline-sidebar,
.portal-sidebar {
  flex: 0 0 220px;
  width: 220px;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  position: sticky;
  top: var(--space-4); /* no sticky top bar since the sidebar shell */
}

.pipeline-detail,
.portal-main {
  flex: 1 1 auto;
  min-width: 0;
}

.sidebar-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.55rem var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-decoration: none;
  transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
}

.sidebar-item:hover {
  background: var(--color-surface-hover);
  color: var(--color-text);
}

.sidebar-item.active {
  background: var(--color-surface-2);
  border-color: var(--color-border-strong);
  color: var(--color-text);
}

.sidebar-item .si-index {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-dim);
  flex-shrink: 0;
  width: 1.6rem;
}

.sidebar-item .si-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-item .si-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  flex-shrink: 0;
  background: var(--color-border-strong);
}

.sidebar-item .si-dot.is-succeeded { background: var(--color-success); }
.sidebar-item .si-dot.is-running {
  background: var(--color-warning);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-status-amber) 18%, transparent);
  animation: pulse-dot 1.6s ease-in-out infinite;
}
.sidebar-item .si-dot.is-failed { background: var(--color-danger); }
.sidebar-item .si-dot.is-cancelled { background: var(--color-text-dim); }

.sidebar-group-label {
  text-transform: uppercase;
  letter-spacing: var(--tracking-widest);
  font-size: var(--text-xs);
  color: var(--color-text-dim);
  padding: var(--space-4) var(--space-3) var(--space-1);
  font-weight: 700;
}

@media (max-width: 900px) {
  .pipeline-layout,
  .portal-layout {
    flex-direction: column;
  }
  .pipeline-sidebar,
  .portal-sidebar {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    width: auto;
  }
  .sidebar-item {
    flex: 1 1 auto;
  }
  /* A stage's preview line ("17 pages") belongs under its own stage, not
     beside the next one in the wrapped row. */
  .pipeline-sidebar .si-preview {
    flex: 1 0 100%;
  }
  .sidebar-group-label {
    flex: 1 0 100%;
    padding: var(--space-2) var(--space-3) 0.1rem;
  }
}

/* ============================================================================
 * ATTENTION LIST (ui.attention_list) — rows in a sheet: severity badge,
 * title, detail; the link to act on it at the right.
 * ============================================================================ */

.attention-list .attention-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  min-width: 0;
}

.attention-list .attention-detail {
  flex-basis: 100%;
  margin: 0;
  overflow-wrap: anywhere;
}

.attention-list .attention-link { flex-shrink: 0; font-size: var(--text-sm); }

/* A long identifier in a stat caption (a revision name) wraps, not spills. */
.stat-delta { overflow-wrap: anywhere; }

/* Phones: stat tiles two to a row (web's TileGrid), not one tall column. */
@media (max-width: 480px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-value { font-size: var(--text-xl); line-height: 1.75rem; }
}

/* ============================================================================
 * PROMPT LIBRARY — one sheet per prompt (prompts_list / _prompt_editor).
 * ============================================================================ */

.prompt-card .sheet-head-text .mono-id { display: block; margin-top: var(--space-1); }
.prompt-card textarea.prompt-template {
  display: block;
  width: 100%;
  min-height: 10rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.5;
  resize: vertical;
}
.prompt-actions { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; margin-top: var(--space-3); }
.callout-list { margin: var(--space-1) 0 0; padding-left: 1.25rem; }
.brain-context {
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--ink-3);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}
.brain-context p { margin: 0 0 var(--space-1); }
.brain-context code { font-size: var(--text-xs); }
.htmx-request.brain-btn .brain-label::after { content: "…"; }

/* LLM usage — the pricing note under the headline figures. */
.usage-note { margin: 0 0 var(--space-2); font-size: var(--text-sm); }

/* A sheet's footer band (a flush sheet's form under its table), and a note
 * between the title band and a flush table. */
.sheet > .sheet-foot { margin: 0; padding: var(--space-4) var(--space-5); border-top: 1px solid var(--ink-5); }
.sheet > .sheet-note { margin-top: var(--space-3); margin-bottom: var(--space-3); font-size: var(--text-sm); }
.sheet-head-text p { overflow-wrap: anywhere; }

/* Hazard layers: the per-layer refresh row and the upload grid. */
.layer-refresh { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-end; }
.layer-refresh .filter-field { flex: 1 1 14rem; min-width: 0; }
.layer-upload { grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr)); align-items: end; }


/* Platform: Keys & audit. A one-line standing condition above the tabs
 * (badge + why), the issued secret with its copy button, the scope
 * checkboxes in the issue dialog, and the keyset pager under a table. */
.status-line { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline; margin: 0 0 var(--space-4); font-size: var(--text-sm); }
.secret-row { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; margin-top: var(--space-2); }
.secret-row .secret {
  min-width: 0;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-storm-ink);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
  user-select: all;
}
.check-group { display: grid; gap: var(--space-2); margin: 0; padding: 0; border: none; }
.check-group legend { margin-bottom: var(--space-1); padding: 0; font-size: var(--text-xs); font-weight: 700; letter-spacing: var(--tracking-wider); text-transform: uppercase; color: var(--color-text-muted); }
.check-option { display: flex; gap: var(--space-2); align-items: baseline; font-size: var(--text-sm); line-height: 1.25rem; }
.check-option input { flex: none; }
.check-option .muted { font-size: var(--text-xs); }
/* A cell that must not break (a timestamp, an actor, a key prefix): the
 * table scrolls sideways in its .table-scroll box instead. */
td.nowrap { white-space: nowrap; }
.drawer-body .provenance { margin-top: var(--space-4); }
.pager { display: flex; gap: var(--space-3); justify-content: center; margin: var(--space-4) 0; font-size: var(--text-sm); }

/* Platform: Health, Database, Storage. A check row's text beside its
 * badge; two sheets side by side that stack on a phone; a bar in a cell. */
.check-main { min-width: 0; flex: 1; }
.check-main p { margin: var(--space-1) 0 0; overflow-wrap: anywhere; }
.split-sheets { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr)); gap: var(--space-4); }
.split-sheets > .sheet { margin-bottom: 0; }
.share-cell { min-width: 10rem; }
.share-cell .ds-bar-row { margin: 0; grid-template-columns: 1fr 3rem; }
.share-cell .ds-bar-label { display: none; }

/* Health: the configuration key-value grid (mono keys, wrapping values). */
.config-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr)); gap: var(--space-3) var(--space-5); margin: 0; font-size: var(--text-sm); }
.config-grid dt { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-storm-muted); }
.config-grid dd { margin: 0; color: var(--color-storm-ink); overflow-wrap: anywhere; }
.sheet-value.nowrap { white-space: nowrap; }
