/* =============================================================================
   AI Runtime operations console.

   Direction: dense instrumentation — Grafana / Datadog / Honeycomb. Data is the
   only thing on the page with contrast. Chrome recedes. Nothing is decorative:
   if a rule does not encode information it is not here.

   Everything below the token block is expressed in tokens. No component
   hardcodes a colour and no token is named for its appearance, so a dark theme
   is a value swap inside one :root block, not a rewrite.

   Pico.css (vendored and embedded, see static/vendor/README.md) is the
   document layer underneath. It is imported into a cascade layer, and a
   layered rule loses to every unlayered rule in this file whatever the
   specificity — even to a :where() at zero — so nothing here needs
   !important to win. Pico draws the elements; this file owns the tokens
   (Pico's variables are derived from them at the end of :root) and every
   component. Where Pico's element default and a component disagree, the
   component is right.
   ========================================================================== */

@import url("../vendor/pico-2.1.1/pico.classless.min.css") layer(pico);

:root {
  /* ---- Spacing -------------------------------------------------------------
     Base unit 4px. Every padding, margin and gap in this file is one of these
     seven steps. A value off the scale carries a comment saying why. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-8: 48px;

  /* ---- Density -------------------------------------------------------------
     The console tightens or loosens from these. Row height and cell padding
     set the table rhythm; control height keeps form controls on the same
     baseline grid as the rows they filter. */
  --row-h: 26px;
  --cell-pad-y: 4px;
  --cell-pad-x: var(--sp-2);
  --rule: 1px;
  --control-h: 26px;
  --topbar-h: 38px;
  /* Instrument corners. A panel is a bounded region, not a card. */
  --radius: 2px;
  --radius-control: 3px;
  --sidebar-w: 200px;
  /* Height the sticky chrome takes above a tall table: filter bar + meta row
     + pagination + the content padding above and below. Derived by eye once,
     kept here so .table-tall does not carry a naked number. */
  --table-reserve: 190px;
  /* The active-filter row is conditional, so it cannot live in the constant
     above; a tall table under one subtracts it as well (see .chips + .panel). */
  --chips-h: 30px;

  /* ---- Type ----------------------------------------------------------------
     Two families. Monospace is not decoration here: identifiers, model names
     and every figure are read by comparing them against each other down a
     column, and that only works when the glyphs are the same width. */
  --font-ui: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-data: ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", Menlo, Consolas, monospace;

  --fs-display: 22px;   /* the one figure a stat card exists to show */
  --fs-title: 18px;     /* page title on a detail page */
  --fs-page: 15px;      /* page title in the top bar */
  --fs-section: 13px;   /* section head — a panel's own <h2> */
  --fs-body: 13px;      /* prose, cell text */
  --fs-data: 12.5px;    /* figures and identifiers */
  --fs-sub: 12px;       /* panel head — a heading INSIDE a section */
  --fs-head: 11.5px;    /* table header, control label */
  --fs-caption: 11.5px; /* footnotes, guidance, supporting figures */

  --fw-normal: 400;
  --fw-medium: 500;
  --fw-bold: 600;

  --lh-body: 1.5;

  /* ---- Measure --------------------------------------------------------------
     Long lines are unreadable even in an ops tool. Three caps, by role, so a
     max line length is a decision made once rather than per block. */
  --measure-prose: 84ch;   /* explanatory paragraphs */
  --measure-note: 96ch;    /* footnotes and honesty banners */
  --measure-narrow: 62ch;  /* centred text — an empty state */

  /* ---- Colour --------------------------------------------------------------
     Nine structural values. Borders are the quietest thing on the page that is
     still visible. Semantic colour below is reserved for state; nothing else
     on the page gets colour. */
  --surface-page: #f4f6f8;
  --surface-raised: #ffffff;
  --surface-sunken: #eef1f4;
  --surface-inverse: #0d1117;
  --border-strong: #d2d8de;
  --border-faint: #e7ebef;
  --text-primary: #1f2328;
  --text-secondary: #59636e;
  --text-muted: #8b949e;
  --text-inverse: #e6edf3;
  --text-inverse-dim: #8b949e;
  --text-link: #0a5bd3;
  --focus-ring: #0a5bd3;

  /* Semantic state. Each is ink / fill / edge so a badge, a flash and a value
     read as the same state without three separate palettes. */
  --state-ok-ink: #116329;      --state-ok-fill: #dafbe1;      --state-ok-edge: #a8dfb6;
  --state-error-ink: #b52a37;   --state-error-fill: #ffebe9;   --state-error-edge: #f5b7b6;
  --state-warn-ink: #9a5300;    --state-warn-fill: #fff4e6;    --state-warn-edge: #f0cfa6;
  --state-info-ink: #0a5bd3;    --state-info-fill: #ddf4ff;    --state-info-edge: #a9d5f5;
  --state-idle-ink: #59636e;    --state-idle-fill: #eef1f4;    --state-idle-edge: #d2d8de;

  /* Code surface — the one inverse region inside the content area. */
  --code-key: #79c0ff;
  --code-str: #a5d6ff;
  --code-num: #ffa657;
  --code-bool: #ff7b72;
  --code-var: #d2a8ff;
  --code-var-on-light: #8250df;

  /* Pipeline stages. Ordinal, not semantic: these encode position in the
     request lifecycle, which is information. */
  --stage-queued: #8b949e;
  --stage-select: #6e7781;
  --stage-build: #8250df;
  --stage-call: #0a5bd3;
  --stage-receive: #218bff;
  --stage-parse: #bc4c00;
  --stage-validate: #9a6700;
  --stage-done: #1a7f37;

  /* ---- Chart series ----------------------------------------------------------
     Three palettes, named for what a series IS, never mixed on one chart:

     Semantic — a status. Aliases of the state inks so a bar and a badge agree.
     Measure  — a quantity that is neither a status nor a category: p50 next to
                p95, this window next to the previous one.
     Categorical — a provider, and its models. The hue identifies the provider
                (a bare hue, in degrees); the lightness ladder tells one
                provider's models apart, so it only has to separate the two
                or three that share a chart, never all of them at once.
     Ordinal  — prompt versions in order of first appearance: one hue, light to
                dark, because versions have an order and providers do not.
     Colour is never the only carrier: bars are labelled, legends are named. */
  --series-ok: var(--state-ok-ink);
  --series-error: var(--state-error-ink);
  /* The third outcome: the call SUCCEEDED and the answer was the wrong
     shape. It needs its own colour wherever a chart shows both, or the
     schema axis reads as a kind of failure — which is the one thing
     output_valid exists not to be. */
  --series-warn: var(--state-warn-ink);
  --series-idle: var(--text-muted);

  --measure-primary: #0a5bd3;
  --measure-accent: #bc4c00;
  --measure-ghost: #8b949e;
  /* A bar whose colour encodes nothing (cost by prompt, p50 by model), and
     the whisker that carries p95 over it: one shape, ink not hue. */
  --measure-neutral: #9aa5b1;
  --measure-whisker: #3d444d;

  --cat-openai: 170;
  --cat-anthropic: 25;
  --cat-gemini: 225;
  --cat-xai: 300;
  --cat-moonshot: 265;
  --cat-deepseek: 200;
  --cat-mistral: 45;
  --cat-groq: 345;
  --cat-openrouter: 90;
  --cat-other: 210;            /* a provider the stylesheet does not know */
  --cat-sat: 55%;
  --cat-sat-other: 8%;
  --cat-l-0: 28%; --cat-l-1: 36%; --cat-l-2: 44%; --cat-l-3: 52%; --cat-l-4: 60%; --cat-l-5: 68%;

  /* Failure classes — semantic like status, but a closed set of seven, so
     each gets a named role. Retryable ones warm, permanent ones cool,
     cancelled and unclassified grey: the chart stacks them in that order. */
  --class-timeout: #9a5300;
  --class-rate-limited: #d4a72c;
  --class-provider: #b52a37;
  --class-bad-response: #e07b53;
  --class-rejected: #0a5bd3;
  --class-auth: #0f7b83;
  --class-config: #6e40c9;
  --class-cancelled: #8b949e;
  --class-unknown: #c9d1d9;

  --ordinal-1: #c7d9f2; --ordinal-2: #9dbde8; --ordinal-3: #6e9ddb;
  --ordinal-4: #3f7bcb; --ordinal-5: #1f5aa8; --ordinal-6: #0f3a75;

  /* ---- Pico ------------------------------------------------------------------
     Pico's own variables, each DERIVED from a token above and never given a
     literal of its own: one scale and one palette, with Pico reading it
     (pico_test.go fails on a literal, or on a name Pico does not define).

     Light only, pinned by data-theme="light" on <html>. The tokens have no
     dark values; with the attribute absent, a machine set to dark would get
     Pico's dark surfaces under this file's light components — worse than a
     console that is simply light. */
  color-scheme: light;
  --pico-font-family: var(--font-ui);
  --pico-font-family-sans-serif: var(--font-ui);
  --pico-font-family-monospace: var(--font-data);
  /* The root size IS the body size, so Pico's 1rem controls are body type. */
  --pico-font-size: var(--fs-body);
  --pico-line-height: var(--lh-body);
  --pico-font-weight: var(--fw-normal);
  --pico-border-radius: var(--radius-control);
  --pico-border-width: var(--rule);
  --pico-outline-width: var(--rule);
  --pico-transition: 0s;
  --pico-spacing: var(--sp-3);
  --pico-typography-spacing-vertical: var(--sp-2);
  --pico-block-spacing-vertical: var(--sp-3);
  --pico-block-spacing-horizontal: var(--sp-3);
  /* Density. Pico sizes a one-line control as one line of body type plus this
     padding twice plus the border twice; solved for --control-h, every input
     and select lands on the same 26 px grid as the table rows it filters. */
  --pico-form-element-spacing-vertical: calc((var(--control-h) - var(--fs-body) * var(--lh-body) - 2 * var(--rule)) / 2);
  --pico-form-element-spacing-horizontal: var(--sp-2);
  --pico-nav-element-spacing-vertical: 0;
  --pico-nav-element-spacing-horizontal: 0;
  --pico-nav-link-spacing-vertical: 0;
  --pico-nav-link-spacing-horizontal: 0;

  --pico-background-color: var(--surface-raised);
  --pico-color: var(--text-primary);
  --pico-text-selection-color: var(--state-info-fill);
  --pico-muted-color: var(--text-secondary);
  --pico-muted-border-color: var(--border-faint);
  --pico-h1-color: var(--text-primary);
  --pico-h2-color: var(--text-primary);
  --pico-h3-color: var(--text-primary);
  --pico-h4-color: var(--text-primary);
  --pico-h5-color: var(--text-primary);
  --pico-h6-color: var(--text-primary);
  --pico-primary: var(--text-link);
  --pico-primary-background: var(--text-link);
  --pico-primary-border: var(--text-link);
  --pico-primary-underline: var(--text-link);
  --pico-primary-hover: var(--text-link);
  --pico-primary-hover-background: var(--text-link);
  --pico-primary-hover-border: var(--text-link);
  --pico-primary-hover-underline: var(--text-link);
  --pico-primary-inverse: var(--surface-raised);
  /* Focus is drawn once, by the outline rule under Base. Pico's box-shadow
     ring on links, buttons and checkboxes would be a second ring outside it;
     text controls keep Pico's own (border + --pico-form-element-focus-color). */
  --pico-primary-focus: transparent;
  /* A focused input inside a role="search" form would also ring the form. */
  --pico-group-box-shadow-focus-with-input: none;
  --pico-table-border-color: var(--border-faint);
  --pico-code-background-color: var(--surface-sunken);
  --pico-code-color: var(--text-primary);
  --pico-form-element-background-color: var(--surface-raised);
  --pico-form-element-active-background-color: var(--surface-raised);
  --pico-form-element-selected-background-color: var(--surface-sunken);
  --pico-form-element-border-color: var(--border-strong);
  --pico-form-element-active-border-color: var(--focus-ring);
  --pico-form-element-focus-color: var(--focus-ring);
  --pico-form-element-color: var(--text-primary);
  --pico-form-element-placeholder-color: var(--text-muted);
  --pico-form-element-disabled-opacity: 0.55;
  --pico-form-element-invalid-border-color: var(--state-error-edge);
  --pico-form-element-invalid-active-border-color: var(--state-error-ink);
  --pico-form-element-invalid-focus-color: var(--state-error-ink);
  --pico-form-element-valid-border-color: var(--state-ok-edge);
  --pico-form-element-valid-active-border-color: var(--state-ok-ink);
  --pico-form-element-valid-focus-color: var(--state-ok-ink);
  --pico-accordion-close-summary-color: var(--text-primary);
  --pico-accordion-open-summary-color: var(--text-primary);
  --pico-accordion-active-summary-color: var(--text-primary);
}

/* =============================================================================
   Base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }
/* Pico paints the root; the page ground is ours, including under overscroll. */
html { -webkit-text-size-adjust: 100%; background: var(--surface-page); }

body {
  margin: 0;
  font: var(--fw-normal) var(--fs-body)/var(--lh-body) var(--font-ui);
  color: var(--text-primary);
  background: var(--surface-page);
}

a { color: var(--text-link); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Text controls are not listed: Pico rings them itself, on the border, in the
   same --focus-ring colour (see the Pico block in :root). */
:where(a, button, summary, [tabindex], input[type="checkbox"], input[type="radio"]):focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
  border-radius: var(--radius-control);
}
/* On the inverse sidebar the link-blue ring disappears; the ring there is the
   one place a colour is chosen for contrast rather than for meaning. */
.sidebar :where(a, button):focus-visible { outline-color: var(--text-inverse); }

/* ---- Pico's document defaults, handed back to the component ---------------
   Pico is written for a document: every control full width with a gap under
   it, every <nav> spread edge to edge as a row, square bullets on every list,
   a chevron at the far margin of every <details>. In this console a control's
   size and place belong to the component it sits in — a filter bar, a key
   row, a table cell — so these zero-specificity defaults return placement to
   it. Appearance (border, fill, caret, checkbox, focus) stays Pico's. None of
   this is density: density is the variables. */
:where(input, select, textarea, button, fieldset, details, pre, table) { margin: 0; }
:where(input:not([type="checkbox"], [type="radio"]), select, textarea, button) { width: auto; }
:where(label, legend) { margin-bottom: 0; }
/* role="group" (the .seg window switches) and role="search" (the top bar)
   are Pico's input groups: full width with a gap under them. */
:where([role="group"], [role="search"]) { width: auto; margin-bottom: 0; }
:where(nav) { justify-content: normal; }
:where(nav ul, nav ol, nav li) { display: block; }
/* A list with a class is a component (nav, chips, ranks); prose lists keep
   their bullets. */
:where(ul[class], ol[class]) > li { list-style: none; margin-bottom: 0; }
/* Pico sets pointer-events: none on a disabled button, which also kills its
   title — and here a disabled button's title is how it says why. */
:where(button)[disabled] { pointer-events: auto; }
/* Pico pads <code> as a block (0.375rem all round); in running text that
   pushes lines apart. */
:where(code) { display: inline; padding: 0 2px; }
/* Steppers are browser chrome the arrow keys already provide, and at control
   height they are unreadable. */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

.mono { font-family: var(--font-data); font-size: var(--fs-data); }
.muted { color: var(--text-secondary); }
.small { font-size: var(--fs-caption); }
.ok { color: var(--state-ok-ink); }
.bad { color: var(--state-error-ink); }
.mt { margin-top: var(--sp-5); }

/* ---- Values that are not values -------------------------------------------
   Three different absences, three different shapes. A bare em dash says
   nothing, so nothing in this console prints one without saying which it is. */
.v-zero    { color: var(--text-muted); }                    /* genuinely zero */
.v-unknown { color: var(--text-muted); cursor: help; }      /* not recorded */
.v-na {                                                     /* cannot be computed */
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-family: var(--font-ui);
  cursor: help;
}
.v-lt { color: var(--text-secondary); }  /* below display resolution, not zero */
/* The unit rides along with the figure but never competes with it. */
.unit { color: var(--text-muted); font-weight: var(--fw-normal); }

/* =============================================================================
   Shell
   ========================================================================== */

.sidebar {
  position: fixed; inset: 0 auto 0 0; width: var(--sidebar-w);
  background: var(--surface-inverse); color: var(--text-inverse);
  display: flex; flex-direction: column; z-index: 30;
}
.brand {
  display: grid; grid-template-columns: auto 1fr; column-gap: var(--sp-2);
  align-items: center; padding: var(--sp-3) var(--sp-4);
  color: var(--text-inverse); border-bottom: var(--rule) solid #21262d;
}
.brand:hover { text-decoration: none; }
.brand-mark { font-size: 16px; color: var(--state-ok-ink); filter: brightness(1.8); grid-row: 1 / 3; }
.brand-name { font-weight: var(--fw-bold); font-size: var(--fs-body); }
.brand-sub { grid-column: 2; font-size: var(--fs-caption); color: var(--text-inverse-dim); }
.nav { list-style: none; margin: var(--sp-2) 0; padding: 0; }
.nav a {
  display: block; padding: var(--sp-1) var(--sp-4);
  color: #c9d1d9; font-size: var(--fs-body);
  border-left: 2px solid transparent;
}
.nav a:hover { background: #161b22; text-decoration: none; }
.nav a.active { color: #fff; background: #161b22; border-left-color: #3fb950; font-weight: var(--fw-bold); }

.main { margin-left: var(--sidebar-w); min-height: 100vh; display: flex; flex-direction: column; }

.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: var(--sp-4);
  height: var(--topbar-h);
  background: var(--surface-raised);
  border-bottom: var(--rule) solid var(--border-strong);
  padding: 0 var(--sp-4);
}
.topbar-back {
  display: inline-grid; place-items: center; flex: 0 0 auto;
  width: 26px; height: 26px;
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface-sunken); color: var(--text-primary);
  font-size: var(--fs-page); line-height: 1;
}
.topbar-back:hover { background: var(--border-faint); text-decoration: none; }
.topbar-title { font-weight: var(--fw-bold); font-size: var(--fs-page); white-space: nowrap; }
.topbar-search { flex: 1; max-width: 480px; display: flex; }
.topbar-search input {
  width: 100%; height: var(--control-h);
  padding: 0 var(--sp-2);
  font: var(--fs-body)/1 var(--font-ui);
  color: var(--text-primary);
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface-page);
}
.topbar-env {
  margin-left: auto; font-size: var(--fs-caption); color: var(--text-secondary);
  white-space: nowrap; display: flex; align-items: center; gap: var(--sp-1);
}
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }

/* ---- In-page swaps (htmx) --------------------------------------------------
   A swap with no feedback reads as a broken click. While a view loads, the
   top bar says so (Pico's aria-busy spinner) and the region being replaced
   dims — after a short delay, so a fast swap does not flicker. If it fails,
   the page keeps what it had and the top bar says why. */
.topbar-status { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-caption); color: var(--text-secondary); min-width: 0; }
.busy { visibility: hidden; display: inline-flex; align-items: center; gap: var(--sp-1); white-space: nowrap; }
.busy.htmx-request { visibility: visible; }
.swap-error { color: var(--state-error-ink); }
.view.htmx-request { opacity: .55; transition: opacity .1s linear .15s; }
/* A region takes focus only when the control that had it no longer exists;
   it is a landing point, not a control, so it draws no ring. */
.view:focus { outline: none; }
.btn.htmx-request { opacity: .55; cursor: progress; }
.dot-ok { background: var(--state-ok-ink); }

/* `min-width: 0` because a flex/grid item defaults to `min-width: auto`,
   which is "as wide as my content" — that default is how one over-wide
   descendant silently becomes a horizontally scrolling page. Wide content
   that genuinely needs room scrolls inside `.table-wrap` or `.code`. */
.content { padding: var(--sp-3) var(--sp-4); flex: 1; min-width: 0; }
.main { min-width: 0; }
.footer {
  padding: var(--sp-2) var(--sp-4);
  border-top: var(--rule) solid var(--border-faint);
  color: var(--text-muted); font-size: var(--fs-caption);
}

/* =============================================================================
   Panels and cards
   ========================================================================== */

.cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: var(--sp-2); margin-bottom: var(--sp-3);
}
.card {
  background: var(--surface-raised);
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3);
}
/* Size and weight already separate a label from its figure; small caps added
   letterspacing and a second reading pass for nothing. */
.card-label { font-size: var(--fs-head); color: var(--text-secondary); }
.card-value {
  font-size: var(--fs-display); font-weight: var(--fw-bold); font-family: var(--font-data);
  font-variant-numeric: tabular-nums; margin-top: var(--sp-1);
}
.card-value.ok { color: var(--state-ok-ink); }
.card-value.bad { color: var(--state-error-ink); }
.card-of { font-size: var(--fs-caption); color: var(--text-muted); margin-left: var(--sp-1); font-weight: var(--fw-normal); }

.panel {
  background: var(--surface-raised);
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius);
  padding: var(--sp-3);
  margin-bottom: var(--sp-3);
}
/* Three heading levels, visibly three things. Section head: full size, full
   weight, primary ink. Panel head (a heading INSIDE a section): a step down,
   secondary ink, and a hairline above it so the break is structural rather
   than typographic. Control label (.filter label) is smaller again, medium
   weight, and never bold. Nothing here relies on size alone. */
.panel-title { font-size: var(--fs-section); font-weight: var(--fw-bold); color: var(--text-primary); margin: 0 0 var(--sp-2); }
.panel-subtitle {
  font-size: var(--fs-sub); font-weight: var(--fw-bold); color: var(--text-secondary);
  border-top: var(--rule) solid var(--border-faint);
  margin: var(--sp-3) 0 var(--sp-1); padding-top: var(--sp-2);
}
.panel-sub { font-weight: var(--fw-normal); color: var(--text-muted); font-size: var(--fs-caption); }
/* `.mt` is declared above .panel-title, so on a `panel-title mt` heading the
   title's `margin: 0 …` won and the heading sat flush against the control
   before it (Playground's "Save as a case" under the Run button). */
.panel-title.mt { margin-top: var(--sp-5); }
.panel-error { border-color: var(--state-error-edge); }
.panel-center { text-align: center; padding: var(--sp-8) var(--sp-4); }
.lead { color: var(--text-secondary); margin: 0 0 var(--sp-2); max-width: var(--measure-prose); }
/* minmax(0, 1fr), not 1fr: a bare 1fr is minmax(auto, 1fr), and Safari
   counts an SVG's viewBox width (1280) as its min-content, so a track with
   a chart in it grew past half the page and the whole grid ran off the
   right edge — the chart looked "clipped" when it was the panel that had
   overflowed. Chrome resolves the percentage width first and never showed
   it. The children get min-width: 0 for the same reason. */
.grid-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-3); }
.grid-2 > * { min-width: 0; }
.span-2 { grid-column: 1 / -1; }
/* Off scale on purpose: a 404 glyph is an illustration, not type. */
.error-code { font-size: 40px; margin: 0 0 var(--sp-2); color: var(--text-secondary); }

/* =============================================================================
   Tables

   The base layer sits inside :where() so it has zero specificity: a per-column
   class wins on its own weight, with no !important arms race. (The old
   .empty { padding: … !important } existed only because tbody td out-specified
   it.)
   ========================================================================== */

.table-wrap {
  overflow: auto;
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-raised);
}
.table-tall { max-height: calc(100vh - var(--topbar-h) - var(--table-reserve)); }
.chips + .panel .table-tall { max-height: calc(100vh - var(--topbar-h) - var(--table-reserve) - var(--chips-h)); }
/* Inside a panel the panel edge is already the frame, so the table drops its
   own side borders and bleeds to it: one line instead of two, and the width of
   the panel padding back for data. */
.panel > .table-wrap {
  border-left: 0; border-right: 0; border-radius: 0;
  margin-left: calc(var(--sp-3) * -1); margin-right: calc(var(--sp-3) * -1);
}
.panel > .table-wrap:last-child { margin-bottom: calc(var(--sp-3) * -1); border-bottom: 0; }

:where(table) {
  width: 100%; border-collapse: separate; border-spacing: 0;
  font-size: var(--fs-body);
  background: var(--surface-raised);
}
:where(thead th) {
  position: sticky; top: 0; z-index: 5;
  background: var(--surface-sunken);
  text-align: left; font-weight: var(--fw-bold);
  padding: var(--cell-pad-y) var(--cell-pad-x);
  border-bottom: var(--rule) solid var(--border-strong);
  white-space: nowrap;
  font-size: var(--fs-head); color: var(--text-secondary);
}
:where(tbody td) {
  height: var(--row-h);
  padding: var(--cell-pad-y) var(--cell-pad-x);
  border-bottom: var(--rule) solid var(--border-faint);
  vertical-align: middle;
  white-space: nowrap;
}
:where(tbody tr:last-child td) { border-bottom: 0; }
tbody tr:hover td { background: var(--surface-page); }

/* Every figure is read down its column, so every figure is right-aligned and
   tabular. This one pair of properties is most of the difference between a
   table of numbers and a column of numbers. */
:where(th.num, td.num) { text-align: right; }
:where(td.num), :where(td .num) { font-variant-numeric: tabular-nums; }
:where(.mono, code, pre, .id) { font-variant-numeric: tabular-nums; }

.row-dim td { color: var(--text-muted); }
.row-current td { background: var(--surface-sunken); }

.tick { color: var(--state-ok-ink); font-weight: var(--fw-bold); }
.cross { color: var(--text-muted); }

.table-meta {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-caption); color: var(--text-secondary);
  margin-bottom: var(--sp-2);
}
.page-sizes a {
  margin-left: var(--sp-1); padding: 1px var(--sp-2);
  border: var(--rule) solid var(--border-strong); border-radius: var(--radius);
  color: var(--text-secondary);
}
.page-sizes a.active { background: var(--text-primary); color: var(--surface-raised); border-color: var(--text-primary); }

/* ---- Row selection and bulk export (Requests) -----------------------------
   The controls ride in the meta row, so they are sized to it: a full-height
   .btn would make the row taller than the --table-reserve it was measured at. */
.table-meta-lead { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); }
.bulk { display: inline-flex; align-items: center; gap: var(--sp-2); }
.bulk .btn { height: 20px; padding: 0 var(--sp-2); font-size: var(--fs-caption); }
.bulk-count:empty { display: none; }
.bulk-count[data-some] { color: var(--text-primary); font-weight: var(--fw-bold); }
:where(th.sel, td.sel) { width: 1%; padding-right: 0; }
th.sel input, td.sel input { margin: 0; vertical-align: middle; cursor: pointer; }
/* A ticked row is marked across the whole row, and out-ranks the hover tint so
   a selection does not vanish under the pointer. */
tbody tr:has(> td.sel input:checked) td { background: var(--state-info-fill); }

/* Caps a JSON schema preview at about a line; the full value is on the page
   it links to. Off scale — it is a legibility limit, not spacing. */
.schema-chip {
  font-family: var(--font-data); font-size: var(--fs-caption);
  background: var(--surface-sunken);
  border: var(--rule) solid var(--border-faint); border-radius: var(--radius);
  padding: 0 var(--sp-1);
  display: inline-block; max-width: 340px;
  overflow: hidden; text-overflow: ellipsis; vertical-align: middle;
}

/* ---- Text that does not fit ----------------------------------------------
   Identifiers truncate in the MIDDLE (server-side, see mid() in handlers.go):
   the tail is what distinguishes req_…33a0 from req_…33b1, so a trailing
   ellipsis throws the discriminating half away. The full value is always in
   the title and on the copy button — truncation is never the only access. */
.id { font-family: var(--font-data); font-size: var(--fs-data); white-space: nowrap; }
.copy {
  border: var(--rule) solid transparent; background: none;
  color: var(--text-muted); cursor: pointer;
  font: var(--fs-caption)/1 var(--font-ui);
  padding: 1px var(--sp-1); border-radius: var(--radius);
  vertical-align: baseline;
}
.copy:hover { color: var(--text-primary); border-color: var(--border-strong); background: var(--surface-sunken); }
.copy[data-copied] { color: var(--state-ok-ink); border-color: var(--state-ok-edge); background: var(--state-ok-fill); }

/* Anything a user or a provider supplied: wrap it anywhere rather than let a
   pasted 400-character token push the layout sideways. */
.wrap-any { white-space: normal; overflow-wrap: anywhere; }
/* Prose has a measure even in an ops tool; long lines are unreadable. */
.measure { max-width: var(--measure-prose); }
.clamp-2 {
  white-space: normal; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}

/* =============================================================================
   Empty states

   Three states, not one: nothing has ever happened / nothing matches the
   filters / too thin to mean anything. Each says what would change it.
   ========================================================================== */

.empty { color: var(--text-secondary); white-space: normal; }
td.empty { text-align: center; padding: var(--sp-5) var(--sp-4); }
.empty-title { display: block; font-weight: var(--fw-bold); color: var(--text-primary); margin-bottom: var(--sp-1); }
.empty-body { display: block; max-width: var(--measure-narrow); margin: 0 auto; font-size: var(--fs-caption); line-height: var(--lh-body); }

/* =============================================================================
   Status

   Read under stress, so status never rests on hue alone: a leading glyph
   carries the state, weight carries its severity, and the states that mean
   "look here" get a solid leading bar as well.
   ========================================================================== */

.badge {
  display: inline-block; padding: 0 var(--sp-1);
  border-radius: var(--radius);
  font-size: var(--fs-head); font-weight: var(--fw-bold);
  font-family: var(--font-data);
  border: var(--rule) solid transparent;
  white-space: nowrap;
}
.badge-lg { font-size: var(--fs-body); padding: 1px var(--sp-2); }
.badge::before { font-weight: var(--fw-bold); }

.badge-completed, .badge-valid, .badge-prov-healthy {
  color: var(--state-ok-ink); background: var(--state-ok-fill); border-color: var(--state-ok-edge);
}
.badge-completed::before, .badge-valid::before, .badge-prov-healthy::before { content: "\2713\00a0"; }

.badge-failed, .badge-invalid {
  color: var(--state-error-ink); background: var(--state-error-fill); border-color: var(--state-error-edge);
  box-shadow: inset 2px 0 0 currentColor;
  padding-left: var(--sp-2);
}
.badge-failed::before, .badge-invalid::before { content: "\2715\00a0"; }

.badge-retrying, .badge-prov-rate_limited {
  color: var(--state-warn-ink); background: var(--state-warn-fill); border-color: var(--state-warn-edge);
  box-shadow: inset 2px 0 0 currentColor;
  padding-left: var(--sp-2);
}
.badge-retrying::before, .badge-prov-rate_limited::before { content: "\21BB\00a0"; }

.badge-running { color: var(--state-info-ink); background: var(--state-info-fill); border-color: var(--state-info-edge); }
.badge-running::before { content: "\25B8\00a0"; }

.badge-cancelled, .badge-prov-offline {
  color: var(--state-idle-ink); background: var(--state-idle-fill); border-color: var(--state-idle-edge);
}
.badge-cancelled::before, .badge-prov-offline::before { content: "\2298\00a0"; }

/* Not a state — a label (the default model, a prompt version hash). No glyph. */
.badge-default { color: var(--state-info-ink); background: var(--state-info-fill); border-color: var(--state-info-edge); }
/* Error classes: the class token as ink on the idle fill, so the badge and
   the failure chart agree on which class is which. */
.badge-class { background: var(--surface-sunken); border-color: var(--border-strong); }
.badge-class-timeout { color: var(--class-timeout); }
.badge-class-rate_limited { color: var(--class-timeout); }
.badge-class-provider_error { color: var(--class-provider); }
.badge-class-bad_response { color: var(--class-bad-response); }
.badge-class-rejected { color: var(--class-rejected); }
.badge-class-auth { color: var(--class-auth); }
.badge-class-config { color: var(--class-config); }
.badge-class-cancelled, .badge-class-escalated, .badge-class-unknown { color: var(--text-secondary); }
.alerts-form .kv-inline input:not([type="checkbox"]) { width: 100%; }
.alerts-form dt label { cursor: pointer; }

/* =============================================================================
   Filters

   One row of inline label+control pairs instead of two rows of stacked ones.
   Active filters are restated underneath as removable chips, so the current
   query is legible without reading eight controls to find the two that are set.
   ========================================================================== */

.filters {
  position: sticky; top: var(--topbar-h); z-index: 15;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2) var(--sp-3);
  background: var(--surface-raised);
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-2);
}
.filter { display: flex; align-items: center; gap: var(--sp-1); min-width: 0; }
.filter label {
  flex: none;
  font-size: var(--fs-head); font-weight: var(--fw-medium);
  color: var(--text-secondary);
}
/* A textarea is not a one-line control: those stack. */
.filter:has(textarea) { flex-direction: column; align-items: stretch; gap: var(--sp-1); }
.filter:has(textarea) label { align-self: flex-start; }
.filter-actions { gap: var(--sp-2); margin-left: auto; }

/* ---- Controls -------------------------------------------------------------
   Pico draws every control — border, fill, select caret, checkbox, date icon,
   placeholder, disabled and focus states — from the variables at the end of
   :root, at --control-h. What is left here is placement, and the one thing a
   variable cannot reach. */
.filter input, .filter select { min-width: 0; }
/* A textarea is several lines, but Pico pads it with the one-line control's
   vertical padding (2.25 px at this density), which leaves the text touching
   the frame. The same variable sizes every input, so it cannot be raised for
   textareas alone: this is set here instead. */
textarea { padding-block: var(--sp-1); }
.filter textarea, .vars textarea { width: 100%; }
/* A datetime-local is the widest control in any filter bar — the date icon
   plus a full dd.mm.yyyy --:-- placeholder. At the data size it fits the row
   with the other seven controls instead of forcing a second one. The width
   is in em of that size, not a spacing step. */
.filter input[type="datetime-local"] {
  font-family: var(--font-data); font-size: var(--fs-caption);
  width: 15em;
}
/* Selects size to their longest option; a long model id must not take the row
   hostage. The full value stays selectable in the open list. A cap, not a
   spacing step. */
.filter select { max-width: 240px; text-overflow: ellipsis; }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: var(--control-h); padding: 0 var(--sp-3);
  font: var(--fw-bold) var(--fs-body)/1 var(--font-ui);
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius-control);
  background: var(--surface-sunken); color: var(--text-primary);
  cursor: pointer; white-space: nowrap;
}
.btn:hover { background: var(--border-faint); text-decoration: none; }
.btn-primary { background: var(--state-ok-ink); border-color: var(--state-ok-ink); color: #fff; }
.btn-primary:hover { background: var(--state-ok-ink); filter: brightness(1.15); }
.btn[disabled], .btn[aria-busy="true"] { opacity: .55; cursor: default; }
.btn[hidden] { display: none; }

/* ---- Active filter chips --------------------------------------------------
   The current query, restated as data. Each chip is a link that drops one
   term; page is dropped with it, because a narrower result set has no page 4. */
.chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-2);
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-caption);
}
.chips-label { color: var(--text-secondary); }
.chip-filter {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 1px var(--sp-1) 1px var(--sp-2);
  border: var(--rule) solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface-raised); color: var(--text-primary);
  /* Not a measure: a truncation cap, so one long value cannot own the row. */
  max-width: 34ch;
}
.chip-filter:hover { text-decoration: none; border-color: var(--state-error-edge); background: var(--state-error-fill); }
.chip-key { color: var(--text-secondary); }
.chip-val { font-family: var(--font-data); font-size: var(--fs-data); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip-x { color: var(--text-muted); font-weight: var(--fw-bold); }
.chip-filter:hover .chip-x { color: var(--state-error-ink); }
.chips-clear { color: var(--text-link); }

/* =============================================================================
   Pagination
   ========================================================================== */

.pagination { display: flex; gap: var(--sp-1); justify-content: center; margin-top: var(--sp-2); font-size: var(--fs-caption); }
.pagination a, .pagination span {
  padding: 1px var(--sp-2);
  border: var(--rule) solid var(--border-strong); border-radius: var(--radius);
  color: var(--text-link);
  font-variant-numeric: tabular-nums;
}
.pagination span.active { background: var(--text-primary); border-color: var(--text-primary); color: var(--surface-raised); }

/* =============================================================================
   Detail pages
   ========================================================================== */

.detail-head { display: flex; align-items: baseline; gap: var(--sp-3); margin-bottom: var(--sp-3); flex-wrap: wrap; }
/* Pushed to the far end so it never sits between the id and its status. */
.detail-export { margin-left: auto; }
.detail-id { font-size: var(--fs-title); margin: 0; }
.back { font-size: var(--fs-caption); color: var(--text-secondary); }
.kv { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: var(--sp-1) var(--sp-5); margin: 0; }
.kv > div { display: flex; flex-direction: column; padding: var(--sp-1) 0; border-bottom: var(--rule) dotted var(--border-faint); }
.kv dt { font-size: var(--fs-head); color: var(--text-secondary); }
.kv dd { margin: 0; }
.kv-inline { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
/* A table sizes itself to its widest cell, and `overflow: hidden` does not
   change that — it clips the paint, not the layout. One prompt variable
   holding a 2 kB capability descriptor on a single line therefore made
   this table wider than the viewport and pushed the whole PAGE sideways,
   sidebar and all. `table-layout: fixed` plus a width is what actually
   binds a cell to its container; the wrapping rules then let a long value
   grow downwards instead of outwards. */
.vars { border: var(--rule) solid var(--border-faint); border-radius: var(--radius); overflow: hidden; }
.vars { width: 100%; table-layout: fixed; }
.vars td, .vars th { overflow-wrap: anywhere; word-break: break-word; white-space: pre-wrap; vertical-align: top; }
.vars td:first-child, .vars th:first-child { width: 192px; }
.chip-list { list-style: none; display: flex; flex-wrap: wrap; gap: var(--sp-1); margin: 0; padding: 0; }
.chip {
  vertical-align: middle;
  background: var(--surface-sunken);
  border: var(--rule) solid var(--border-faint); border-radius: var(--radius);
  padding: 0 var(--sp-1); font-size: var(--fs-caption);
}

/* =============================================================================
   Code and JSON viewers
   ========================================================================== */

/* Kept over Pico's accordion: its chevron floats to the far margin, which on a
   full-width panel is a screen away from the heading it opens. */
details > summary { cursor: pointer; list-style: none; line-height: inherit; }
details > summary::after { display: none; }
details > summary::before { content: "\25B8\00a0"; color: var(--text-muted); }
details[open] > summary::before { content: "\25BE\00a0"; }
.code {
  font-family: var(--font-data); font-size: var(--fs-data); line-height: var(--lh-body);
  background: var(--surface-inverse); color: var(--text-inverse);
  border-radius: var(--radius); padding: var(--sp-2) var(--sp-3); margin: var(--sp-2) 0 0;
  overflow: auto; max-height: 320px;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.code-prompt { background: var(--surface-sunken); color: var(--text-primary); border: var(--rule) solid var(--border-faint); }
.json-viewer { min-height: 60px; }
.resize-handle { height: 8px; margin-top: 2px; cursor: ns-resize; border-radius: var(--radius); }
.resize-handle::after {
  content: ""; display: block; width: 42px; height: 3px; margin: 2px auto;
  background: var(--border-strong); border-radius: 2px;
}
.hl-key { color: var(--code-key); }
.hl-str { color: var(--code-str); }
.hl-num { color: var(--code-num); }
.hl-bool { color: var(--code-bool); }
.hl-var { color: var(--code-var); font-weight: var(--fw-bold); }
.code-prompt .hl-var { color: var(--code-var-on-light); }

/* =============================================================================
   Timeline
   ========================================================================== */

.timeline-bar {
  display: flex; height: 12px; border-radius: var(--radius); overflow: hidden;
  border: var(--rule) solid var(--border-faint); margin-bottom: var(--sp-2);
}
.tl-seg { min-width: 3px; }
.timeline { list-style: none; margin: 0; padding: 0; }
.tl-row {
  display: grid; grid-template-columns: 14px 1fr auto; gap: var(--sp-2); align-items: center;
  padding: var(--sp-1) 0; border-bottom: var(--rule) dotted var(--border-faint); position: relative;
}
.tl-row:last-child { border-bottom: 0; }
.tl-row::before {
  content: ""; position: absolute; left: 6px; top: -3px; bottom: -3px;
  width: 1px; background: var(--border-faint); z-index: 0;
}
.tl-dot { width: 9px; height: 9px; border-radius: 50%; justify-self: center; z-index: 1; border: 2px solid var(--surface-raised); }
.tl-dur { color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.tl-queued, .tl-dot.tl-queued { background: var(--stage-queued); }
.tl-selecting-provider { background: var(--stage-select); }
.tl-building-prompt { background: var(--stage-build); }
.tl-calling-provider, .tl-calling-openai { background: var(--stage-call); }
.tl-receiving-response { background: var(--stage-receive); }
.tl-parsing-json { background: var(--stage-parse); }
.tl-validation { background: var(--stage-validate); }
.tl-completed { background: var(--stage-done); }

/* Where the input went (request details): one bar of segments, the same
   three tones the charts use for the pieces of one input. */
.seg-bar {
  display: flex; height: 12px; border-radius: var(--radius); overflow: hidden;
  border: var(--rule) solid var(--border-faint); margin-bottom: var(--sp-2);
}
.seg { min-width: 2px; }
.seg-prompt { background: var(--ordinal-6); }
.seg-variable { background: var(--ordinal-4); }
.seg-input { background: var(--ordinal-2); }
.seg-table .chart-swatch { margin-right: var(--sp-2); }

/* The trace: one action's calls on a time axis. A gap is the absence of a
   measurement and is drawn as an outline, never a fill. */
.chart-trace { width: 100%; height: auto; display: block; }
.trace-bar { stroke: none; }
.trace-gap { fill: var(--surface-sunken); stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 3; }
.trace-gap-label { font-size: 10px; fill: var(--text-muted); font-style: italic; }
.trace-gap-swatch { background: var(--surface-sunken); border: 1px dashed var(--text-muted); box-sizing: border-box; }
.trace-scope { max-width: var(--measure-note); }

/* The flow graph: the same calls as the timeline, drawn as the shape the
   caller gave them. Colour carries the status, exactly as the timeline
   bars do, with one addition — an answer that failed its schema is its
   own colour, because the call succeeded and the answer did not. */
.chart-flow { width: 100%; height: auto; display: block; }
.flow-box { fill: var(--surface-raised); stroke: var(--border-strong); stroke-width: 1; }
.flow-node-ok .flow-box { stroke: var(--state-ok-edge); fill: var(--state-ok-fill); }
.flow-node-failed .flow-box { stroke: var(--state-error-edge); fill: var(--state-error-fill); }
.flow-node-invalid .flow-box { stroke: var(--state-warn-ink); fill: var(--state-warn-fill); }
.flow-node-repaired .flow-box { stroke: var(--state-info-ink); fill: var(--state-info-fill); stroke-dasharray: 6 2; }
/* A node that is not a call — a decision taken without a model, a parent
   outside this trace — is drawn as an outline, like the timeline's gaps:
   it is something this runtime did not measure. */
.flow-outside .flow-box,
.flow-cached .flow-box,
.flow-missing .flow-box { fill: var(--surface-sunken); stroke-dasharray: 4 3; stroke: var(--text-muted); }
.flow-title { font: var(--fw-medium) 13px var(--font-ui); fill: var(--text-primary); }
.flow-sub { font: 11px var(--font-mono); fill: var(--text-secondary); }
.flow-detail { font: 11px var(--font-ui); fill: var(--text-muted); }
.flow-edge { fill: none; stroke: var(--border-strong); stroke-width: 1.5; }
.flow-edge-retry { stroke-dasharray: 4 3; stroke: var(--state-warn-ink); }
.flow-edge-virtual { stroke-dasharray: 2 3; stroke: var(--text-muted); }
.flow-edge-label { font: 10px var(--font-ui); fill: var(--text-secondary); }
.flow-retry-swatch { background: transparent; border-top: 2px dashed var(--state-warn-ink); border-radius: 0; align-self: center; }
.flow-node a:focus-visible .flow-box { stroke-width: 2; }

/* "Path of this request": the ancestors, one per line, each with the
   label of the edge that led to the next. A list, not a graph: one
   request has one path, and a chain reads better than a picture of one. */
.flow-path { list-style: none; margin: 0; padding: 0; }
.flow-path li { display: flex; align-items: baseline; gap: var(--sp-2); padding: 2px 0; }
.flow-path .flow-path-edge { font-size: var(--fs-small); color: var(--text-muted); }
.flow-path .flow-path-here { font-weight: var(--fw-medium); }

/* Streaks: a list inside the notice, one run per line. */
.streak-list { margin: var(--sp-1) 0; padding-left: var(--sp-5); }
.streak-list li { padding: 2px 0; }

/* =============================================================================
   Dashboard widgets
   ========================================================================== */

.error-list, .rank-list { list-style: none; margin: 0; padding: 0; }
.error-list li { padding: var(--sp-1) 0; border-bottom: var(--rule) dotted var(--border-faint); }
.error-list li:last-child { border-bottom: 0; }
.error-list .muted { display: block; font-size: var(--fs-caption); }
.error-msg { font-size: var(--fs-caption); color: var(--state-error-ink); overflow-wrap: anywhere; }
.rank-list { counter-reset: rank; }
.rank-list li {
  counter-increment: rank; display: flex; justify-content: space-between; gap: var(--sp-2);
  padding: var(--sp-1) 0; border-bottom: var(--rule) dotted var(--border-faint);
}
.rank-list li::before { content: counter(rank) "."; color: var(--text-muted); min-width: 18px; }
.rank-list li:last-child { border-bottom: 0; }
.rank-list li > :first-child { flex: 1; }
.rank-count { font-family: var(--font-data); color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* =============================================================================
   Playground
   ========================================================================== */

.playground { display: grid; grid-template-columns: minmax(340px, 460px) 1fr; gap: var(--sp-3); align-items: start; }
.pg-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); margin-bottom: var(--sp-2); }
/* The Playground is authored with the label above its control (long labels,
   full-width textareas), so its filters stack rather than sit inline. Holding
   pattern until the Playground gets its own pass — without it the long
   "Registry prompt" label squeezes its select to a caret. */
.pg-form .filter { flex-direction: column; align-items: stretch; gap: var(--sp-1); margin-bottom: var(--sp-2); }
.pg-form .filter label { align-self: flex-start; }
.pg-form .filter select, .pg-form .filter input:not([type="checkbox"]) { max-width: none; width: 100%; }
.pg-actions { display: flex; align-items: center; gap: var(--sp-3); }
.pg-side { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.pg-tags { flex-wrap: wrap; margin-bottom: var(--sp-2); }
.pg-case-actions { white-space: nowrap; }
.pg-case-actions .btn { padding: 0 var(--sp-2); font-size: var(--fs-caption); }
.cases-table td { vertical-align: top; }

/* =============================================================================
   Settings and model profiles
   ========================================================================== */

.flash {
  border: var(--rule) solid var(--border-strong); border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3); font-size: var(--fs-body);
}
.flash-ok { color: var(--state-ok-ink); background: var(--state-ok-fill); border-color: var(--state-ok-edge); }
.flash-bad { color: var(--state-error-ink); background: var(--state-error-fill); border-color: var(--state-error-edge); }
.key-form { display: flex; gap: var(--sp-1); align-items: center; }
/* Sized to an API key prefix, not to the scale: any narrower and a pasted
   key shows only its first few characters. */
.key-form input[type="password"] { width: 220px; font-family: var(--font-data); font-size: var(--fs-data); }
.settings-table td { vertical-align: top; }
/* The key form is the POINT of this page, so it must never be the thing that
   scrolls out of view. `Models` is last in the row (see settings.html) and it
   wraps inside a cap instead of growing without bound. Before this, OpenAI's
   ~10 model ids made the table wider than the viewport and pushed
   "Set / replace key" past the right edge of .table-wrap — which scrolls, but
   shows no scrollbar at rest on macOS, so the input read as simply absent. */
.settings-table .models { max-width: 352px; white-space: normal; }
.actions { white-space: nowrap; }
.actions .inline { display: inline; margin-left: var(--sp-1); }
.test-result { display: block; margin-top: var(--sp-1); max-width: 260px; white-space: normal; }
.model-row { cursor: pointer; }
.model-row:hover { background: var(--surface-sunken); }
.model-profile td { background: var(--surface-sunken); }
.profile { padding: var(--sp-1) var(--sp-1) var(--sp-2); font-size: var(--fs-body); }
.profile-desc { margin: 0 0 var(--sp-1); font-weight: var(--fw-bold); }
.profile-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-1) var(--sp-5); margin: 0; }
.profile-grid dt { font-size: var(--fs-head); margin-bottom: 2px; }
.profile-grid dd { margin: 0; color: var(--text-secondary); }

/* =============================================================================
   Charts, notices, sorting
   ========================================================================== */

.chart { display: block; width: 100%; max-width: 100%; height: auto; font-family: var(--font-data); }
.chart-grid { stroke: var(--border-faint); stroke-width: 1; }
.chart-tick { font-size: 10px; fill: var(--text-secondary); }
.chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-line-faint { stroke-width: 1.5; stroke-dasharray: 4 3; opacity: .6; }
.chart-bar { stroke: none; }
.chart-legend { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); font-size: var(--fs-caption); color: var(--text-secondary); margin-top: var(--sp-1); }
.chart-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 5px; vertical-align: -1px; }
.chart-swatch-faint { opacity: .5; }

/* The Graph view of a trace (static/js/graph.js). The canvas is Cytoscape's;
   everything around it is ordinary console chrome. */
.graph-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-2); font-size: var(--fs-caption); }
.graph-toolbar label { display: inline-flex; align-items: center; gap: 5px; color: var(--text-secondary); }
.graph-status { color: var(--text-secondary); font-size: var(--fs-caption); margin-left: auto; }
.graph-status[data-status="failed"] { color: var(--state-error-ink); }
.graph-status[data-status="running"] { color: var(--state-info-ink); }
.graph-wrap { display: flex; gap: var(--sp-2); align-items: stretch; }
.graph-canvas { flex: 1 1 auto; min-width: 0; height: max(480px, 68vh); border: var(--rule) solid var(--border-faint); border-radius: var(--radius); background: var(--surface-raised); }
.graph-canvas:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.graph-side { flex: 0 0 300px; max-height: max(480px, 68vh); overflow: auto; border: var(--rule) solid var(--border-faint); border-radius: var(--radius); background: var(--surface-raised); padding: var(--sp-2); font-size: var(--fs-caption); }
.graph-side-head { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-1); font-size: var(--fs-body); }
.graph-side-dl { display: grid; grid-template-columns: max-content 1fr; gap: 2px var(--sp-2); margin: var(--sp-2) 0; }
.graph-side-dl dt { color: var(--text-muted); }
.graph-side-dl dd { margin: 0; overflow-wrap: anywhere; }
.graph-side-members { margin: var(--sp-1) 0 0; padding-left: 1.2em; }
.graph-key { display: inline-block; width: 16px; height: 10px; margin-right: 5px; vertical-align: -1px; border: 1.5px solid var(--text-secondary); background: var(--surface-raised); }
.graph-key-call { border-radius: 3px; }
.graph-key-decision { width: 10px; transform: rotate(45deg) scale(.8); }
.graph-key-code { background: var(--surface-sunken); }
.graph-key-io { background: var(--surface-sunken); border-radius: 40%; }
.graph-key-result { background: var(--state-ok-fill); border-width: 2.5px; border-color: var(--state-ok-ink); border-radius: 3px; }
.graph-key-group { border-style: double; border-width: 3px; border-radius: 3px; }
.graph-key-crit { border: 0; height: 4px; vertical-align: 2px; background: var(--stage-build); }
@media (max-width: 900px) { .graph-wrap { flex-direction: column; } .graph-side { flex-basis: auto; } }
.chart-note { font-style: italic; }
.chart-mark { stroke: var(--text-secondary); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-mark-label { font-size: 10px; fill: var(--text-secondary); }
/* Horizontal bars: the category label whole at the left, the exact figure
   past the bar's end, p95 as a whisker in ink. */
.chart-label { font-size: 11px; fill: var(--text-primary); }
.chart-value { font-size: 10px; fill: var(--text-secondary); }
.chart-value-na { fill: var(--text-muted); font-style: italic; }
.chart-col-head { font-family: var(--font-ui); font-size: 11px; font-weight: var(--fw-medium); fill: var(--text-secondary); }
.chart-whisker { stroke: var(--measure-whisker); stroke-width: 1.5; }
.chart-swatch-whisker {
  display: inline-block; width: 14px; height: 9px; margin-right: 5px; vertical-align: -1px;
  box-sizing: border-box; border-right: 1.5px solid var(--measure-whisker);
  background: linear-gradient(var(--measure-whisker), var(--measure-whisker)) center / 100% 1.5px no-repeat;
}
.chart-empty {
  padding: var(--sp-5) var(--sp-3); text-align: center; color: var(--text-secondary);
  font-size: var(--fs-body); border: var(--rule) dashed var(--border-strong); border-radius: var(--radius);
}

/* Explanatory copy is important but it is not data: one treatment, always the
   same, so it reads as guidance and never competes with a figure. */
.notice {
  border: var(--rule) solid var(--border-strong); border-left-width: 3px;
  border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3); margin: 0 0 var(--sp-3);
  font-size: var(--fs-caption); line-height: var(--lh-body);
  background: var(--surface-sunken); color: var(--text-secondary);
  max-width: var(--measure-note);
}
.notice strong { color: var(--text-primary); }
.notice-warn { border-left-color: var(--state-warn-ink); background: var(--state-warn-fill); }
.notice-bad { border-left-color: var(--state-error-ink); background: var(--state-error-fill); }

/* Failure-rate judgement. The verdict is a word in the cell; the fill and
   the row rule repeat it for the eye. See judgement.go. */
.fail-note { display: block; font-size: var(--fs-caption); font-weight: var(--fw-normal); color: var(--text-muted); white-space: nowrap; }
td.fail-over { color: var(--state-error-ink); background: var(--state-error-fill); font-weight: var(--fw-bold); }
td.fail-maybe { color: var(--state-warn-ink); background: var(--state-warn-fill); }
td.fail-over .fail-note, td.fail-maybe .fail-note { color: inherit; }
td.fail-thin { color: var(--text-secondary); }
tr.row-over > td:first-child { box-shadow: inset 3px 0 0 var(--state-error-ink); }
tr.row-maybe > td:first-child { box-shadow: inset 3px 0 0 var(--state-warn-ink); }
.failure-line .failure-thin { display: block; color: var(--text-secondary); }

/* Period over period under the Dashboard cards. */
.card-delta { font-size: var(--fs-caption); color: var(--text-secondary); margin-top: 2px; }
.card-delta.delta-worse { color: var(--state-error-ink); }
.card-delta.delta-better { color: var(--state-ok-ink); }
.delta-word { font-weight: var(--fw-bold); }
.delta-label { color: var(--text-muted); }

/* Recent thumbs-down and quality over time on Compare. */
.vote-note { min-width: 24ch; max-width: 60ch; white-space: pre-wrap; }
.trend + .trend { border-top: var(--rule) solid var(--border-faint); margin-top: var(--sp-3); padding-top: var(--sp-3); }
.trend-title { font-size: var(--fs-sub); font-weight: var(--fw-bold); margin: 0 0 var(--sp-2); }
.trend-label { font-size: var(--fs-caption); color: var(--text-secondary); margin-bottom: var(--sp-1); }
.notice code { font-family: var(--font-data); font-size: var(--fs-data); }

.seg { display: inline-flex; border: var(--rule) solid var(--border-strong); border-radius: var(--radius); overflow: hidden; }
.seg a { padding: 0 var(--sp-2); line-height: calc(var(--control-h) - 2px); font-size: var(--fs-caption); color: var(--text-secondary); border-right: var(--rule) solid var(--border-strong); }
.seg a:last-child { border-right: 0; }
.seg a:hover { background: var(--surface-sunken); text-decoration: none; }
.seg a.active { background: var(--text-primary); color: var(--surface-raised); }

/* ---- Sortable headers -----------------------------------------------------
   A header that sorts is a control and looks like one; a header that does not
   is plain text with no affordance at all. The indicator is drawn on the
   ACTIVE column only — an arrow on every header is noise that tells the
   reader nothing about the current order.

   .th-sort covers both mechanisms: a link on the server-sorted tables
   (Requests, Compare) and a button the JS builds on the client-sorted ones.
   They are styled identically because to the reader they are the same
   control. */
th.sortable a, .th-sort {
  color: inherit; font: inherit;
  display: inline-block; max-width: 100%;
  background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; text-align: inherit;
}
th.sortable a:hover, .th-sort:hover { text-decoration: underline; color: var(--text-primary); }
/* A column that can be ordered says so at rest, or the reader has to hover
   every header to find out which ones are controls. The mark is the quietest
   thing that still reads as an affordance, and it gives way to the real
   arrow once the column is the active one. */
th.sortable a::after, .th-sort::after {
  content: " \21C5";
  color: var(--text-muted);
  opacity: .5;
  font-size: 9px;
}
th.sorted a::after, .th-sorted::after { content: none; }
th.sorted, .th-sorted { color: var(--text-primary); }
.sort-arrow { font-size: 9px; color: var(--text-muted); }
/* The sorted column is marked by weight as well as by the arrow, so the
   active order survives being read in a hurry or without colour. */
th[aria-sort="ascending"], th[aria-sort="descending"] {
  color: var(--text-primary);
  box-shadow: inset 0 -2px 0 var(--text-secondary);
}

.table-foot { font-size: var(--fs-caption); color: var(--text-secondary); margin: var(--sp-2) 2px 0; line-height: var(--lh-body); max-width: var(--measure-note); }
.price-date { font-size: var(--fs-caption); color: var(--text-secondary); margin: 0 0 var(--sp-2); max-width: var(--measure-note); }
.price-date code { font-family: var(--font-data); font-size: var(--fs-data); }

/* =============================================================================
   Feedback votes
   ========================================================================== */

.vote { display: inline-flex; gap: 2px; align-items: center; }
.vote-btn {
  border: var(--rule) solid transparent; background: transparent;
  border-radius: var(--radius); padding: 0 var(--sp-1); cursor: pointer;
  font-size: var(--fs-body); line-height: 1; opacity: .4;
}
.vote-btn:hover { opacity: 1; border-color: var(--border-strong); background: var(--surface-sunken); }
.vote-on-up { opacity: 1; border-color: var(--state-ok-edge); background: var(--state-ok-fill); }
.vote-on-down { opacity: 1; border-color: var(--state-error-edge); background: var(--state-error-fill); }
.vote-detail { gap: var(--sp-2); flex-wrap: wrap; }
.vote-detail input[type="text"] { flex: 1; min-width: 260px; }
.vote-current { margin: 0 0 var(--sp-2); font-size: var(--fs-body); display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.vote-note { color: var(--text-secondary); font-style: italic; }

/* =============================================================================
   Panel layout (Dashboard, Compare) — the GridStack board panelLayout in
   app.js builds. Without script none of this applies and the grid is the
   markup's two columns.
   ========================================================================== */

.layout-bar { font-size: var(--fs-caption); margin: 0 0 var(--sp-1); }
.linklike { all: unset; color: var(--text-link); cursor: pointer; font: inherit; }
.linklike:hover { text-decoration: underline; }
.linklike:focus-visible { outline: 2px solid var(--text-link); outline-offset: 2px; }

/* The card is the grid item's content box, so a panel given a height scrolls
   inside its own frame; the panel inside it is only padding. */
.grid-stack { margin: 0 calc(-1 * 6px); }
.grid-stack > .grid-stack-item > .grid-stack-item-content {
  background: var(--surface-raised);
  border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius);
}
.grid-stack > .grid-stack-item > .grid-stack-item-content > .panel {
  position: relative; border: 0; background: transparent; margin: 0; border-radius: 0;
}
.grid-stack .panel > .panel-title { padding-right: 76px; }
.grid-stack-placeholder > .placeholder-content {
  background: var(--surface-sunken) !important;
  border: 1px dashed var(--text-link); border-radius: var(--radius);
}
.grid-stack > .ui-draggable-dragging > .grid-stack-item-content,
.grid-stack > .ui-resizable-resizing > .grid-stack-item-content { box-shadow: 0 6px 24px rgba(0, 0, 0, .18); }
.grid-stack-item > .ui-resizable-handle { opacity: .7; }

.panel-tools {
  position: absolute; top: var(--sp-2); right: var(--sp-2);
  display: flex; gap: 2px; z-index: 2;
}
.panel-tool {
  all: unset; box-sizing: border-box;
  width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
  border: var(--rule) solid transparent; border-radius: var(--radius);
  color: var(--text-muted); font-size: 13px; line-height: 1; cursor: pointer;
}
.panel-tool:hover, .panel-export[open] > .panel-tool { color: var(--text-primary); border-color: var(--border-strong); background: var(--surface-sunken); }
.panel-tool:focus-visible { outline: 2px solid var(--text-link); outline-offset: 1px; }
.panel-tool::-webkit-details-marker { display: none; }
.panel-grip { cursor: grab; font-size: 15px; }
.panel-grip:active { cursor: grabbing; }

.panel-export { position: relative; }
.panel-export > summary { list-style: none; }
.panel-export > summary::marker { content: ""; }
.panel-export > summary::after, .panel-export > summary::before { display: none !important; } /* the disclosure triangles */
.panel-export-menu {
  position: absolute; right: 0; top: 26px; z-index: 5;
  display: flex; flex-direction: column; min-width: 90px;
  background: var(--surface-raised); border: var(--rule) solid var(--border-strong); border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, .12); padding: 2px 0;
}
.panel-export-menu a { padding: 3px var(--sp-2); font-size: var(--fs-caption); white-space: nowrap; }
.panel-export-menu a:hover { background: var(--surface-sunken); text-decoration: none; }
/* The menu sticks out of a short panel: the item must not clip it. */
.grid-stack-item:has(.panel-export[open]) { z-index: 10; }
.grid-stack-item:has(.panel-export[open]) > .grid-stack-item-content { overflow: visible; }

/* Folded: the title and the tools; nothing else. */
.panel-folded > :not(.panel-title):not(.panel-tools) { display: none; }
.panel-folded > .panel-title { margin-bottom: 0; }

/* The row under a page's window bar: the page's Export, the jump to the
   review. */
.page-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; margin: 0 0 var(--sp-2); }

/* AI review */
.ai-review-run { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin: 0 0 var(--sp-2); }
.ai-review-run .muted { flex: 1; min-width: 20em; }
.ai-review-run .when-busy, #ai-review.htmx-request .when-idle { display: none; }
#ai-review.htmx-request .when-busy { display: inline; }
.ai-entry { border-top: var(--rule) solid var(--border-faint); padding: var(--sp-2) 0; }
.ai-entry > summary { cursor: pointer; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); align-items: baseline; }
.ai-entry-head { font-weight: var(--fw-bold); color: var(--text-primary); }
.ai-summary { margin: var(--sp-2) 0; max-width: var(--measure-prose); }
.ai-findings { list-style: none; margin: 0 0 var(--sp-2); padding: 0; }
.ai-findings li { display: flex; gap: var(--sp-2); align-items: baseline; padding: var(--sp-1) 0; border-bottom: var(--rule) dotted var(--border-faint); }
.ai-sev {
  flex: none; font-size: var(--fs-caption); font-family: var(--font-data); text-transform: uppercase;
  padding: 0 var(--sp-1); border-radius: var(--radius); border: var(--rule) solid currentColor; min-width: 5.5em; text-align: center;
}
.ai-sev-critical .ai-sev { color: var(--state-error-ink); }
.ai-sev-warning .ai-sev { color: var(--state-warn-ink); }
.ai-sev-info .ai-sev { color: var(--text-secondary); }
.ai-sev-good .ai-sev { color: var(--state-ok-ink); }
.ai-steps { margin: 0; padding-left: 1.4em; }
.ai-steps li { padding: 2px 0; }

/* Latest errors (Dashboard): a paged, searchable table. */
.errors-search { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin: 0; flex: 1; }
.errors-search input[type="search"] { width: 26em; max-width: 100%; margin: 0; height: var(--control-h); font-size: var(--fs-caption); }
.errors-table .error-msg { min-width: 18em; }
.nowrap { white-space: nowrap; }

/* =============================================================================
   Responsive
   ========================================================================== */

@media (max-width: 1100px) {
  .playground { grid-template-columns: 1fr; }
  .grid-2 { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 760px) {
  :root { --sidebar-w: 0px; }
  .sidebar { position: static; width: auto; flex-direction: row; align-items: center; overflow-x: auto; }
  .brand { border-bottom: 0; padding: var(--sp-2) var(--sp-3); }
  .brand-sub { display: none; }
  .nav { display: flex; margin: 0; }
  .nav a { border-left: 0; border-bottom: 2px solid transparent; white-space: nowrap; }
  .nav a.active { border-bottom-color: #3fb950; }
  .main { margin-left: 0; }
  .topbar { position: static; height: auto; flex-wrap: wrap; padding: var(--sp-2) var(--sp-3); }
  .filters { position: static; }
  .table-tall { max-height: none; }
}

/* Motion answers an action or it does not happen. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}

/* ---------------------------------------------------------------------------
   Prompt editor: history diff, danger panel, model checkboxes
   --------------------------------------------------------------------------- */
.diff { padding: 0; }
.diff-line { display: block; padding: 0 var(--sp-2); white-space: pre-wrap; }
.diff-add { color: var(--state-ok-ink); background: var(--state-ok-fill); }
.diff-del { color: var(--state-error-ink); background: var(--state-error-fill); text-decoration: line-through; text-decoration-color: var(--state-error-edge); }
.diff-same { color: var(--text-secondary); }
.row-active td { background: var(--state-ok-fill); }
.panel-danger { border-color: var(--state-error-edge); }
/* A chip with a tick inside is a flex line, not two inline boxes: without a
   flex display the `gap` above does nothing and the checkbox sits on the
   text baseline instead of beside it. */
.chip-check { display: inline-flex; align-items: center; cursor: pointer; gap: var(--sp-1); }
.chip-check input { margin: 0; flex: none; }
.chip-check:hover { border-color: var(--border-strong); }
.chip-check:has(:checked) { background: var(--state-ok-fill); border-color: var(--state-ok-edge); }
/* The metadata filter: up to three key=value rows, each a select beside
   an input. They stack because a term is a PAIR — side by side the third
   row would wrap mid-pair and the reader would have to work out which
   value belonged to which key. */
.filter-meta { min-width: 22rem; }
.meta-term { display: flex; gap: var(--sp-1); align-items: center; }
.meta-term + .meta-term { margin-top: var(--sp-1); }
.meta-term select { flex: 0 0 40%; min-width: 0; }
.meta-term input { flex: 1 1 auto; min-width: 0; }

/* An ordered chain reads down, one slot per line, so position IS the order. */
.chain { display: flex; flex-direction: column; gap: var(--sp-1); }

/* Tabs. The panels are plain sections toggled with the `hidden` attribute;
   nothing here may set a `display` that out-specifies it. */
.tabs { display: flex; gap: var(--sp-1); margin-bottom: calc(var(--sp-2) * -1); border-bottom: var(--rule) solid var(--border-strong); }
.tab {
  height: var(--control-h); padding: 0 var(--sp-3);
  font: var(--fw-medium) var(--fs-body)/1 var(--font-ui);
  color: var(--text-secondary); background: none; cursor: pointer;
  border: var(--rule) solid transparent; border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  margin-bottom: calc(var(--rule) * -1);
}
.tab:hover { color: var(--text-primary); background: var(--surface-sunken); }
.tab[aria-selected="true"] {
  color: var(--text-primary); font-weight: var(--fw-bold);
  background: var(--surface-raised); border-color: var(--border-strong);
}
.tab-panel[hidden] { display: none; }
/* The save bar belongs to the draft, not to a tab: it stays put whichever
   section is open, so Save is never a tab away. It only needs a rule to
   separate it from a section — on the Test tab it is the panel's only
   content, and a rule there frames an empty box. */
.save-bar { padding-top: var(--sp-2); }
.pg-form:has(.tab-panel:not([hidden])) .save-bar {
  border-top: var(--rule) solid var(--border-faint); margin-top: var(--sp-3);
}
.models-fieldset { border: 0; padding: 0; margin: 0; }
.models-fieldset legend { padding: 0; margin-bottom: var(--sp-1); font-weight: var(--fw-bold); font-size: var(--fs-caption); }
.rename-form { align-items: flex-end; gap: var(--sp-2); }
.pg-result-panel .kv { display: grid; grid-template-columns: repeat(4, auto); gap: var(--sp-2); margin: var(--sp-2) 0; }
.flash ul { margin: var(--sp-1) 0 0 var(--sp-3); }

/* ---------------------------------------------------------------------------
   Prompt icons
   An icon is how a prompt is found at a glance down a column of requests,
   so it rides INSIDE the name's link and never replaces the name. The emoji
   families are named first because the data font stack is monospace, and a
   monospace face without colour glyphs renders some of these as tofu.
   --------------------------------------------------------------------------- */
.prompt-icon {
  display: inline-block; margin-right: var(--sp-1);
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  font-size: 13px; line-height: 1; vertical-align: -1px;
  text-decoration: none;
}
.detail-id .prompt-icon { font-size: 18px; vertical-align: -2px; }
.icon-picker { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin: var(--sp-2) 0 0; }
#icon summary.panel-title { margin-bottom: 0; }

/* Link tabs ([data-tabs], the Playground): the same strip as the prompt
   editor's button tabs, but each tab is a real ?tab= link so it still works
   without script. A link needs the button's box model spelled out. */
a.tab { display: inline-flex; align-items: center; gap: var(--sp-1); }
a.tab:hover { text-decoration: none; }
.tab-count {
  font: var(--fw-normal) var(--fs-caption)/1 var(--font-data);
  color: var(--text-secondary); background: var(--surface-sunken);
  border: var(--rule) solid var(--border-faint); border-radius: var(--radius);
  padding: 1px var(--sp-1);
}
/* The strip sits on the panels below it, not in a gap above them. */
.tabs[data-tabs] { margin-bottom: 0; }
.tabs[data-tabs] + .tab-panel,
.tabs[data-tabs] ~ .tab-panel { margin-top: var(--sp-3); }
/* Sized to the glyph, not the control grid: an emoji at control height is
   too small to tell apart, and this is a picker, not a form row. */
.icon-choice {
  width: 32px; height: 32px; padding: 0;
  font: 17px/1 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
  background: var(--surface-raised); cursor: pointer;
  border: var(--rule) solid var(--border-faint); border-radius: var(--radius-control);
}
.icon-choice:hover { border-color: var(--border-strong); background: var(--surface-sunken); }
.icon-choice[aria-pressed="true"] {
  border-color: var(--state-ok-ink); background: var(--state-ok-fill);
  box-shadow: inset 0 0 0 1px var(--state-ok-ink);
}
.icon-choice-none {
  width: auto; padding: 0 var(--sp-3);
  font: var(--fw-medium) var(--fs-caption)/1 var(--font-ui); color: var(--text-secondary);
}

/* A settings field callers override per call (prompt editor, max_tokens). */
.warn-note {
  margin: var(--sp-1) 0 0;
  padding: var(--sp-1) var(--sp-2);
  border-left: 2px solid var(--state-warn-ink);
  background: var(--state-warn-fill);
}

/* The experiment layer's bucket space (collections): one bar, 0–100%. */
.xbar { position: relative; height: 1.6rem; border: 1px solid var(--border-strong); border-radius: 4px; background: var(--surface-sunken); overflow: hidden; margin: var(--sp-2) 0; }
.xbar-seg { position: absolute; top: 0; bottom: 0; font-size: var(--fs-caption); line-height: 1.6rem; padding: 0 var(--sp-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid var(--surface-raised); }
.xbar-live { background: var(--series-ok); color: var(--surface-raised); }
.xbar-off { background: var(--series-warn); color: var(--text-primary); }
.xbar-free { background: var(--surface-sunken); color: var(--text-secondary); }

/* ---------------------------------------------------------------------------
   Collections pages (/collections, /collections/{id})
   A page head with actions, cards with a head / body / foot, toggle cards,
   underline tabs inside a card, and a danger card for irreversible actions.
   --------------------------------------------------------------------------- */
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-4); margin-bottom: var(--sp-3); }
.page-head h1 { font-size: var(--fs-title); font-weight: var(--fw-bold); margin: 0 0 var(--sp-1); display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.page-head .lead { margin: 0; }
.page-meta { font-size: var(--fs-body); font-weight: var(--fw-normal); color: var(--text-secondary); }
.page-actions { display: flex; gap: var(--sp-2); align-items: center; flex-shrink: 0; }
.crumbs { font-size: var(--fs-body); color: var(--text-secondary); margin-bottom: var(--sp-2); }
.crumbs a { margin-right: var(--sp-1); }
.crumbs span { margin-left: var(--sp-1); }

.pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font: var(--fw-medium) var(--fs-caption)/1 var(--font-ui);
  padding: 3px var(--sp-2); border-radius: 999px;
  background: var(--surface-sunken); color: var(--text-secondary);
}
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .7; }
.pill-live { background: var(--state-ok-fill); color: var(--state-ok-ink); }

.xcard { background: var(--surface-raised); border: var(--rule) solid var(--border-strong); border-radius: var(--radius); margin-bottom: var(--sp-3); }
.xcard-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-3); border-bottom: var(--rule) solid var(--border-faint); }
.xcard-head h2 { font-size: var(--fs-section); font-weight: var(--fw-bold); margin: 0; }
.xcard-body { padding: var(--sp-3); }
.xcard-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-2) var(--sp-3); border-top: var(--rule) solid var(--border-faint); background: var(--surface-page); }
.xcard-foot input { max-width: 360px; margin: 0; }
.xcard-hint { padding: var(--sp-2) var(--sp-3); border-top: var(--rule) solid var(--border-faint); color: var(--text-secondary); }
.xcard > .table-wrap { border: 0; border-radius: 0; }
.xcard > .table-wrap + .xcard-hint { border-top: var(--rule) solid var(--border-faint); }

.legend { display: flex; gap: var(--sp-3); font-size: var(--fs-caption); color: var(--text-secondary); }
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: var(--sp-1); vertical-align: -1px; }
.legend .l-live { background: var(--series-ok); }
.legend .l-off { background: var(--series-warn); }
.legend .l-free { background: var(--border-strong); }
.xbar-empty { font-family: var(--font-data); font-size: var(--fs-caption); color: var(--text-secondary); padding: 0 var(--sp-2); line-height: 1.6rem; }

.split-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-4); border-top: var(--rule) solid var(--border-faint); padding-top: var(--sp-3); margin-top: var(--sp-3); }
.split-2 h3, .manage-row h3 { font-size: var(--fs-body); font-weight: var(--fw-bold); margin: 0; }
.split-2 p, .manage-row p { margin: 2px 0 var(--sp-2); color: var(--text-secondary); font-size: var(--fs-caption); }
.inline-form { display: flex; gap: var(--sp-2); align-items: center; }
.inline-form input { margin: 0; flex: 1; }

.btn-danger { background: var(--surface-raised); color: var(--state-error-ink); border-color: var(--state-error-edge); }
.btn-danger:hover { background: var(--state-error-fill); }
.btn-outline { background: var(--surface-raised); }

.fields { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: var(--sp-3); margin-bottom: var(--sp-3); }
.fields label { font-size: var(--fs-head); font-weight: var(--fw-bold); color: var(--text-primary); display: block; }
.fields input { margin-top: var(--sp-1); margin-bottom: 0; }
.toggles { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-3); }
.toggle-card { display: flex; gap: var(--sp-2); align-items: flex-start; border: var(--rule) solid var(--border-strong); border-radius: var(--radius); padding: var(--sp-2) var(--sp-3); cursor: pointer; margin: 0; }
.toggle-card strong { display: block; font-size: var(--fs-body); }
.toggle-card span { color: var(--text-secondary); font-size: var(--fs-caption); }
.toggle-card code { font-weight: var(--fw-bold); background: none; padding: 0; }
input.switch[type="checkbox"] {
  appearance: none; -webkit-appearance: none; flex-shrink: 0;
  width: 30px; height: 16px; margin: 2px 0 0; border-radius: 999px; border: 0;
  background: var(--border-strong); position: relative; cursor: pointer; transition: background .15s;
}
input.switch[type="checkbox"]::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 12px; height: 12px;
  border-radius: 50%; background: var(--surface-raised); transition: left .15s;
}
input.switch[type="checkbox"]:checked { background: var(--state-ok-ink); }
input.switch[type="checkbox"]:checked::after { left: 16px; }

.utabs { display: flex; gap: var(--sp-4); padding: 0 var(--sp-3); border-bottom: var(--rule) solid var(--border-faint); }
.utab { display: inline-flex; align-items: center; gap: var(--sp-1); padding: var(--sp-3) 0 var(--sp-2); color: var(--text-secondary); border-bottom: 2px solid transparent; margin-bottom: calc(var(--rule) * -1); }
.utab:hover { color: var(--text-primary); text-decoration: none; }
.utab[aria-selected="true"] { color: var(--text-primary); font-weight: var(--fw-bold); border-bottom-color: var(--state-ok-ink); }
.utab-body { padding: var(--sp-3); }
.utab-body > .panel { border: 0; padding: 0; margin: 0; }
.actions-bar { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; padding: var(--sp-2) var(--sp-3); border-top: var(--rule) solid var(--border-faint); background: var(--surface-page); }
.actions-bar > * { margin: 0; width: auto; }
.actions-bar .muted { margin-right: auto; }

.prompt-tab-toolbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); border-bottom: var(--rule) solid var(--border-faint);
}
.prompt-tab-toolbar > div:first-child { display: flex; align-items: baseline; gap: var(--sp-2); min-width: 0; }
.add-prompts { position: relative; flex: none; }
.add-prompts > summary { list-style: none; cursor: pointer; }
.add-prompts > summary::-webkit-details-marker { display: none; }
.add-prompts > summary::marker { content: ""; }
.add-prompts[open] > summary { background: var(--state-ok-fill); color: var(--state-ok-ink); }
.add-prompts-form {
  position: absolute; z-index: 20; top: calc(100% + var(--sp-2)); right: 0;
  width: min(880px, calc(100vw - var(--sidebar-w) - var(--sp-6)));
  padding: var(--sp-3); background: var(--surface-raised); border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius); box-shadow: 0 8px 24px rgba(13, 17, 23, .14);
}
.add-prompts-head, .add-prompts-actions, .entry-actions, .targeting-actions, .targeting-probe {
  display: flex; align-items: center; gap: var(--sp-2);
}
.add-prompts-head { margin-bottom: var(--sp-2); }
.add-prompts-head input { flex: 1; min-width: 0; }
.prompt-picker-table { max-height: 310px; }
.prompt-picker-table table { min-width: 680px; }
.prompt-picker-table td:last-child { max-width: 30em; }
.entry-editor {
  display: grid; grid-template-columns: minmax(150px, 1.2fr) minmax(150px, 1fr) minmax(105px, .6fr) minmax(105px, .6fr) minmax(170px, 1fr);
  gap: var(--sp-2); min-width: 0;
}
.entry-editor label, .targeting-basics > label {
  display: flex; flex-direction: column; gap: var(--sp-1);
  min-width: 0; color: var(--text-secondary); font-size: var(--fs-caption); font-weight: var(--fw-medium);
}
.entry-editor :is(input, select), .targeting-basics :is(input:not([type="checkbox"]), select) { width: 100%; min-width: 0; }
.add-prompts-form .entry-editor { margin-top: var(--sp-3); }
.add-prompts-actions { justify-content: space-between; margin-top: var(--sp-3); }
.entry-bulk-actions { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: end; gap: var(--sp-3); }
.entry-bulk-actions > strong { align-self: center; white-space: nowrap; }
.entry-actions { align-self: end; }

.targeting-form { display: grid; gap: var(--sp-3); }
.targeting-basics {
  display: grid; grid-template-columns: minmax(100px, .55fr) minmax(210px, 1fr) minmax(130px, .7fr) minmax(290px, 1.5fr);
  gap: var(--sp-3); align-items: end;
}
.targeting-check { flex-direction: row !important; align-items: center; padding-bottom: 7px; color: var(--text-primary) !important; }
.targeting-check input { flex: none; }
.targeting-check small { color: var(--text-secondary); font-weight: var(--fw-normal); }
.targeting-lists fieldset { min-width: 0; padding: var(--sp-3); }
.targeting-lists fieldset > input { width: 100%; }
.targeting-actions { padding-top: var(--sp-2); border-top: var(--rule) solid var(--border-faint); }
.targeting-actions input, .targeting-probe input { flex: 1; min-width: 0; }
.targeting-actions button, .targeting-probe button { flex: none; }
.targeting-probe { margin-top: var(--sp-3); }

.xcard-danger { border-color: var(--state-error-edge); }
.manage-row { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-4); padding: var(--sp-3); border-top: var(--rule) solid var(--state-error-fill); }
.manage-row .inline-form { width: 46%; }
.manage-row.danger h3 { color: var(--state-error-ink); }

.menu { position: relative; }
.menu > summary { list-style: none; }
.menu > summary::-webkit-details-marker { display: none; }
.menu-pop { position: absolute; right: 0; top: calc(100% + var(--sp-1)); z-index: 20; width: 320px; background: var(--surface-raised); border: var(--rule) solid var(--border-strong); border-radius: var(--radius); padding: var(--sp-3); box-shadow: 0 6px 18px rgba(13, 17, 23, .12); }
.menu-pop input { margin-bottom: var(--sp-2); }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.xcard-titlebar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.xcard-titlebar h2 { font-size: var(--fs-section); font-weight: var(--fw-bold); margin: 0; }
.menu > summary::after, .menu > summary::marker { display: none; content: none; }
.fields label { display: flex; flex-direction: column; gap: var(--sp-1); }
.fields input { width: 100%; margin-top: 0; }
.toggles > .toggle-card { width: 100%; box-sizing: border-box; }
.xcard-foot input { flex: 1; max-width: 520px; }
.page-head h1 .pill { font-size: var(--fs-caption); }
.menu > summary::before, details[open].menu > summary::before { content: none; }

@media (max-width: 1200px) {
  .entry-bulk-actions { grid-template-columns: 1fr auto; }
  .entry-bulk-actions > strong { grid-column: 1 / -1; }
  .entry-editor { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .entry-editor .entry-note { grid-column: 1 / -1; }
  .targeting-basics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
  .page-head, .prompt-tab-toolbar, .manage-row { align-items: stretch; flex-direction: column; }
  .page-actions { align-self: flex-start; }
  .prompt-tab-toolbar > div:first-child { align-items: flex-start; flex-direction: column; gap: 0; }
  .add-prompts { width: 100%; }
  .add-prompts > summary { width: 100%; text-align: center; }
  .add-prompts-form { position: static; width: 100%; margin-top: var(--sp-2); box-shadow: none; }
  .entry-bulk-actions { grid-template-columns: minmax(0, 1fr); }
  .entry-editor, .targeting-basics { grid-template-columns: minmax(0, 1fr); }
  .entry-editor .entry-note { grid-column: auto; }
  .entry-actions, .targeting-actions, .targeting-probe { align-items: stretch; flex-direction: column; }
  .entry-actions .btn, .targeting-actions .btn, .targeting-probe .btn { width: 100%; }
  .manage-row .inline-form { width: 100%; }
  .utabs { gap: var(--sp-3); overflow-x: auto; }
}

/* ---- Sign in (auth.go) ------------------------------------------------------
   One card on the page colour; the console's own type and controls. */
.signout { margin: auto 0 0; padding: var(--sp-3) var(--sp-4); border-top: var(--rule) solid #21262d; }
.signout button {
  width: 100%; padding: var(--sp-1) var(--sp-2); font-size: var(--fs-body);
  background: transparent; color: #c9d1d9; border: var(--rule) solid #30363d; border-radius: 6px; cursor: pointer;
}
.signout button:hover { background: #161b22; color: #fff; }
.login-page { min-height: 100vh; margin: 0; display: grid; place-items: center; background: var(--surface-page); padding: var(--sp-4); box-sizing: border-box; }
.login-card {
  width: 100%; max-width: 360px; background: var(--surface-raised); color: var(--text-primary);
  border: var(--rule) solid var(--border-faint); border-radius: 10px; padding: var(--sp-5);
  box-shadow: 0 8px 28px rgba(13, 17, 23, .08);
}
.login-brand { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.login-brand .brand-mark { font-size: 22px; color: var(--state-ok-ink); }
.login-title { font-weight: var(--fw-bold); font-size: 17px; }
.login-sub { color: var(--text-secondary); font-size: var(--fs-head); }
.login-form { display: flex; flex-direction: column; gap: var(--sp-1); }
.login-form label { font-size: var(--fs-head); font-weight: var(--fw-bold); color: var(--text-secondary); margin-top: var(--sp-2); }
.login-form input { width: 100%; box-sizing: border-box; }
.login-form button { width: 100%; margin-top: var(--sp-4); cursor: pointer; }
.login-error {
  margin: 0 0 var(--sp-1); padding: var(--sp-2) var(--sp-3); border-radius: 6px; font-size: var(--fs-body);
  background: var(--state-error-fill); color: var(--state-error-ink);
}

/* ---------- /prompts: bulk actions ----------
   A choice of three, then only the fields that choice uses (:has() hides the
   rest; a browser without it shows every field, which still posts correctly). */
.bulk-bar {
  position: sticky; bottom: var(--sp-3); z-index: 15;
  display: flex; align-items: center; gap: var(--sp-2); width: fit-content; margin: var(--sp-3) 0 0 auto;
  padding: var(--sp-2) var(--sp-3); background: var(--surface-raised); border: var(--rule) solid var(--border-strong);
  border-radius: var(--radius); box-shadow: 0 6px 18px rgba(13, 17, 23, .14);
}
.bulk-bar[hidden] { display: none; }
.bulk-panel { margin-top: var(--sp-4); padding: var(--sp-3); border: var(--rule) solid var(--border-faint); border-radius: var(--radius); background: var(--surface-raised); }
.bulk-title { margin: 0; font-size: var(--fs-section); font-weight: var(--fw-bold); }
.bulk-lead { margin: var(--sp-1) 0 var(--sp-3); }
.bulk-choices { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-2); margin: 0 0 var(--sp-3); padding: 0; border: 0; }
.bulk-choice {
  display: flex; align-items: flex-start; gap: var(--sp-2); margin: 0; padding: var(--sp-3);
  border: var(--rule) solid var(--border-strong); border-radius: var(--radius); cursor: pointer;
}
.bulk-choice input { flex: none; margin-top: 2px; }
.bulk-choice > span { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.bulk-choice small { color: var(--text-secondary); font-size: var(--fs-caption); line-height: 1.4; }
.bulk-choice:has(input:checked) { border-color: var(--state-info-edge); background: var(--state-info-fill); }
.bulk-choice:has(input:focus-visible) { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.bulk-step + .bulk-step { margin-top: var(--sp-2); }
.entry-editor.bulk-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.entry-editor.bulk-four { grid-template-columns: minmax(170px, 1.2fr) minmax(220px, 1.6fr) minmax(105px, .5fr) minmax(105px, .5fr); }
.bulk-replace { display: flex; align-items: flex-end; gap: var(--sp-2); flex-wrap: wrap; }
.bulk-replace > label:not(.targeting-check) {
  display: flex; flex-direction: column; gap: var(--sp-1); min-width: 200px;
  color: var(--text-secondary); font-size: var(--fs-caption); font-weight: var(--fw-medium);
}
.bulk-arrow { padding-bottom: 9px; color: var(--text-muted); }
.bulk-submit { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: var(--rule) solid var(--border-faint); }
.bulk-for-collection, .bulk-for-replace { display: none; }
.bulk-panel:has([name="action"][value="add_collection"]:checked) .bulk-for-collection { display: block; }
.bulk-panel:has([name="action"][value="replace_model"]:checked) .bulk-for-replace { display: block; }
.bulk-panel:has([name="action"][value="replace_model"]:checked) .bulk-for-model { display: none; }
@media (max-width: 1000px) {
  .bulk-choices, .entry-editor.bulk-four { grid-template-columns: minmax(0, 1fr); }
  .bulk-submit { flex-direction: column; align-items: stretch; }
}

/* ---------- prompt tags ---------- */
.notice-ok { border-left-color: var(--state-ok-ink); background: var(--state-ok-fill); }
.tag {
  display: inline-block; margin: 0 var(--sp-1) 2px 0; padding: 0 6px; border-radius: 999px;
  background: var(--surface-sunken); border: var(--rule) solid var(--border-faint);
  color: var(--text-secondary); font-size: var(--fs-caption); line-height: 18px; white-space: nowrap;
}
.tag-cell { white-space: nowrap; }
.tag-filter { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); margin: 0 0 var(--sp-2); }
.tag-filter > .small { margin-right: var(--sp-1); }
.tag-chip {
  display: inline-flex; align-items: center; gap: 6px; width: auto; margin: 0; padding: 2px 10px;
  border: var(--rule) solid var(--border-strong); border-radius: 999px; background: var(--surface-raised);
  color: var(--text-primary); font-size: var(--fs-caption); line-height: 18px; cursor: pointer;
}
.tag-chip:hover { border-color: var(--state-info-edge); }
.tag-chip[aria-pressed="true"] { background: var(--state-info-fill); border-color: var(--state-info-edge); color: var(--state-info-ink); }
.tag-count { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.tag-chip[aria-pressed="true"] .tag-count { color: inherit; }
.tags-form { display: flex; align-items: flex-end; gap: var(--sp-2); flex-wrap: wrap; }
.tags-field { display: flex; flex-direction: column; gap: var(--sp-1); flex: 1; min-width: 240px; color: var(--text-secondary); font-size: var(--fs-caption); font-weight: var(--fw-medium); }
.tags-field input { margin: 0; }
.bulk-for-tag, .bulk-note-tag, .bulk-go-tag { display: none; }
.bulk-panel:has([name="action"][value="tag"]:checked) .bulk-for-tag { display: block; }
.bulk-panel:has([name="action"][value="tag"]:checked) :is(.bulk-for-model, .bulk-note-preview, .bulk-go-preview) { display: none; }
.bulk-panel:has([name="action"][value="tag"]:checked) :is(.bulk-note-tag, .bulk-go-tag) { display: inline-flex; }
.bulk-choices { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1300px) { .bulk-choices { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 1000px) { .bulk-choices { grid-template-columns: minmax(0, 1fr); } }
.registry-filter { display: flex; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-2); }
.registry-filter input { flex: 1; min-width: 0; margin: 0; }
.registry-filter button { flex: none; width: auto; margin: 0; }

/* ---- Graph tab: header, cards, waterfall, inspector (graph*.js) ---- */
.graph-header { margin: 0 0 var(--sp-2); font-size: var(--fs-caption); }
.graph-goal { margin: 0 0 var(--sp-1); font-size: var(--fs-body); color: var(--text-primary); }
.graph-figs { margin: 0 0 var(--sp-1); }
.graph-warnings { margin: var(--sp-1) 0 0; padding-left: 1.2em; }
.graph-warn { color: var(--state-warn-ink); }
.graph-warn-failed, .graph-warn-thumbs_down, .graph-warn-empty_length { color: var(--state-error-ink); }
.graph-waterfall { margin-top: var(--sp-2); }
.wf { width: 100%; border-collapse: collapse; font-size: var(--fs-caption); }
.wf th { text-align: left; color: var(--text-muted); font-weight: var(--fw-regular); padding: 2px 6px; }
.wf-row { cursor: pointer; }
.wf-row td { padding: 2px 6px; border-top: var(--rule) solid var(--border-faint); white-space: nowrap; }
.wf-row.wf-hover td, .wf-row:hover td { background: var(--surface-sunken); }
.wf-row.wf-selected td { background: var(--state-info-fill, #ddf4ff); }
.wf-name { max-width: 300px; overflow: hidden; text-overflow: ellipsis; }
.wf-glyph { display: inline-block; width: 8px; height: 8px; margin-right: 6px; border-radius: 2px; background: var(--text-muted); vertical-align: 0; }
.wf-kind-decision { transform: rotate(45deg) scale(.8); }
.wf-kind-call { background: var(--text-secondary); }
.wf-kind-result { background: var(--state-ok-ink); }
.wf-kind-io, .wf-kind-code { background: var(--border-strong); }
.wf-bar-cell { position: relative; width: 60%; height: 16px; }
.wf-bar { position: absolute; top: 3px; height: 10px; border-radius: 2px; background: var(--state-idle-ink); opacity: .75; }
.wf-status-ok { background: var(--state-ok-ink); }
.wf-status-failed { background: var(--state-error-ink); }
.wf-status-invalid, .wf-status-partial, .wf-status-repaired { background: var(--state-warn-ink); }
.wf-status-running { background: var(--state-info-ink); }
.wf-crit .wf-bar { outline: 2px solid var(--stage-build); outline-offset: 1px; }
.wf-took { text-align: right; width: 72px; }
.wf-hist { display: flex; align-items: flex-end; gap: 1px; height: 80px; border-bottom: var(--rule) solid var(--border-strong); }
.wf-hist-bar { flex: 1 1 0; background: var(--state-warn-ink); opacity: .7; min-width: 2px; }
.graph-side-tabs { margin: var(--sp-1) 0; }
.graph-side-tabs { display: flex; gap: 2px; border-bottom: var(--rule) solid var(--border-faint); }
.graph-side-tabs .utab { all: unset; cursor: pointer; font-size: var(--fs-caption); padding: 4px 8px; color: var(--text-secondary); border-bottom: 2px solid transparent; }
.graph-side-tabs .utab[aria-selected="true"] { color: var(--text-primary); border-bottom-color: var(--text-link); font-weight: var(--fw-bold); }
.graph-side-tabs .utab:focus-visible { outline: 2px solid var(--focus-ring); }
.graph-side-body h4 { margin: var(--sp-2) 0 var(--sp-1); font-size: var(--fs-caption); color: var(--text-secondary); }
.graph-side-body pre.code { max-height: 260px; overflow: auto; font-size: 11px; white-space: pre-wrap; overflow-wrap: anywhere; }
.graph-side-actions { display: flex; flex-wrap: wrap; gap: var(--sp-1); align-items: center; margin-top: var(--sp-2); }
.graph-side-status { margin: var(--sp-1) 0 0; min-height: 1em; }
.graph-side-vote { font-size: var(--fs-body); margin-left: 4px; }
.graph-sessions { width: 100%; border-collapse: collapse; font-size: var(--fs-caption); margin-top: var(--sp-2); }
.graph-sessions th, .graph-sessions td { text-align: left; padding: 3px 6px; border-top: var(--rule) solid var(--border-faint); }
.linklike { all: unset; cursor: pointer; color: var(--text-link); text-decoration: underline; }

/* ---------- the demo console (AI_RUNTIME_MODE=demo) ----------
   Its own accent, so a screenshot of the demo is never taken for production. */
body.demo { --demo-accent: #7c3aed; }
body.demo .sidebar { box-shadow: inset -3px 0 0 var(--demo-accent); }
body.demo .brand-mark { color: #c4b5fd; filter: none; }
.demo-banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-3);
  padding: var(--sp-2) var(--sp-4); background: var(--demo-accent, #7c3aed); color: #fff; font-size: var(--fs-body);
}
.demo-banner-sub { opacity: .85; font-size: var(--fs-caption); }
.demo-banner-reset { margin: 0 0 0 auto; }
.demo-banner-reset button {
  padding: 2px var(--sp-2); font-size: var(--fs-caption); cursor: pointer;
  background: transparent; color: #fff; border: var(--rule) solid rgba(255, 255, 255, .6); border-radius: 6px;
}
.demo-banner-reset button:hover { background: rgba(255, 255, 255, .15); }
.login-demo-creds { margin: 0 0 var(--sp-3); padding: var(--sp-2) var(--sp-3); border-radius: 6px; background: #f3effe; color: #3b1d80; font-size: var(--fs-body); }
.login-demo-creds code { font-weight: var(--fw-bold); }
.login-demo-note { color: var(--text-secondary); font-size: var(--fs-caption); margin: var(--sp-3) 0 0; }
.login-demo-enter { background: #7c3aed; border-color: #7c3aed; }
