:root {
  color-scheme: dark;
  --bg: #0b1020;
  --bg-2: #141a2e;
  --bg-3: #1c2238;
  --fg: #d9e1f2;
  --fg-2: #9fb1d6;
  --accent: #6ea1ff;
  /* v0.79.7: queued is BLUE, but must stay distinguishable from running when
     the pulse is off-frame or motion is reduced — so it is the accent
     desaturated and darkened, not the accent itself. */
  --accent-dim: #3d5a99;
  --ok: #55d495;
  --warn: #ffd166;
  --err: #ff7070;
  /* v0.79.7: skipped is GRAY — it ran and no-opped. Distinct from queued
     (blue, not started yet) and from unstarted (hollow, never ran at all). */
  --skip: #6b7280;
  --border: #283052;
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* fix v0.86.10: ONE content size for a step — the size the CONTROL captions
     (topic_id · langs · budget $) already render at, which was the document
     default and therefore never stated anywhere. Everything else in a step had
     drifted below it in three different steps (13px, 12px, 11px), so the same
     screen read as four typographic registers. Two exceptions, both deliberate:
     a block HEADER is 11px bold uppercase (it is a label for a region, not
     content), and a TABLE HEADER stays a step smaller than its own cells. */
  --fs-content: 1rem;
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--fg); }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 16px; background: var(--bg-2);
  border-bottom: 1px solid var(--border);
}
.topbar-left { display: flex; align-items: center; gap: 16px; }
.brand { font-weight: 700; letter-spacing: 0.5px; color: var(--accent); }
.group-select {
  background: var(--bg-3); color: var(--fg);
  border: 1px solid var(--border); border-radius: 4px;
  padding: 4px 10px; font-size: 13px; cursor: pointer;
  font-family: inherit;
}
.group-select:focus { outline: 1px solid var(--accent); }
.topbar-right { display: flex; align-items: center; gap: 16px; }
.header-stats {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 2px; line-height: 1.2;
}
.header-version {
  font-size: 12px; color: var(--fg-dim, #8a8f99);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.3px;
}
/* v0.4.32: the commit subject under the idle pill — single line, truncated
   (full text in the title attr); right-aligned by .header-stats. */
.commit-name {
  max-width: 360px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; text-align: right;
}
.header-credits {
  display: flex; gap: 12px; font-size: 11px;
  color: var(--fg-dim, #8a8f99);
}
.header-credits .hc-item { display: inline-flex; gap: 4px; align-items: baseline; }
.header-credits .hc-label { opacity: 0.7; }
.header-credits .hc-val { color: var(--fg); font-variant-numeric: tabular-nums; }
/* v0.49.14: Novita balance shares the running pill's row, vertically centered on
   it. The fixed gap is the "static margin" — the pill can grow/shrink (idle ↔
   running) without shifting the balance's alignment relative to it. */
.header-top-row { display: flex; align-items: center; gap: 12px; }
/* v0.49.13: Novita balance readout, left of the running pill. */
.novita-balance {
  display: inline-flex; align-items: baseline; gap: 4px;
  font-size: 12px; color: var(--fg-dim, #8a8f99);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.novita-balance .nb-label { opacity: 0.7; }
.novita-balance .nb-val { color: var(--fg); }
/* v0.62.1: YouTube daily-quota readout — same treatment as the Novita balance,
   sitting to its right in the header-top-row. */
.yt-quota { margin-left: 2px; }
.running-indicator {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--bg-3); font-size: 13px;
}
.running-indicator.idle .dot { background: #555; }
.running-indicator.running .dot { background: var(--accent); animation: pulse 1.4s infinite; }
.running-indicator .dot {
  width: 8px; height: 8px; border-radius: 50%; display: inline-block; background: #555;
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.4; transform: scale(1.3); }
}

main { display: flex; flex-direction: column; padding: 12px 16px; gap: 12px; }

.tabs-bar {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.tab-groups { display: flex; gap: 24px; align-items: flex-end; flex-wrap: wrap; flex: 1; }
.tabs { display: flex; gap: 4px; }
.tab {
  padding: 8px 12px; background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 6px 6px 0 0; cursor: pointer; display: flex; gap: 8px; align-items: center;
  font-size: 14px;
}
.tab.active { background: var(--bg-3); border-bottom-color: transparent; color: var(--fg); }
.tab.unimplemented { opacity: 0.55; }
.tab.unimplemented .badge { background: var(--bg); color: #555; }
.tab .badge {
  min-width: 24px; padding: 1px 6px; border-radius: 10px;
  background: var(--bg); color: var(--fg-2); font-size: 12px; text-align: center;
}
.tab.active .badge { background: var(--accent); color: var(--bg); }
.io-box.unimplemented {
  color: var(--fg-2); font-style: italic; opacity: 0.7;
}
.step-dot.unimplemented { background: #2a3252; }

.panel {
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 0 6px 6px 6px;
  padding: 12px; min-height: 60vh;
}
.panel-header {
  display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;
}
.panel-header h2 { margin: 0; font-size: 16px; font-weight: 600; color: var(--fg); }
.panel-controls { display: flex; gap: 12px; align-items: center; }
.chain-toggle { display: flex; gap: 6px; align-items: center; font-size: 13px; color: var(--fg-2); }

.run-btn {
  padding: 3px 14px; border-radius: 4px; border: 0; background: var(--ok); color: #04180c;
  font-weight: 600; cursor: pointer; font-size: 13px; line-height: 1.6;
}
/* v0.49.7: the running/stop state is a soft, slightly-desaturated pastel red
   (still clearly red) with legible light text — not the harsh brown-red fill. */
.run-btn.stop { background: #c96f6f; color: #fff1f1; }
.run-btn.stop:disabled { opacity: 0.85; }
.run-btn:disabled { opacity: 0.6; cursor: not-allowed; }

/* v0.4.16: captured LLM exchange (prompt/response), expandable */
.llm-exchange { margin: 4px 0; border: 1px solid var(--border, #333); border-radius: 4px; }
.llm-exchange > summary { cursor: pointer; padding: 4px 8px; font-size: 13px; }
.muted.xs { font-size: 11px; margin: 4px 8px 0; }
.llm-text {
  white-space: pre-wrap; word-break: break-word; margin: 2px 8px 8px;
  max-height: 320px; overflow: auto; font-size: 12px; line-height: 1.4;
  background: var(--bg-2, #161616); padding: 6px 8px; border-radius: 3px;
}

.dump-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 5px 9px; border-radius: 4px;
  background: var(--bg); color: var(--fg-2);
  border: 1px solid var(--border); cursor: pointer;
  font-family: inherit;
}
.dump-btn:hover { background: var(--bg-2); color: var(--fg); border-color: var(--accent); }
.dump-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.dump-btn[hidden] { display: none; }

.master-btn { font-size: 12px; padding: 4px 10px; }

.master-dialog {
  background: var(--bg-2); color: var(--fg);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 0; min-width: 320px;
}
.master-dialog::backdrop { background: rgba(0, 0, 0, 0.55); }
.master-dialog-form { padding: 16px 18px; display: flex; flex-direction: column; gap: 10px; }
.master-dialog h3 { margin: 0; font-size: 15px; font-weight: 600; }
.master-dialog-hint { margin: 0; font-size: 12px; color: var(--fg-2); }
.master-providers { display: flex; flex-direction: column; gap: 6px; padding: 4px 0; }
.master-providers label {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; cursor: pointer;
}
.master-dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.master-dialog-actions button {
  background: var(--bg-3); color: var(--fg);
  border: 1px solid var(--border); border-radius: 4px;
  padding: 5px 12px; font-size: 13px; cursor: pointer;
  font-family: inherit;
}
.master-dialog-actions button.primary {
  background: var(--accent); color: var(--bg); border-color: var(--accent); font-weight: 600;
}
.master-dialog-actions button:disabled { opacity: 0.5; cursor: not-allowed; }
.master-status { font-size: 12px; color: var(--fg-2); min-height: 14px; }
.master-status.err { color: var(--err); }
.master-status.ok  { color: var(--ok); }

.steps-layout { display: grid; grid-template-columns: 220px 1fr; gap: 12px; }

.steps-sidebar {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 4px;
  padding: 6px; display: flex; flex-direction: column; gap: 2px;
}
.step-item {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  border-radius: 3px; cursor: pointer; font-size: var(--fs-content);
  color: var(--fg-2);
}
.step-item:hover { background: var(--bg-3); }
.step-item.active { background: var(--bg-3); color: var(--fg); }
/* v0.79.7 — the step-dot vocabulary, stated once for every step in every
   pipeline: queued blue · done green · skipped gray · failed red · warn yellow.
   The bare class IS queued (stepDotCls emits no modifier for `queued`, and
   zeroStepState's `planned` lands here too), which is why the default fill
   carries the queued colour rather than a neutral grey. */
.step-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent-dim);
  flex: 0 0 8px;
}
.step-dot.pending { background: var(--accent-dim); }
.step-dot.running { background: var(--accent); animation: pulse 1.4s infinite; }
.step-dot.done { background: var(--ok); }
.step-dot.warning { background: var(--warn); }
.step-dot.failed { background: var(--err); }
.step-dot.skipped { background: var(--skip); }
/* v0.76.5: hollow — the step exists and did not run (distinct from `queued`,
   which is the plain default fill, and from `skipped`, which ran and no-opped) */
.step-dot.unstarted { background: transparent; box-shadow: inset 0 0 0 1px #4a5478; }
.pill.unstarted { color: var(--fg-3); border-color: #4a5478; }

.step-cost {
  font-size: 11px; color: var(--fg-3); font-variant-numeric: tabular-nums;
  margin-left: auto; opacity: 0.85; white-space: nowrap;
}
.step-cost:empty { display: none; }

.step-tags { display: flex; gap: 4px; flex-shrink: 0; margin-left: 8px; }
.step-tag {
  font-size: 9px; padding: 1px 5px; border-radius: 3px;
  font-weight: 500; opacity: 0.7; letter-spacing: 0.3px;
}
.step-tag-scrape { background: rgba(251, 191, 36, 0.15); color: #fbbf24; }
.step-tag-llm    { background: rgba(139, 92, 246, 0.15); color: #a78bfa; }
.step-tag-geo    { background: rgba(34, 197, 94, 0.15);  color: #4ade80; }
.step-tag-fix    { background: rgba(59, 130, 246, 0.18); color: #60a5fa; }

.step-detail {
  display: flex; flex-direction: column; gap: 10px;
  min-height: 400px;
}
.step-detail-empty { color: var(--fg-2); font-style: italic; }

.io-block {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 10px 12px;
  border-left: 3px solid var(--border);
}
.io-block:empty { display: none; }
.io-block h3 {
  margin: 0 0 8px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 1px; color: var(--fg-2); font-weight: 700;
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
#input-block  { border-left-color: var(--accent); }
#output-block { border-left-color: var(--ok); }
#log-block    { border-left-color: #7a5cff; }
#stat-block   { border-left-color: var(--warn); }
.io-box {
  background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
  padding: 8px; font-family: "SF Mono", Menlo, monospace; font-size: 12px;
  white-space: pre-wrap; word-break: break-word; max-height: 60vh; overflow: auto;
  color: var(--fg);
}
/* v0.48.2: run-level LOG window — a run's lifecycle narration, newest at bottom */
.run-log {
  background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
  padding: 8px; font-family: "SF Mono", Menlo, monospace; font-size: 12px;
  line-height: 1.5; max-height: 40vh; overflow: auto; color: var(--fg);
}
.run-log-line { white-space: pre-wrap; word-break: break-word; }
.run-log-line.lvl-warn  { color: var(--warn); }
.run-log-line.lvl-error { color: var(--err, #ff6b6b); }
.rl-seq { display: inline-block; min-width: 2.2em; text-align: right; opacity: .55; }

.spec-box {
  background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
  padding: 10px 12px; margin: 0;
  font-family: "SF Mono", Menlo, monospace; font-size: 12px; line-height: 1.45;
  white-space: pre-wrap; word-break: normal; overflow-wrap: anywhere;
  max-height: 40vh; overflow: auto; color: var(--fg-2);
}
.spec-toggle {
  display: flex; align-items: center; gap: 6px; width: 100%;
  margin: 0 0 8px; padding: 0 0 6px;
  background: transparent; border: 0; border-bottom: 1px solid var(--border);
  color: var(--fg-2); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1px;
  cursor: pointer; font-family: inherit; text-align: left;
}
.spec-toggle:hover { color: var(--fg); }
.spec-caret { display: inline-block; transition: transform 0.15s; font-size: 10px; }
.spec-collapsed .spec-toggle { margin-bottom: 0; padding-bottom: 0; border-bottom: 0; }
.spec-collapsed .spec-box { display: none; }
.io-block:not(.spec-collapsed) .spec-caret { transform: rotate(90deg); }
#spec-block { border-left-color: #7aa5ff55; }
.io-box .table-preview { min-width: 100%; }
.kv-row { display: flex; gap: 8px; padding: 2px 0; }
.kv-key { color: var(--fg-2); min-width: 120px; }
.kv-val { color: var(--fg); }

.table-preview { width: 100%; border-collapse: collapse; }
.table-preview th, .table-preview td {
  padding: 4px 6px; border-bottom: 1px solid var(--border); text-align: left;
}
.table-preview th { color: var(--fg-2); font-weight: 600; font-size: 11px; text-transform: uppercase; }

.log-viewer {
  background: #02080f; border: 1px solid var(--border); border-radius: 3px;
  padding: 8px; font-family: "SF Mono", Menlo, monospace; font-size: 11px;
  white-space: pre-wrap; max-height: 40vh; min-height: 60px;
  overflow-y: auto; color: #b8c7e0;
}
/* v0.74.1: per-line level colouring in the step LOG panel — same palette the
   run-level LOG window uses (WARN yellow, ERROR red). */
.log-line { white-space: pre-wrap; word-break: break-word; }
.log-line.lvl-warn  { color: var(--warn); }
.log-line.lvl-error { color: var(--err, #ff6b6b); }
.row-num { color: var(--fg-2); width: 40px; text-align: right; }

/* v0.76.11: the step LOG panel's Step/Full toggle. */
.log-scope-toggle { display: flex; gap: 6px; margin-bottom: 6px; }
.log-scope-btn {
  padding: 3px 9px; border-radius: 4px; font-size: 11px;
  background: var(--bg); color: var(--fg-2);
  border: 1px solid var(--border); cursor: pointer; font-family: inherit;
}
.log-scope-btn:hover { background: var(--bg-2); color: var(--fg); border-color: var(--accent); }
.log-scope-btn.active { background: var(--accent); color: var(--bg); border-color: var(--accent); }

/* v0.51.9: the cached-eligible (system) part of a stored LLM request — the
   part the provider prefix cache can serve — renders visibly distinct. */
.llm-cached { background: #0a1a2e; border-left: 3px solid #3b6ea5; color: #9fb8d8; }

.hint { color: var(--fg-2); font-size: 12px; }
.err-text { color: var(--err); font-size: 12px; }

/* ─── synth thread-manager (kudago_synth_v2 / 02_synth only) ─────────────── */
.synth-thread-summary {
  font-size: 12px; color: var(--fg-2); margin-bottom: 8px;
  font-family: "SF Mono", Menlo, monospace;
}
.synth-thread-grid {
  display: flex; flex-wrap: wrap; gap: 3px;
}
.synth-thread-cell {
  width: 14px; height: 14px; border-radius: 2px;
  background: #3a4368;
}
.synth-thread-cell.queued  { background: #3a4368; }
.synth-thread-cell.running { background: var(--accent); animation: pulse 1.4s infinite; }
.synth-thread-cell.done    { background: var(--ok); }
.synth-thread-cell.failed  { background: var(--err); }
.synth-thread-cell.skipped { background: #2a2f3d; opacity: 0.55; }

/* ─── sidebar TOTAL panel (widgets/total_panel.js) ──────────────────────── */
.steps-side { display: flex; flex-direction: column; gap: 8px; }
.total-panel {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 4px;
  padding: 10px 12px; font-size: 12px; color: var(--fg-2);
  font-family: "SF Mono", Menlo, monospace;
  display: flex; flex-direction: column; gap: 4px;
}
.total-title {
  font-size: 11px; text-transform: uppercase; letter-spacing: 1px;
  color: var(--fg-2); opacity: 0.6; font-weight: 600; margin-bottom: 4px;
}
.total-row {
  display: grid; grid-template-columns: 80px 1fr; column-gap: 8px;
  align-items: baseline;
}
.total-label { color: var(--fg-2); opacity: 0.7; }
.total-value { color: var(--fg); word-break: break-all; }

/* ─── step controls (widget slot above Input panel) ──────────────────────── */
.control-slot { margin-bottom: 12px; }
.control-banner {
  font-size: 11px; color: var(--err); padding: 4px 8px;
  background: rgba(255,112,112,0.08); border: 1px solid rgba(255,112,112,0.3);
  border-radius: 3px; margin-top: 4px;
}

/* SourceModeAndCount */
.smc { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.smc-modes { display: flex; gap: 12px; align-items: center; }
.smc-modes label {
  display: inline-flex; gap: 4px; align-items: center; font-size: 13px;
  color: var(--fg-2); cursor: pointer;
}
.smc-modes input[type="radio"] { accent-color: var(--accent); cursor: pointer; }
.smc-count, .smc-id { display: inline-flex; gap: 6px; align-items: center; font-size: 13px; color: var(--fg-2); }
.smc-count input, .smc-id input, .smc-status select {
  background: var(--bg); color: var(--fg); border: 1px solid var(--border);
  border-radius: 3px; padding: 3px 6px; font-size: 13px; font-family: inherit;
}
.smc-count input { width: 70px; }
.smc-id input { width: 90px; }
.smc-status select { min-width: 200px; }
.smc[data-disabled="true"] input,
.smc[data-disabled="true"] select { opacity: 0.5; pointer-events: none; }

/* AddressBook */
.ab { display: flex; flex-direction: column; gap: 8px; }
.ab-cards { display: flex; gap: 6px; flex-wrap: wrap; }
.ab-card {
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
  padding: 6px 10px; cursor: pointer; font-size: 12px; color: var(--fg-2);
  display: flex; flex-direction: column; gap: 2px; min-width: 130px;
}
.ab-card:hover { background: var(--bg-2); }
.ab-card.active { background: var(--bg-3); border-color: var(--accent); color: var(--fg); }
.ab-card-name { font-weight: 600; font-size: 13px; }
.ab-card-meta { font-size: 10px; opacity: 0.7; font-family: "SF Mono", Menlo, monospace; }
.ab-params {
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  font-size: 13px; color: var(--fg-2);
  background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
  padding: 8px 10px;
}
.ab-params label { display: inline-flex; gap: 4px; align-items: center; }
.ab-params input, .ab-params select {
  background: var(--bg-2); color: var(--fg); border: 1px solid var(--border);
  border-radius: 3px; padding: 3px 6px; font-size: 13px; font-family: inherit;
}
.ab-params input.invalid { border-color: var(--err); }
.ab-params input[type="number"] { width: 70px; }
.ab-params input[type="text"] { width: 320px; }
.ab[data-disabled="true"] input,
.ab[data-disabled="true"] select,
.ab[data-disabled="true"] .ab-card { opacity: 0.5; pointer-events: none; }

/* ─── KudagoFetchParams widget (kudago_fetch/01_fetch) ─────────────────── */
.kfp { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.kfp-field { display: inline-flex; gap: 6px; align-items: center; font-size: 13px; color: var(--fg-2); }
.kfp-field input, .kfp-field select {
  background: var(--bg); color: var(--fg); border: 1px solid var(--border);
  border-radius: 4px; padding: 4px 8px; font-family: inherit; font-size: 13px;
}
.kfp-field input[type="number"] { width: 80px; }
.kfp-field select { min-width: 180px; }
.kfp[data-disabled="true"] input,
.kfp[data-disabled="true"] select { opacity: 0.5; pointer-events: none; }

/* ─── STAT panel (step-level summary above INPUT) ──────────────────────── */
.stat-list {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  font-family: "SF Mono", Menlo, monospace;
}
.stat-list .kv-row { gap: 12px; }
.stat-list .kv-key { min-width: 160px; color: var(--fg-2); }
.stat-list .kv-val { color: var(--fg); word-break: break-all; }

/* ─── IoTable widget (input/output row tables) ─────────────────────────── */
.io-slot { margin-bottom: 8px; }
.io-slot:empty { display: none; }

.kg-table-wrap { display: flex; flex-direction: column; gap: 6px; }
.kg-table-meta {
  display: flex; justify-content: flex-end; align-items: center; gap: 8px;
}
.kg-table-count {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fg-2);
  padding: 2px 8px;
  border-radius: 10px;
  background: var(--bg-2);
}

.kg-table {
  display: grid;
  gap: 0;
  font-size: 13px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.kg-table-header,
.kg-table-body,
.kg-table-row { display: contents; }

.kg-cell {
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: flex;
  align-items: center;
  gap: 6px;
}
.kg-table-row:last-child .kg-cell { border-bottom: 0; }
/* v0.49.4: a clickable row (drills into a child run). The row is display:contents,
   so cursor/hover live on its cells; clicks bubble to the row listener. */
.kg-row-click .kg-cell { cursor: pointer; }
.kg-row-click:hover .kg-cell { background: var(--bg-3); }

.kg-cell-num {
  text-align: right;
  justify-content: flex-end;
  color: var(--fg-2);
  font-family: "SF Mono", Menlo, monospace;
  font-size: 12px;
}
.kg-cell-title {
  font-weight: 500;
  color: var(--fg);
}
.kg-cell-head {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--fg-2);
  font-weight: 600;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
}
.kg-cell-empty {
  text-align: center;
  color: var(--fg-2);
  font-style: italic;
  padding: 12px;
}
/* v0.76.13: the per-row object-detail accordion — a DOM child of the row's
   own .kg-table-row div, spans every column. NEVER combine with .kg-cell
   (its overflow:hidden/ellipsis would clip a multi-line detail block). */
.kg-row-detail {
  grid-column: 1 / -1;
  white-space: normal;
  overflow: visible;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
}

/* v0.76.8: ItemGrid — the live StepExecution/Batch/Item progress grid
   (output.progress). Reuses .rollup/.rk-* (already styled above) for the
   summary chips; only the per-item cell mosaic is new here. */
.ig-wrap { display: flex; flex-direction: column; gap: 6px; }
.ig-meta { font-size: 12px; }
.ig-grid {
  display: flex; flex-wrap: wrap; gap: 3px;
  max-height: 160px; overflow-y: auto;
}
.ig-cell {
  width: 10px; height: 10px; border-radius: 2px;
  background: var(--bg-3);
}
.ig-cell-queued  { background: var(--bg-3); }
.ig-cell-running { background: var(--accent); animation: pulse 1.4s infinite; }
.ig-cell-done    { background: var(--ok); }
.ig-cell-failed  { background: var(--err); }
/* v0.79.5: the remaining two of the closed six-state set — retrying reuses the
   existing warning colour token (v0.76.10), skipped is a muted terminal. */
.ig-cell-retrying { background: var(--warn); }
.ig-cell-skipped  { background: var(--bg-3); opacity: 0.45; }
/* batch boundaries: a visible group of squares with a compact counters-only
   label — never an evidence surface. */
.ig-batch {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 3px 5px; margin: 2px 4px 2px 0;
  border: 1px solid var(--bg-3); border-radius: 4px;
}
.ig-batch-pending { border-style: dashed; }
.ig-batch-label { font-size: 10px; margin-right: 3px; white-space: nowrap; }
/* square ↔ object-row linkage */
.ig-click { cursor: pointer; }
.ig-hi { outline: 2px solid var(--fg); outline-offset: 1px; }

.kg-pill {
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kg-pill-status                 { background: rgba(159,177,214,0.10); color: var(--fg-2); }
.kg-pill-status-intake          { background: rgba(110,161,255,0.15); color: var(--accent); }
.kg-pill-status-discovery       { background: rgba(110,161,255,0.20); color: var(--accent); }
.kg-pill-status-synth           { background: rgba(85,212,149,0.18);  color: var(--ok); }
.kg-pill-status-verify          { background: rgba(85,212,149,0.28);  color: var(--ok); }
.kg-pill-status-failed          { background: rgba(255,112,112,0.15); color: var(--err); }
.kg-pill-status-expired         { background: rgba(255,209,102,0.18); color: var(--warn); }

.kg-pill-result-queued  { background: #3a4368; color: var(--fg-2); }
.kg-pill-result-running { background: var(--accent); color: var(--bg); animation: pulse 1.4s infinite; }
.kg-pill-result-done    { background: var(--ok);     color: #04180c; }
.kg-pill-result-failed  { background: var(--err);    color: #230; }

/* fix v0.79.3: the canonical SPEC section — folded by default. */
.spec-fold summary { cursor: pointer; font-size: 12px; }
.spec-fold[open] summary { margin-bottom: 6px; }

/* v0.79.4: umbrella cross-child matrix cells — colour is a TINT on the token
   text, never a replacement for it (the token itself always renders). */
.um-cell               { font-size: 12px; }
.um-click              { cursor: pointer; text-decoration: underline dotted; }
.um-not-received       { color: var(--fg-2); opacity: 0.55; }
.um-running            { color: var(--accent); }
.um-retrying           { color: var(--warn); }
.um-passed             { color: var(--ok); }
.um-reused             { color: var(--ok); opacity: 0.8; }
.um-rejected           { color: var(--warn); }
.um-failed             { color: var(--err); }
.um-skipped            { color: var(--fg-2); }
/* fix v0.79.4: a transport/provider failure is visibly not a domain verdict */
.um-transport          { text-decoration: underline wavy; }

.kg-verdict       { font-weight: 700; font-size: 14px; line-height: 1; }
.kg-verdict-pass  { color: var(--ok); }
.kg-verdict-fail  { color: var(--err); }
.kg-verdict-fixed { color: #d4a017; }

/* v0.4.7: runtime-object-view banner — makes the read-only drill-in obvious */
.rt-obj-banner {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 12px; padding: 7px 12px;
  background: rgba(255, 209, 102, 0.12);
  border: 1px solid var(--warn); border-left: 4px solid var(--warn);
  border-radius: 4px; color: var(--warn);
  font-size: 12px; font-weight: 600; letter-spacing: 0.4px;
}
.rt-obj-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--warn); }
.rt-obj-back { color: var(--accent); font-weight: 600; text-decoration: none; }
.rt-obj-back:hover { text-decoration: underline; }

/* v0.4.8: LLM step mark (ported from scrape's .step-tag-llm) — colored text on a
   soft, slightly-blurred purple background, sitting next to the step name. */
.step-llm {
  font-size: 9px; padding: 1px 6px; border-radius: 3px; margin-left: 8px;
  background: rgba(139, 92, 246, 0.16); color: #a78bfa;
  font-weight: 600; letter-spacing: 0.3px; white-space: nowrap;
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}

/* v0.49.0: a `danger` toggle (e.g. Erase) — neutral when off, red when armed.
   No confirm dialog: the red state IS the warning. Applied to the whole
   rf-row so the label + checkbox both read red while checked. */
.rf-row.danger-toggle input[type="checkbox"] { accent-color: var(--err); cursor: pointer; }
.rf-row.danger-toggle.on,
.rf-row.danger-toggle.on .rf-label { color: var(--err); font-weight: 600; }

/* v0.49.4: a run-child link in STAT (children row) — same affordance as .ln-hop. */
.child-link { color: var(--accent); text-decoration: none; cursor: pointer; }
.child-link:hover { text-decoration: underline; }

/* v0.72.1: Structure console — a navigation-surface row is a section
   heading (Explore/Educate/Music/Watch/Ambient), visually distinct from an
   ordinary child row so a real parent/child tree reads as one, not a flat
   dot-indented list. */
.st-surface {
  font-weight: 700; text-transform: uppercase; font-size: 11px;
  letter-spacing: 0.6px; color: var(--accent);
  border-top: 1px solid var(--border); padding-top: 8px; margin-top: 6px;
}
.st-state-banner { color: var(--fg-2); font-size: 12px; }
.st-findings:empty { display: none; }
.st-finding-orphan, .st-finding-duplicate, .st-finding-cycle {
  background: rgba(255, 209, 102, 0.14); color: var(--warn);
}
.st-role-attached_tree_root, .st-role-tree_root {
  background: rgba(110, 161, 255, 0.14); color: var(--accent);
}

/* v0.74.1 — Generation input. Two vocabularies, deliberately distinct:
   the AUTHORED/DERIVED origin tag (which of these is a human decision?) and
   the finding severity (what blocks promotion?). */
.st-origin-authored { background: rgba(110, 161, 255, 0.14); color: var(--accent); }
.st-origin-derived  { background: rgba(255, 255, 255, 0.05); color: var(--fg-2); }
.st-origin-untagged { background: rgba(255, 112, 112, 0.18); color: var(--err); }
.st-finding-error   { background: rgba(255, 112, 112, 0.14); color: var(--err); }
.st-finding-warning { background: rgba(255, 209, 102, 0.14); color: var(--warn); }
/* v0.74.3 — a Topic a generation run CREATED. Its own colour, distinct from the
   authored/derived tags above: that pair answers "who decided this field?",
   this one answers "did a run mint this Topic?". */
.st-origin-generated { background: rgba(167, 139, 250, 0.18); color: #c4b5fd; }
.st-generation-input textarea {
  background: var(--bg-2); color: var(--fg); border: 1px solid var(--border);
  border-radius: 4px; font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 4px 6px;
  margin: 2px 0; box-sizing: border-box;
}
.st-generation-input h4 {
  margin: 10px 0 4px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.6px; color: var(--fg-2);
}

/* v0.74.2 — the whole-tree plan, rendered in its DERIVED topological order.
   Indentation is the plan's own parentage, so a parent always reads above its
   children; nothing here is a per-branch control, because a plan is approved
   or rejected as a whole. */
.st-plan { font-family: var(--mono, monospace); font-size: 12px; margin: 6px 0; }
.st-plan-node { padding: 1px 0; white-space: nowrap; overflow-x: auto; }
.st-generation-run h4 { margin: 10px 0 4px; }

/* v0.74.4 — preview two composition modes SIDE BY SIDE, then publish.
   The two variants sit in a two-column grid because the operator's question is
   comparative ("which of these two compositions do I want"), and a stacked pair
   makes that a scrolling exercise. It collapses to one column when the pane is
   narrow, so a dragged-in pane degrades to reading order rather than to
   overlapping text. */
.st-preview-pair {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 8px; margin: 6px 0;
}
.st-preview-variant {
  border: 1px solid var(--bd, #333); border-radius: 3px; padding: 6px 8px;
  min-width: 0;                       /* so a long route id scrolls, not spills */
}
.st-diff-row {
  font-family: var(--mono, monospace); font-size: 12px; padding: 1px 0;
  white-space: nowrap; overflow-x: auto;
}
.st-preview-publish h4 {
  margin: 10px 0 4px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.6px; color: var(--fg-2);
}
/* the production confirmation is the one block on this page that must not read
   as ordinary chrome: it is the step that changes what feedlab serves. */
.st-confirm-production { border-left: 3px solid var(--err); }

/* v0.74.6 — the generated-subtree workflow's two new homes.

   The CONTROL widget is a whole authoring surface inside ONE control row, so it
   breaks out of the row's inline flow and gets its own bordered column; without
   this it would render as a very tall inline-flex cell beside its label. */
.rf-row.rf-generation-row {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  min-width: 0;
}
.gc-widget {
  border: 1px solid var(--bd, #333); border-radius: 3px; padding: 8px 10px;
  min-width: 0;
}
.gc-widget h4 {
  margin: 10px 0 4px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.6px; color: var(--fg-2);
}
.gc-dispatch-state { margin-top: 4px; }
/* the fingerprint field is OBSERVED, never typed — it reads as a readout rather
   than as an editor the operator simply has not filled in yet. */
input.rf-observed { opacity: 0.75; cursor: default; }

/* the plan review surface. A destination, so it lays out full width like a run
   inspection rather than inside the Topic area's narrow left pane — which is
   the whole reason these blocks moved. */
.plan-review { display: flex; flex-direction: column; gap: 8px; }
.plan-review-header h2 { margin: 0 0 4px; }
.plan-step { padding: 2px 0; }
.plan-lifecycle .st-badge.st-role-mount { opacity: 1; }
.plan-topology h4 {
  margin: 10px 0 4px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.6px; color: var(--fg-2);
}
/* a recorded object's owning surface, offered beside its table */
.object-destinations { margin-top: 6px; }

/* a copyable identifier (double-click) — the affordance is the dotted
   underline; the interaction itself is declared in the view manifest. */
.copyable {
	border-bottom: 1px dotted var(--fg-2);
	cursor: copy;
}
/* fix v0.86.10: on the manifest path the dotted rule is a HOVER affordance, not
   a permanent mark. At rest it read as a stray fragment of underline under one
   value — the run id — with nothing to distinguish it from a rendering fault. */
.io-block--tight .copyable { border-bottom-color: transparent; }
.io-block--tight .copyable:hover { border-bottom-color: var(--fg-2); }


/* ── v0.86.10: the corrected step view ────────────────────────────────────── */
/* fix v0.86.10: the manifest path's own rhythm. The six sections used to spend
   more height on their chrome than on their content; this cuts the padding that
   separates them so the screen reads as columns. Scoped to `--tight` so every
   legacy step view stays byte-unchanged. */
/* fix v0.86.10: one content size across a step's panels, scoped to the manifest
   path so no other pipeline's screen moves. The block HEADER keeps its own
   11px bold uppercase — it labels a region rather than stating content — and a
   TABLE HEADER keeps the smaller size it already had, a step under its cells. */
.io-block--tight,
.io-block--tight .field-grid,
.io-block--tight .kv-row,
.io-block--tight .vrow-cell,
.io-block--tight .run-log,
.io-block--tight .obj-row-select,
.io-block--tight .artifact-action,
.io-block--tight .log-view-seg,
.io-block--tight .muted.xs { font-size: var(--fs-content); }
.io-block--tight .vrow-cell.vrow-key,
.io-block--tight .vgroup-name { font-size: 10px; }

/* fix v0.86.10: SPEC · STAT · INPUT · OUTPUT · LOG share ONE frame. Each had
   arrived at its own padding a scope at a time, so five sections of one screen
   sat on five different left edges. Now: the same box on every one, the HEADER
   flush with the block's left edge, and every piece of CONTENT one step in from
   it — so the eye finds one indent line down the whole panel instead of five. */
.io-block--tight { padding: 5px 10px 7px; }
.io-block--tight > *:not(h3) { padding-left: 16px; }
/* an inner surface must not indent AGAIN — it would put its text a second step
   in and break the one line the rule above exists to make */
.io-block--tight .obj-actions,
.io-block--tight .obj-open,
.io-block--tight .obj-siblings,
.io-block--tight .vgroup { padding-left: 0; }
.io-block--tight h3 {
	/* the header sits ON the left edge, and the space under it is the same in
	   every section */
	margin: 0 0 6px;
	/* fix v0.86.10: no rule under the section title. On a four-line block the
	   separator cost as much height as a line of content and separated the
	   title from the only thing it titles. */
	padding-bottom: 0;
	border-bottom: none;
	display: flex;
	align-items: center;
	gap: 10px;
}
.io-block--tight h3 .io-title { flex: none; }
.io-block--tight h3.io-head-click { cursor: pointer; }
/* the disclosure marker sits AFTER the section word — `SPEC ▸` — because the
   title line IS the control, and a section whose control is its title needs
   nothing below it to click (fix v0.86.10) */
.io-caret {
	flex: none;
	font-size: 9px;
	line-height: 1;
	color: var(--fg-2);
	margin-left: -4px;
}
.io-block--tight h3.io-head-click:hover .io-caret { color: var(--fg); }
.io-body[hidden] { display: none; }
.io-block--tight .kv-row { padding: 0; }
/* the kv surface carries vertical room only: a left pad here would put its rows
   a second step in and break the panel's one indent line */
.io-block--tight .io-box { padding: 4px 6px 4px 0; }

/* STAT — a compact labelled grid, not a dot-separated sentence. Two columns on
   a narrow panel, four when there is room; a `full`-span field runs its value
   to the end of the row. */
/* Every track is max-content and the grid does not stretch, so the second
   column is exactly as wide as its own widest value and the third column's
   label lands just past it — not at the far edge of the panel, which is what a
   `1fr` value track did. */
.field-grid {
	display: inline-grid;
	grid-template-columns: max-content minmax(0, max-content);
	/* fix v0.86.10: 2px between the lines. At zero they were a wall of text; the
	   block is four lines and can afford to let them breathe. */
	gap: 2px 12px;
	font-size: 12px;
}
@media (min-width: 900px) {
	.field-grid {
		grid-template-columns: max-content minmax(0, max-content)
		                       max-content minmax(0, max-content);
	}
}
.field-grid .fg-key { color: var(--fg-2); }
.field-grid .fg-val { color: var(--fg); }

/* RowTable — a section as ONE table row that opens. The columns are the
   section's summary-role fields; the body is what it opens into. */
.vrow-summary { cursor: pointer; }
/* the opened body: three short labelled groups, not one ledger */
.vgroup { margin-top: 6px; }
.vgroup:first-child { margin-top: 4px; }
.vgroup-name {
	font-size: 10px;
	text-transform: uppercase;
	letter-spacing: .6px;
	color: var(--fg-2);
	margin-bottom: 1px;
}
.vgroup .kv-key { min-width: 190px; }
.vrow-table {
	display: inline-grid;
	gap: 0 14px;
	font-size: 12px;
	vertical-align: top;
}
.vrow-cell { white-space: nowrap; color: var(--fg); }
.vrow-cell.vrow-key {
	color: var(--fg-2);
	font-size: 10px;
	text-transform: uppercase;
	letter-spacing: .6px;
}

/* OUTPUT — selection, not accordions */
.obj-id { font-family: var(--mono, monospace); }
.obj-siblings { margin: 6px 0 0; }
.obj-row-select {
	padding: 4px 10px;
	font-size: 12px;
	cursor: pointer;
	border-left: 2px solid transparent;
	background: var(--bg-2, rgba(255,255,255,.03));
}
.obj-row-select:hover { background: rgba(88, 166, 255, .10); }
.obj-row-select.selected {
	border-left-color: var(--accent, #58a6ff);
	background: rgba(88, 166, 255, .16);
	color: var(--fg);
}

/* fix v0.86.10: the selected object's actions sit on their own DARK band
   directly beneath its row, and what an action opens appears under the band.
   The band is what makes the strip read as belonging to the row above it. */
.obj-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	align-items: center;
	padding: 6px 10px;
	background: var(--bg, #0d1117);
	border-left: 2px solid var(--accent, #58a6ff);
}
.obj-open:empty { display: none; }
.obj-open {
	background: var(--bg, #0d1117);
	border-left: 2px solid var(--accent, #58a6ff);
	padding: 0 10px 8px;
}
.obj-open .io-box { background: transparent; border: 0; padding: 0; }
.artifact-action {
	font-family: var(--mono, monospace);
	font-size: 11px;
	padding: 3px 10px;
	border-radius: 3px;
	border: 1px solid var(--line, #30363d);
	background: transparent;
	color: var(--fg-2);
	cursor: pointer;
}
.artifact-action:hover { border-color: var(--fg-2); color: var(--fg); }
/* the COLOUR belongs to the artifact VERSION: a border tick, so a long strip
   still reads as one row of controls rather than a traffic light */
.artifact-action.art-match { border-color: var(--ok, #3fb950); color: var(--ok, #3fb950); }
.artifact-action.art-mismatch { border-color: var(--bad, #f85149); color: var(--bad, #f85149); }
.artifact-action.selected {
	background: var(--accent, #58a6ff);
	border-color: var(--accent, #58a6ff);
	color: var(--bg, #0d1117);
	font-weight: 600;
}
.artifact-action:disabled { opacity: .45; cursor: default; }
.artifact-viewer-slot:empty { display: none; }
.artifact-viewer { margin-top: 8px; border-top: 1px solid var(--line, #30363d); padding-top: 8px; }
.art-input-link { color: var(--accent, #58a6ff); text-decoration: none; }
.art-input-link:hover { text-decoration: underline; }
.art-content { margin-top: 6px; }
.art-tabs { display: flex; gap: 4px; margin-bottom: 6px; }

/* v0.81.14: a declared REGION under a section's own content — a generic table
   whose columns the payload names. Scrolls in its own box rather than widening
   the panel: nine columns of route evidence is a table, not a paragraph. */
.region-table { margin-top: 8px; overflow-x: auto; }
.rgn-grid { display: grid; gap: 0 14px; font-size: 12px; width: max-content; }
.rgn-cell { white-space: nowrap; color: var(--fg); padding: 1px 0; }
.rgn-cell.rgn-head {
	color: var(--fg-2);
	font-size: 10px;
	text-transform: uppercase;
	letter-spacing: .6px;
}

/* LOG — the shared renderer, two views. fix v0.86.10: the selector sits ON the
   section's title line, so there is no heading-above-summary-above-buttons
   stack and no count in a tab label. */
.log-views { display: flex; gap: 4px; }
/* fix v0.86.10: the switch sits at the END of the title line, not butted up
   against the word LOG — it is a control, and a control belongs at the edge the
   eye returns to, not in the middle of a heading. */
.io-block--tight h3 .log-views { margin: 0 0 0 auto; }
.art-tab-btn {
	font-size: 11px;
	padding: 2px 8px;
	border: 1px solid var(--line, #30363d);
	background: transparent;
	color: var(--fg-2);
	cursor: pointer;
	border-radius: 3px;
}
.art-tab-btn.active { color: var(--fg); border-color: var(--accent, #58a6ff); }

/* fix v0.86.10: ONE switch with two positions. Two separately-bordered buttons
   read as two independent actions; these are exclusive states of one control,
   so they share a frame and only the active segment is filled. */
.log-views {
	border: 1px solid var(--line, #30363d);
	border-radius: 3px;
	overflow: hidden;
}
.log-view-seg {
	font-size: 11px;
	padding: 1px 10px;
	border: 0;
	background: transparent;
	color: var(--fg-2);
	cursor: pointer;
}
.log-view-seg + .log-view-seg { border-left: 1px solid var(--line, #30363d); }
.log-view-seg.active {
	background: var(--accent, #58a6ff);
	color: var(--bg, #0d1117);
	font-weight: 600;
}
/* fix v0.86.10: no problem colouring on the switch. It flagged a view the
   operator had not asked about, in the one control whose job is to state which
   view is showing. The LINES still render warn/error coloured, in the log,
   where the problem actually is. */
.log-body { margin-top: 4px; }

/* fix v0.86.10: the run header is ONE line — run · date · time · lineage. The
   ancestry breadcrumb used to take a second row directly beneath, stating the
   same fact the header already states: which run this is. */
.run-header { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; }
.run-header .rh-bits { display: inline-flex; align-items: baseline; gap: 6px; }
.rh-lineage { display: inline-flex; align-items: baseline; gap: 6px; }
.rh-lineage:empty { display: none; }
.rh-lineage .lineage { display: inline; }

/* fix v0.86.10: a numeric control's MEASURE, beside the field — not spelled
   into the label, where the operator had to parse a sentence to read a number.
   While the value is zero or empty the declared zero-state replaces it, so the
   control states what it currently IS ("No limit") rather than a rule. */
.ctl-measured { display: inline-flex; align-items: baseline; gap: 6px; }
/* only the ZERO-STATE follows the field now; the unit moved into the caption */
.ctl-unit { color: var(--fg-2); font-size: 11px; }
.ctl-unit-state { color: var(--fg); font-style: italic; }
.ctl-unit:empty { display: none; }

/* fix v0.86.10: a DECLARED destructive value, while it is the selection.
   The mark is the FRAME and a faint wash, never the text colour: red type on
   this navy field fails contrast badly enough to be unreadable, and the one
   thing a destructive control must stay is legible. So the value keeps the
   normal foreground and the control around it is what turns red. */
.ctl-danger {
	color: var(--fg);
	border: 1px solid var(--bad, #f85149);
	background: rgba(248, 81, 73, .14);
	box-shadow: inset 0 0 0 1px rgba(248, 81, 73, .35);
}
.ctl-danger:focus { outline-color: var(--bad, #f85149); }

/* a copyable identifier (double-click) */
.copyable { border-bottom: 1px dotted var(--fg-2); cursor: copy; }

/* v0.81.16 — the LLM ASSIGNMENT surface.
   Two shapes from one panel: an editable row per task on the step that owns it,
   and one compact read-only overview on `topic_select`. Nothing here is a
   launch control, so no state is "override"-coloured; the one state that IS
   coloured is `unassigned`, because a run is refused for it. */
.asg { margin-top: 8px; padding: 6px 8px;
  border: 1px solid var(--border, #333); border-radius: 4px; }
.asg-body { display: flex; flex-direction: column; gap: 4px; }
.asg-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 12px; }
.asg-task { min-width: 10em; font-weight: 600; }
.asg-pair { display: inline-flex; gap: 3px; }
.asg-save { font-size: 12px; }
.asg-resolved { font-size: 11px; }
.asg-state { font-size: 11px; }
.asg-unassigned, .asg-state.asg-unassigned { color: #e5534b; }
.asg-rev { font-size: 11px; margin-top: 3px; }
.asg-ov-h, .asg-ov-r { display: grid;
  grid-template-columns: 1.4fr 1.6fr 1.6fr 1.6fr; gap: 6px; font-size: 12px; }
.asg-ov-h { color: var(--muted, #888); }
