/* akari: The Machinist's Bench. Dark violet-graphite steel under a muted lilac
   signal. See DESIGN.md for the system; this stylesheet is its implementation.

   base.css holds the shared foundation: fonts, design tokens, resets, the
   typographic and utility primitives, and the components reused across every
   page (tables, cards, stat tiles, tags, buttons, inputs, empty states). The
   stylesheet is split per page area to match the templates; this file loads
   first so the per-page files cascade over it. */

/* ---- Fonts (self-hosted, embedded in the binary) ---- */
@font-face {
  font-family: "Geist";
  src: url("../fonts/Geist-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("../fonts/Geist-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("../fonts/Geist-SemiBold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/GeistMono-Regular.woff2") format("woff2");
  font-weight: 400 450; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/GeistMono-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

/* ---- Tokens ---- */
:root {
  /* The substrate is a deep, near-neutral graphite ladder: the ground reads as a
     genuinely dark base and each panel step carries only a faint violet cast, so
     the pastel signals and text (unchanged) carry the contrast rather than a
     uniformly lilac-tinted haze. Chroma is roughly halved against the pastels and
     held flat up the ladder; see DESIGN.md for the tonal reasoning. */
  --bg: #0c0a0e;
  --surface: #18161b;
  --surface-2: #242228;
  --surface-3: #302e35;
  --border: #3e3b44;
  --border-strong: #57535f;
  --text: #e6e3f0;
  --subtext: #c5c1d6;
  --muted: #9a94ad;
  --faint: #6f6987;
  --lilac: #c6a8f2;
  --lilac-strong: #b693ea;
  --ink: #1a1320;
  --ok: #9fd6a6;
  --err: #ef9aa9;
  --warn: #f0c592;
  --info: #92cfd4;
  --msg-user: #201e26;
  --msg-assistant: #1b1b1f;

  /* Insights chart palette: eight qualitative hues for series that carry no
     inherent status meaning (models, tool categories, projects). Kept apart
     from the status colors above (ok/err/warn/info) so a chart series is
     never mistaken for a status signal. See internal/server/web/static/js/insights.js. */
  --viz-1: #c6a8f2;
  --viz-2: #88cfce;
  --viz-3: #f0bf92;
  --viz-4: #ec98b0;
  --viz-5: #a6d29e;
  --viz-6: #95c0ef;
  --viz-7: #ddc885;
  --viz-8: #a98ad4;

  /* Insights-mock token aliases: the ported insights.css/insights.js reference
     the mock's own token names verbatim (see the port brief in insights.js's
     header comment) rather than hundreds of individually renamed references. */
  --accent: var(--lilac);
  --accent-strong: var(--lilac-strong);
  --accent-ink: var(--ink);
  --font-ui: var(--sans);
  --font-mono: var(--mono);
  --radius-sm: var(--r-sm);
  --radius-md: var(--r-md);
  --radius-lg: var(--r-lg);

  --sans: "Geist", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --r-sm: 4px;
  --r-md: 6px;
  --r-lg: 10px;

  --sp-xs: 4px;
  --sp-sm: 8px;
  --sp-md: 12px;
  --sp-lg: 16px;
  --sp-xl: 24px;
  --sp-2xl: 32px;

  --dur-fast: 120ms;
  --dur-base: 180ms;
  --dur-slow: 220ms;
  --ease-settle: cubic-bezier(0.165, 0.84, 0.44, 1);
  --ease-state: cubic-bezier(0.4, 0, 0.2, 1);

  --ring: 0 0 0 2px rgba(198, 168, 242, 0.5);
  --shadow-overlay: 0 8px 24px -6px rgba(8, 6, 14, 0.6);

  --z-sticky: 10;
  --z-overlay: 40;

  --maxw: 1180px;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 0.875rem/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---- Native-app feel ----
   A single arrow cursor app-wide (no link/button pointer, no stray I-beam) and
   no text selection over chrome. The I-beam and selection return only on genuine
   text surfaces: form fields, code and tool bodies, and reading content. Author
   `*` overrides the user-agent's link/text cursors; the text surfaces below have
   higher specificity, so they win the I-beam back. */
* { cursor: default; }
body { -webkit-user-select: none; user-select: none; }
input, textarea,
.content, .thinking-body,
pre, code, .tool-body, .diff, .diff-body {
  -webkit-user-select: text; user-select: text; cursor: text;
}
::selection { background: rgba(198, 168, 242, 0.30); color: var(--text); }

/* ---- Scrollbars ----
   The OS default scrollbar is a wide grey slab that reads as foreign chrome on a
   dark instrument panel, most visibly on the session outline rail. Recast it as a
   thin track-less bar in the panel's own steel: the thumb sits low-contrast at
   rest and brightens on hover, so the rail stays quiet until the eye is on it.
   Firefox takes the two-keyword form; Chromium and Safari take the ::-webkit
   pseudo-elements. */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: 99px;
  border: 2px solid transparent; background-clip: padding-box;
  transition: background var(--dur-fast) var(--ease-state);
}
::-webkit-scrollbar-thumb:hover { background: var(--faint); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

a { color: var(--lilac); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-sm); }

h1, h2, h3 { font-weight: 600; letter-spacing: -0.01em; text-wrap: balance; }
h1 { font-size: 1.75rem; line-height: 1.15; letter-spacing: -0.02em; margin: 0; }
h2 { font-size: 1.25rem; margin: 0; }

.muted { color: var(--muted); }
.small { font-size: 0.75rem; }
.mono { font-family: var(--mono); }
.label {
  font-weight: 500; font-size: 0.6875rem; line-height: 1.2;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
code, .mono { font-family: var(--mono); }
.num, .value { font-variant-numeric: tabular-nums; }
.spacer { flex: 1; }
.inline { margin: 0; display: inline-flex; }

main { max-width: var(--maxw); margin: 0 auto; padding: var(--sp-xl); }

.page-head { display: flex; align-items: baseline; gap: var(--sp-md); margin-bottom: var(--sp-xl); }
section { margin-top: var(--sp-2xl); }
.panel-head { display: flex; align-items: baseline; gap: var(--sp-md); margin-bottom: var(--sp-md); }

/* ---- Token figures (hover/focus breakdown card) ---- */
/* A tok-cell's compact total sits in a table cell; hovering or focusing the
   cell reveals the full class breakdown in a card anchored beneath it, the
   same dotted-underline-plus-card idiom the stat tiles below use. This page
   ships no script, so the reveal has to be pure CSS: opacity and visibility
   toggled by :hover and :focus-within rather than a class swap. */
.tok-cell { position: relative; cursor: help; }
.tok-total { text-decoration: underline dotted var(--faint); text-underline-offset: 3px; }
.tok-tip {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: var(--z-overlay);
  width: max-content; max-width: 260px; min-width: 190px;
  padding: var(--sp-sm) var(--sp-md);
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: var(--r-md); box-shadow: var(--shadow-overlay);
  opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-state), transform var(--dur-fast) var(--ease-state), visibility 0s linear var(--dur-fast);
}
.tok-cell:hover .tok-tip, .tok-cell:focus-within .tok-tip {
  opacity: 1; visibility: visible; transform: none; transition-delay: 0s;
}
.tok-tip .tt-total { display: block; margin-bottom: 5px; color: var(--text); font: 450 0.75rem var(--mono); }

/* ---- Tables (the data grid) ---- */
/* Every data table sits in a .table-scroll wrapper: a wide table scrolls inside
   its own panel instead of forcing the whole page to scroll horizontally at
   narrow viewports. The wrapper is a scroll container, so it also clips
   vertically; the rule below flips the token hover card upward on bottom rows
   (where the table guarantees room above) so the card stays readable instead of
   spilling past the clipped bottom edge. Rows near the top keep the downward
   card, since a short table has no room above the row either way. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll tfoot .tok-tip,
.table-scroll tbody tr:nth-last-child(-n+2):not(:nth-child(-n+3)) .tok-tip {
  top: auto; bottom: calc(100% + 6px);
}
table.grid { width: 100%; border-collapse: collapse; }
table.grid th, table.grid td {
  text-align: left; padding: var(--sp-sm) var(--sp-md);
  border-bottom: 1px solid var(--border);
}
table.grid th {
  color: var(--muted); font-weight: 500; font-size: 0.6875rem;
  text-transform: uppercase; letter-spacing: 0.08em;
}
table.grid td { transition: background var(--dur-fast) var(--ease-state); }
table.grid tbody tr:hover td { background: var(--surface-2); }
/* The remainder footer summarizes windowed sessions beyond the capped rows, so it
   reads as a quiet summary line, not a clickable data row: muted, no hover, and a
   firmer top rule to set it off from the rows it totals. */
table.grid tfoot tr.remainder td { color: var(--muted); border-top: 2px solid var(--border); border-bottom: none; }
table.grid tfoot tr.remainder:hover td { background: none; }
/* The table.grid variants repeat the selectors at higher specificity: the grid's own
   th/td rule (table.grid th, table.grid td) sets text-align: left and would out-specify
   a bare td.num, leaving every numeric column left-aligned against the design system's
   right-aligned-numerics rule. */
td.num, th.num, table.grid td.num, table.grid th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }

/* ---- Cards ---- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-lg);
}

/* ---- Stat tiles (instrument readouts) ---- */
.stats { display: flex; flex-wrap: wrap; gap: var(--sp-md); margin-bottom: var(--sp-lg); }
.stat {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 10px var(--sp-lg); min-width: 116px;
}
.stat .label { display: block; margin-bottom: 3px; }
.stat .value { font-family: var(--mono); font-size: 1.125rem; font-variant-numeric: tabular-nums; color: var(--text); }
/* The analytics panels (overview, projects, project) fuse their readouts into a
   single instrument strip: one bordered row split by hairlines, instead of four
   detached tiles floating above the chart. The strip stays overflow-visible so the
   Tokens readout's hover tooltip can drop clear of it rather than being clipped;
   the fused rounded ends come from the first/last tile instead of a clip. */
.analytics .stats { gap: 0; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); overflow: visible; }
.analytics .stat { flex: 1; min-width: 0; border: none; border-radius: 0; }
.analytics .stat:first-child { border-top-left-radius: var(--r-md); border-bottom-left-radius: var(--r-md); }
.analytics .stat:last-child { border-top-right-radius: var(--r-md); border-bottom-right-radius: var(--r-md); }
.analytics .stat + .stat { border-left: 1px solid var(--border); }
/* The session header fuses its readouts into the same instrument strip, sticky to
   the top of the transcript. It lays out as an auto-fitting grid: one hairline-split
   row when the tiles fit, and a clean multi-row cluster when they do not (the old
   flex strip wrapped into ragged rows with missing rules). Each tile paints its own
   top and left hairline with a box-shadow, so the rules stay coherent at any column
   count; the shadows land on the container's own border line at the edges, so no
   doubled frame shows. */
.stats.sticky {
  position: sticky; top: 0; z-index: var(--z-sticky);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 0; margin: 0 0 var(--sp-lg); padding: 0;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface);
}
.stats.sticky .stat {
  min-width: 0; padding: 8px var(--sp-lg);
  border: none; border-radius: 0;
  box-shadow: -1px 0 0 var(--border), 0 -1px 0 var(--border);
}
/* The Tokens tile folds input, output, cache read, cache write, and cost into one
   gauge; the Cache tile folds hit rate, the dollars saved, and the prompt-token split
   into another; the Quality tile folds the outcome, its confidence, the score, and the
   tool-health counts into a third. All reveal their breakdown on hover or keyboard
   focus through the one .stat-tip system, the same detail the overview heatmap surfaces
   for a day. */
.tokens-stat, .cache-stat, .quality-stat, .fallback-stat { position: relative; outline: none; }
.tokens-value, .cache-value, .quality-value, .fallback-value { text-decoration: underline dotted var(--faint); text-underline-offset: 3px; }
.tokens-stat:hover, .tokens-stat:focus-visible,
.cache-stat:hover, .cache-stat:focus-visible,
.quality-stat:hover, .quality-stat:focus-visible,
.fallback-stat:hover, .fallback-stat:focus-visible { background: var(--surface-2); }
/* The Fallbacks tile renders only when a session had one, so its presence is the signal;
   the count sits in the warn band (the peach caution the sheet already uses for an
   estimated or in-progress value), never the lilac accent, keeping to the One Voice Rule. */
.fallback-value { color: var(--warn); }
/* The grade banding reads like a report card off the existing status palette (no new
   hues, per the One Voice Rule): A and B sage, C peach, D and F rose, an unscored
   session left in the default text colour so a dash never looks like a verdict. */
.quality-stat.q-good .quality-value { color: var(--ok); }
.quality-stat.q-watch .quality-value { color: var(--warn); }
.quality-stat.q-poor .quality-value { color: var(--err); }
.stat-tip {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 20;
  /* Size to the content, not to the (narrow) tile the tip is anchored to: an
     absolutely positioned box otherwise shrink-wraps against its containing
     block and force-wraps lines that fit comfortably on screen. Wrapping only
     starts once the tip would exceed a fifth of the viewport. The min-width
     floor wins over the cap on very narrow viewports, which is fine: the
     mobile media rule below repins the tip edge to edge anyway. */
  width: max-content; max-width: 20vw;
  min-width: 190px; padding: var(--sp-sm) var(--sp-md);
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: var(--r-md); box-shadow: var(--shadow-overlay);
  opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-state), transform var(--dur-fast) var(--ease-state), visibility 0s linear var(--dur-fast);
}
.tokens-stat:hover .stat-tip, .tokens-stat:focus-within .stat-tip,
.cache-stat:hover .stat-tip, .cache-stat:focus-within .stat-tip,
.quality-stat:hover .stat-tip, .quality-stat:focus-within .stat-tip,
.fallback-stat:hover .stat-tip, .fallback-stat:focus-within .stat-tip {
  opacity: 1; visibility: visible; transform: none; transition-delay: 0s;
}
.stat-tip .tt-grid, .tok-tip .tt-grid { display: grid; grid-template-columns: auto auto; gap: 1px var(--sp-md); margin: 0; font: 450 0.75rem/1.6 var(--mono); }
.stat-tip .tt-grid dt, .tok-tip .tt-grid dt { color: var(--muted); }
.stat-tip .tt-grid dd, .tok-tip .tt-grid dd { margin: 0; color: var(--subtext); text-align: right; font-variant-numeric: tabular-nums; }
.stat-tip .tt-cost, .tok-tip .tt-cost { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--border); color: var(--text); font: 450 0.75rem var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
/* tt-sub heads a second group inside a stat tooltip (the quality tile's input-hygiene
   readout below its score drivers), a muted, ruled label that keeps the two groups from
   reading as one list. */
.stat-tip .tt-sub { margin-top: 6px; padding-top: 5px; border-top: 1px solid var(--border); color: var(--muted); font: 500 0.6875rem/1.4 var(--mono); text-transform: uppercase; letter-spacing: 0.04em; }
.stat-tip .tt-sub + .tt-grid { margin-top: 4px; }
/* tt-note closes a stat tooltip with a plain muted line (the fallback tile's "plus N more"
   when the list was capped), set apart from the grids above without a heading rule. */
.stat-tip .tt-note { margin-top: 6px; color: var(--muted); font: 450 0.75rem/1.6 var(--mono); }
.value.settling { animation: settle var(--dur-slow) var(--ease-settle); }
@keyframes settle {
  0% { color: var(--lilac); text-shadow: 0 0 14px rgba(198, 168, 242, 0.75); }
  100% { color: var(--text); text-shadow: none; }
}

/* ---- Tags (machined rectangles, never pills) ---- */
.tag {
  display: inline-block; padding: 2px 7px; border-radius: var(--r-sm);
  font-size: 0.6875rem; font-weight: 500; letter-spacing: 0.03em;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--muted);
  vertical-align: middle;
}
.tag.agent { color: var(--lilac); }
.tag.public { color: var(--ok); border-color: rgba(159, 214, 166, 0.4); }
.tag.model { color: var(--subtext); font-family: var(--mono); letter-spacing: 0; }
/* Project state: standalone (no remote) reads as a caution; orphaned (folder
   gone) reads as an error. Both carry explanatory titles, so they take the same
   help cursor every other tooltip-bearing tag does. */
.tag.standalone { color: var(--warn); border-color: rgba(240, 197, 146, 0.4); cursor: help; }
.tag.orphaned { color: var(--err); border-color: rgba(239, 154, 169, 0.4); cursor: help; }
/* Data-quality caution: the session repeats a tool-call id across rows. */
.tag.warn { color: var(--warn); border-color: rgba(240, 197, 146, 0.4); cursor: help; }
/* Feed-row quality chips: the grade reads in the report-card banding the Quality tile
   uses (A/B sage, C peach, D/F rose), and the outcome flags a session that did not
   complete. The grade chip is mono and tight so the single letter reads as a mark. */
.tag.grade { font-family: var(--mono); letter-spacing: 0; cursor: help; }
.tag.grade.q-good { color: var(--ok); border-color: rgba(159, 214, 166, 0.4); }
.tag.grade.q-watch { color: var(--warn); border-color: rgba(240, 197, 146, 0.4); }
.tag.grade.q-poor { color: var(--err); border-color: rgba(239, 154, 169, 0.4); }
.tag.outcome { color: var(--err); border-color: rgba(239, 154, 169, 0.4); cursor: help; }
/* A tag that is also a link: the public chip opens the published page in a new
   tab, so it lays out as inline-flex to seat the external-link glyph and lifts
   its fill on hover instead of underlining (an underline under an 11px chip
   reads as clutter). The glyph is the clickability cue; hover firms the sage
   border and brings the glyph to full strength. */
a.tag { text-decoration: none; }
a.tag:hover { text-decoration: none; }
.tag-link { display: inline-flex; align-items: center; gap: 4px; transition: background var(--dur-fast) var(--ease-state), border-color var(--dur-fast) var(--ease-state); }
.tag-link .ext-glyph { flex: none; opacity: 0.65; transition: opacity var(--dur-fast) var(--ease-state); }
.tag-link:hover { background: var(--surface-3); }
.tag-link:hover .ext-glyph { opacity: 1; }
.tag.public.tag-link:hover { border-color: rgba(159, 214, 166, 0.7); }
/* The publish-state cluster (the public chip and its make-private control) as it
   rides inline in a page header. Purely a flex seat: any strip spacing belongs
   to the surface that hosts it (see .overview-public-badge in overview.css). */
.publicity { display: inline-flex; align-items: center; gap: var(--sp-sm); }
/* Fan-out chip: a root session's whole-work-item rollup (how many subagents it spun
   out and the total cost including all of them). It reads as structure, not a verdict,
   so it takes the neutral subtext color and a mono figure rather than a status hue. */
.tag.fanout { color: var(--subtext); font-family: var(--mono); letter-spacing: 0; cursor: help; }

/* ---- Buttons ---- */
button, .btn {
  font: 500 0.875rem/1 var(--sans);
  background: var(--lilac); color: var(--ink);
  border: none; border-radius: var(--r-sm); padding: 8px 14px;
  transition: background var(--dur-fast) var(--ease-state), transform var(--dur-fast) var(--ease-state);
}
button:hover, .btn:hover { background: var(--lilac-strong); }
button:active, .btn:active { transform: translateY(1px); }
button.secondary { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }
button.secondary:hover { background: var(--surface-3); }
button.ghost { background: transparent; color: var(--subtext); }
button.ghost:hover { background: var(--surface-2); }
button.danger { background: var(--surface-2); color: var(--err); border: 1px solid rgba(239, 154, 169, 0.4); }
button.danger:hover { background: var(--err); color: var(--ink); border-color: var(--err); }
button.small, .btn.small { padding: 5px 10px; font-size: 0.8125rem; }
/* The icon-only copy control that pairs with a visible share URL (see copyLink
   in publicity.templ). It flips to the sage check while a copy settles; both
   glyphs occupy the same box, so the swap never shifts layout. */
.copy-link { display: inline-flex; align-items: center; justify-content: center; padding: 4px 6px; line-height: 0; color: var(--muted); }
.copy-link:hover { color: var(--subtext); }
.copy-link .glyph-check { display: none; color: var(--ok); }
.copy-link.copied .glyph-copy { display: none; }
.copy-link.copied .glyph-check { display: inline; }

/* ---- Segmented controls (density / metric) ---- */
.seg-group, .chart-toolbar { display: inline-flex; gap: 0; border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.seg {
  background: var(--surface); color: var(--muted);
  border: none; border-radius: 0; padding: 6px 12px; font-weight: 500; font-size: 0.8125rem;
  transition: background var(--dur-fast) var(--ease-state), color var(--dur-fast) var(--ease-state);
}
.seg + .seg { border-left: 1px solid var(--border); }
.seg:hover { background: var(--surface-2); color: var(--subtext); }
.seg.active { background: var(--surface-3); color: var(--lilac); }

/* ---- Inputs ---- */
input, select, textarea {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 7px 10px; font: inherit;
  transition: border-color var(--dur-fast) var(--ease-state), box-shadow var(--dur-fast) var(--ease-state);
}
input::placeholder { color: var(--muted); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--lilac); box-shadow: var(--ring); }
.filters { display: flex; gap: var(--sp-sm); flex-wrap: wrap; align-items: center; margin-bottom: var(--sp-md); }
.filters .grow, input.grow { min-width: 320px; flex: 1; }

/* Shared reveal animation: the "shown once" cards (account) fade in, and the
   inspector overlay (session) reuses it for its backdrop. */
@keyframes fadein { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* ---- Empty states ---- */
.empty { color: var(--muted); padding: var(--sp-xl); text-align: center; border: 1px dashed var(--border); border-radius: var(--r-md); }
.empty.big { padding: 64px var(--sp-xl); }
.empty.big h1 { font-family: var(--mono); color: var(--lilac); }

/* ---- Responsive ----
   The shared breakpoint scale, used across every stylesheet:
     900px  single-column layouts and tighter page padding.
     640px  phone: stacked rows, folded toolbars, full-screen modal, static strips.
   session.css additionally narrows the outline rail at 1100px. Keep new rules on
   these two stops rather than inventing per-page widths. */
@media (max-width: 900px) {
  main { padding: var(--sp-lg); }
  /* The stat tips anchor left on their tile with a 190px floor, so a tile in the
     right half of a narrow strip pushes its card past the viewport; the card is
     visibility-hidden rather than display-none (the hide transition needs it in
     layout), so even unhovered it widens document.scrollWidth and the whole page
     scrolls horizontally. Re-anchor the card to the strip itself (tiles go
     static, the strip is the positioned ancestor) and span it edge to edge, so
     the breakdown drops below the strip like a panel and can never leave the
     viewport. The sticky session strip is already a containing block, so it
     keeps position: sticky. */
  .stats:not(.sticky) { position: relative; }
  .tokens-stat, .cache-stat, .quality-stat, .fallback-stat { position: static; }
  .stat-tip { left: 0; right: 0; min-width: 0; width: auto; max-width: none; }
}
@media (max-width: 640px) {
  main { padding: var(--sp-lg) var(--sp-md); }
  /* Comfortable tap heights on the form controls, keyed to the viewport as well
     as the coarse-pointer block below: device emulation and small desktop
     windows should read like the phone layout they show. 16px field text also
     stops iOS zooming the page when a field takes focus. Dense instrument
     chrome (stamps, tags) keeps its compact scale. */
  select, textarea, input:not([type="checkbox"]):not([type="radio"]) { min-height: 40px; font-size: 1rem; }
  button:not(.stamp):not(.seg):not(.insp-close), .btn { min-height: 40px; }
  .seg { padding: 9px 12px; }
  /* The session strip stops being sticky on a phone: two or three gauge rows
     pinned to the top would cover half the transcript. It reads once at the top
     instead, and the flow ribbon keeps the sense of position. relative (not
     static) so the stat tooltips keep the strip as their containing block. */
  .stats.sticky { position: relative; top: auto; }
  /* Search and filter fields take the full row instead of a 320px floor that
     forces a sideways layout. */
  .filters .grow, input.grow { min-width: 0; width: 100%; }
}

/* ---- Touch ----
   Coarse-pointer ergonomics, independent of viewport: comfortable tap heights on
   the controls a phone reader actually drives, and 16px text in fields so iOS
   stops zooming the page on focus. Dense instrument chrome (tags, stamps, chips)
   stays compact; width plus spacing carries their tap area. */
@media (pointer: coarse) {
  button, .btn { min-height: 40px; }
  button.small, .btn.small { min-height: 36px; }
  input, select, textarea { min-height: 40px; font-size: 1rem; }
  .seg { padding: 9px 14px; }
  .stamp, button.stamp { min-height: 0; padding: 4px 10px; }
}

/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
  .bar-fill { transition: none; }
}
