/* On the Bench: AI tool benchmark results site. Dark-first, Inter, no framework. */

/* woff2 first, ttf second: the same four faces were 1.6 MB of TrueType, ~780 KB on the wire and the
   slowest thing on the page. woff2 is ~140 KB each. The ttf stays as the fallback for anything that
   cannot read woff2 - it costs nothing, since a browser only fetches the first format it supports. */
@font-face { font-family: "Inter"; src: url("fonts/Inter-Regular.woff2") format("woff2"), url("fonts/Inter-Regular.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Inter"; src: url("fonts/Inter-Medium.woff2") format("woff2"), url("fonts/Inter-Medium.ttf") format("truetype"); font-weight: 500; font-display: swap; }
@font-face { font-family: "Inter"; src: url("fonts/Inter-SemiBold.woff2") format("woff2"), url("fonts/Inter-SemiBold.ttf") format("truetype"); font-weight: 600; font-display: swap; }
@font-face { font-family: "Inter"; src: url("fonts/Inter-Bold.woff2") format("woff2"), url("fonts/Inter-Bold.ttf") format("truetype"); font-weight: 700; font-display: swap; }

/* Theme palettes. Every colour used across the site is a variable here, so a
   single [data-theme] flip on <html> restyles every surface, including the
   canvas charts (they re-read --fg-dim / --grid via chartTheme() on toggle).
   Defaults below are the dark palette; [data-theme="light"] overrides them.
   An inline script in <head> sets data-theme before first paint (no flash),
   defaulting to the OS prefers-color-scheme when nothing is stored. */
:root, [data-theme="dark"] {
  color-scheme: dark;
  --bg: #0d1117;
  --bg-raise: #161b22;
  --bg-hover: #1c232d;
  --border: #2d333b;
  --fg: #e6edf3;
  --fg-dim: #9aa4b2;
  --fg-mute: #626c7a;
  --accent: #4cc38a;
  --accent-fg: #08130d;
  --red: #e5534b;
  --green: #2ea55f;
  --grey: #4b5563;
  --link: #6cb6ff;
  --chip-fg: #fff;
  --on-cell-fg: #fff;
  --grid: rgba(154,164,178,.18);
  --shadow-strong: rgba(0,0,0,.35);
  --shadow-soft: rgba(0,0,0,.3);
  --overlay: rgba(0,0,0,.5);
  --best: rgba(108,182,255,.14);
  --sorted-col-bg: rgba(76,195,138,.08);
  --img-bg: #fff;
}
[data-theme="light"] {
  color-scheme: light;
  --bg: #f6f8fa;
  --bg-raise: #ffffff;
  --bg-hover: #eef1f4;
  --border: #d0d7de;
  --fg: #1f2328;
  --fg-dim: #57606a;
  --fg-mute: #8b949e;
  --accent: #1a7f4b;
  --accent-fg: #ffffff;
  --red: #c1362f;
  --green: #1a7f4b;
  --grey: #8b96a5;
  --link: #0969da;
  --chip-fg: #fff;
  --on-cell-fg: #fff;
  --grid: rgba(87,96,106,.20);
  --shadow-strong: rgba(31,35,40,.18);
  --shadow-soft: rgba(31,35,40,.12);
  --overlay: rgba(31,35,40,.4);
  --best: rgba(9,105,218,.12);
  --sorted-col-bg: rgba(26,127,75,.07);
  --img-bg: #fff;
}

* { box-sizing: border-box; }
html { font-size: 15px; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
}
.wrap { max-width: 1180px; margin: 0 auto; padding: 0 1.25rem; }
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }
code { background: var(--bg-raise); border: 1px solid var(--border); border-radius: 4px; padding: .1em .35em; font-size: .88em; }
.hidden { display: none !important; }
.muted { color: var(--fg-mute); }
.fineprint { color: var(--fg-mute); font-size: .82rem; margin-top: 1rem; }
.lede { color: var(--fg-dim); max-width: 70ch; }
#table-caption { line-height: 1.55; margin: .3rem 0 .9rem; }

/* header: brand mark + wordmark, an accent hairline up top, a designed hierarchy.
   Subtle on purpose: it should read intentional, not redecorated. */
.site-header { position: relative; border-bottom: 1px solid var(--border); background: var(--bg-raise); padding-top: 2rem; }
.site-header::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: linear-gradient(90deg, var(--accent), var(--link) 45%, transparent 80%);
  opacity: .65;
}
.site-header h1.brand { margin: 0 0 .45rem; font-size: 1.55rem; font-weight: 700; letter-spacing: -.01em; }
.brand a { display: inline-flex; align-items: center; gap: .55rem; color: var(--fg); text-decoration: none; }
.brand a:hover { text-decoration: none; }
.brand a:hover .brand-name { color: var(--accent); }
.brand-mark { display: block; width: 28px; height: 28px; border-radius: 7px; }
.brand-name { line-height: 1; transition: color .12s ease; }
.brand-sub { color: var(--fg-mute); font-size: .9rem; font-weight: 500; margin-left: .2rem; letter-spacing: 0; align-self: flex-end; padding-bottom: .1rem; }
.tagline { margin: 0 0 1rem; color: var(--fg-dim); max-width: 72ch; font-size: .95rem; }
/* category row: the benchmarked thing (gateways today), above the view tabs */
.catnav { display: flex; align-items: center; gap: .55rem; margin: 0 0 .7rem; }
.catnav-label { color: var(--fg-mute); font-size: .74rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; }
.catnav .cat {
  display: inline-block; padding: .18rem .85rem; border: 1px solid var(--border); border-radius: 999px;
  color: var(--fg-dim); font-size: .84rem; font-weight: 600; text-decoration: none;
}
.catnav .cat:hover { color: var(--fg); text-decoration: none; }
.catnav .cat.active { background: var(--accent); border-color: transparent; color: var(--accent-fg); }
.tabs { display: flex; gap: .25rem; }
.tab {
  appearance: none; background: none; border: none; border-bottom: 2px solid transparent;
  color: var(--fg-dim); font: inherit; font-weight: 500; padding: .55rem .9rem; cursor: pointer;
  display: inline-block; text-decoration: none;
}
.tab:hover { color: var(--fg); text-decoration: none; }
.tab.active { color: var(--fg); border-bottom-color: var(--accent); }

main { padding: 1.6rem 0 3rem; }

/* ---- home landing page (site root) ------------------------------------------
   The header's category row, tab bar and category tagline belong to the category
   view; on home (body.home, set by showView) they hide and the hero carries the
   brand treatment instead. */
body.home .catnav, body.home .tabs, body.home .tagline { display: none; }
body.home .site-header { padding-bottom: 1.4rem; }
/* The footer duplicates the home body (neutrality + repo + reproduce); it earns its place on the
   DATA views (hardware stamp + reproduce command), so hide it only on home. */
body.home .site-footer { display: none; }
.hero { padding: 2.6rem 0 .4rem; }
.hero-word { margin: 0 0 .5rem; font-size: 1.75rem; font-weight: 700; letter-spacing: -.02em; line-height: 1.2; max-width: 24ch; }
.hero-tag { margin: 0; font-size: 1.08rem; color: var(--fg-dim); max-width: 46ch; }
.home-pitch { margin-top: 1.3rem; max-width: 62ch; color: var(--fg-dim); }
.home-pitch p { margin: .35rem 0; }
/* Neutrality disclosure: same accent-bar treatment as the Method essay's standfirst. */
.home-neutrality {
  max-width: 62ch; margin: 1.3rem 0 0; padding: .7rem .9rem; font-size: .9rem;
  border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 6px; background: var(--bg-raise); color: var(--fg);
}
.home-cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.1rem; margin-top: 1.8rem;
}
.home-card {
  display: flex; flex-direction: column; gap: .35rem;
  background: var(--bg-raise); border: 1px solid var(--border); border-radius: 10px;
  padding: 1.1rem 1.2rem 1rem; color: var(--fg); text-decoration: none;
}
a.home-card:hover { border-color: var(--accent); text-decoration: none; }
.home-card h3 { margin: 0; font-size: 1.05rem; }
.home-card p { margin: 0; color: var(--fg-dim); font-size: .9rem; }
.home-card .card-cta { margin-top: .5rem; color: var(--accent); font-size: .86rem; font-weight: 600; }
.home-card.soon { opacity: .55; }
.home-links { margin-top: 2rem; }
.view h2 { font-size: 1.2rem; margin: .2rem 0 .6rem; }

/* controls */
/* Head band: caption text left, search + count stacked top-right in the same
   vertical space the note already occupies — no dedicated search row. */
.table-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.4rem; margin-top: 1rem; }
.table-head #table-caption { flex: 1 1 auto; margin: 0 0 .9rem; }
.controls { display: flex; flex-wrap: wrap; gap: 1.2rem; align-items: center; margin-top: 1.4rem; margin-bottom: .9rem; }
.table-head .controls { flex: 0 0 auto; flex-direction: column; align-items: flex-end; gap: .5rem; margin: 0; }
.table-head .count { margin: 0; }
/* `.control-group` survives only as the hook .filters uses to make each axis display:contents; the flex box
   it used to be is gone with the two ragged rows, and re-declaring it here would be a layout that never wins. */
.toggle { color: var(--fg-dim); font-size: .86rem; display: inline-flex; gap: .35rem; align-items: center; cursor: pointer; }
.toggle input { accent-color: var(--accent); }
#search {
  font: inherit; font-size: .86rem; color: var(--fg);
  background: var(--bg-raise); border: 1px solid var(--border); border-radius: 6px;
  padding: .35rem .6rem; min-width: 12rem;
}
#search:focus { outline: 1px solid var(--accent); }
.count { font-size: .82rem; margin-left: auto; white-space: nowrap; }

/* results table */
.table-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-raise); }
table { border-collapse: collapse; width: 100%; font-size: .86rem; }
/* THE COLUMN GEOMETRY IS DECLARED, NOT DISCOVERED. The owner: "changing filters shouldn't change column
   widths, just an annoyance." Under auto layout a column is as wide as its widest RENDERED cell, so clicking
   a bound chip - which swaps 20,119 for 20,389, moves which column is highlighted, and can flip a row between
   a number and "no rung held this tail" - re-solved the whole grid and every column jumped. Measured across
   filter combos, all four table views drifted at all three widths.
   `fixed` makes the browser take the widths from the <colgroup> renderTable emits (app.js COL_WIDTHS) and
   never consult a cell, so the geometry is a property of the COLUMN SET - which does not change between
   filters - rather than of the rows currently passing through it. The two halves are one mechanism: without
   the colgroup, `fixed` would divide the width equally and make the checkbox as wide as the gateway names.
   It also fixes the complaint from the other side ("column widths - Tested on is huge (cos of 1 large
   openai > cohere)"): a column can no longer be sized by its longest value in one mode. */
#results-table { table-layout: fixed; }
/* A DECLARED WIDTH IS ONLY HONOURED IF THE CONTENT CAN LIVE INSIDE IT. Values keep nowrap (a wrapped number
   reads as two numbers) but the sub-lines under them are prose, and prose wraps: "no rung held this tail" is
   wider than any rate above it, so left nowrap it would be the thing sizing a numeric column - the same
   defect, one element down. */
thead th {
  position: sticky; top: 0; background: var(--bg-raise); color: var(--fg-dim);
  font-weight: 600; text-align: right; padding: .6rem .55rem;
  border-bottom: 1px solid var(--border); cursor: pointer; user-select: none;
}
/* HEADERS WRAP, NUMBERS DO NOT.
   A column is as wide as its widest cell, and on the metric tabs that is always the header:
   "Added TTFT p50 (µs)" is far longer than any value under it, so nowrap headers pushed the last
   column off the right edge and the whole table scrolled. Letting the header take two short lines
   costs one row of height once and buys back the width every row. The values keep nowrap - a
   wrapped number reads as two numbers. max-width stops a long single word from claiming a whole
   column on its own. */
/* A HEADER MAY WRAP BETWEEN WORDS AND MUST NEVER SPLIT ONE.
   `overflow-wrap: anywhere` was here to stop one long word claiming a whole column, and instead it let
   "Recovered @30 s" render as "Recovere / d @30 s" once the memory table squeezed that column to 73px - a
   header that looks like a rendering fault, which is worse than a wide column. keep-all pins breaking to the
   spaces; max-width still caps the column, and a hypothetical single unbreakable word now overflows its cap
   (visible, ugly, honest) rather than being cut in half. */
#results-table thead th { white-space: normal; overflow-wrap: normal; word-break: keep-all; max-width: 7.5rem; }
/* EVERY nth-child RULE FOR THE STICKY IDENTITY COLUMNS IS PINNED TO thead tr:first-child.
   The Frontier tab's head is now TWO rows (a spanning "Req/s · 99% of requests under:" group over six
   bound sub-headers), and in the second row nth-child(1) is the 1 ms column, not the checkbox. Unscoped,
   these rules gave that bound column position:sticky, left:0 and width:2.4rem, which shoved the sub-header
   strip out of alignment with the body underneath it. The identity columns only ever live in the first row -
   they carry rowspan="2" - so first-child is the exact and only correct scope. */
#results-table thead tr:first-child th:nth-child(1), #results-table thead tr:first-child th:nth-child(2) { white-space: nowrap; max-width: none; }
thead th:first-child, tbody td:first-child { text-align: left; }
/* Lock ONLY the identity of each row (checkbox + Gateway name) while the metric
   columns, Class and Lang included, scroll horizontally. Scoped to the results
   table only (not the matrix grid or the compare table, which share
   .table-scroll). Fixed widths give the Gateway column a stable left offset
   equal to the checkbox width; the inset border is the divider the scrolling
   columns slide under. Applies at every viewport width. */
#results-table thead tr:first-child th:nth-child(1), #results-table tbody td:nth-child(1),
#results-table thead tr:first-child th:nth-child(2), #results-table tbody td:nth-child(2) { position: sticky; z-index: 1; background: var(--bg-raise); }
#results-table thead tr:first-child th:nth-child(1), #results-table thead tr:first-child th:nth-child(2) { z-index: 3; }
/* Widths must be >= the widest content in each column (auto table-layout would otherwise
   grow the real column past these and the left offset would drift). min-width pins them. */
/* Slim padding keeps the checkbox column's real width at its declared 2.4rem
   (content + default padding would overflow it and drift the Gateway column's
   sticky left offset by the difference). */
#results-table thead tr:first-child th:nth-child(1), #results-table tbody td:nth-child(1) { left: 0; width: 2.4rem; min-width: 2.4rem; padding-left: .4rem; padding-right: .4rem; }
#results-table thead tr:first-child th:nth-child(2), #results-table tbody td:nth-child(2) { left: 2.4rem; width: 11rem; min-width: 11rem; box-shadow: inset -1px 0 0 var(--border); }
#results-table tbody tr:hover td:nth-child(1), #results-table tbody tr:hover td:nth-child(2) { background: var(--bg-hover); }
/* Even, full-width matrix columns: fixed layout so the six upstream columns share the row
   equally instead of clumping by header-text width. */
.matrix-table table { table-layout: fixed; width: 100%; }
.matrix-table th.axis, .matrix-table td.name { width: 8.5rem; }
.matrix-table th:not(.axis), .matrix-table td:not(.name) { text-align: center; }
thead th.sorted { color: var(--fg); }
thead th .dir { color: var(--accent); }
/* Sorted column gets a faint tint so the eye tracks which column ranks the table. */
thead th.sorted, tbody td.sorted-col { background: var(--sorted-col-bg); }
/* keep the two sticky left columns opaque even when they are the sorted one */
#results-table tbody td.sorted-col:nth-child(1),
#results-table tbody td.sorted-col:nth-child(2) { background: var(--sorted-col-bg); }
tbody td { padding: .5rem .55rem; text-align: right; border-bottom: 1px solid var(--border); white-space: nowrap; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: var(--bg-hover); }
td.na { color: var(--fg-mute); font-size: .78rem; }
td.name a { color: var(--fg); font-weight: 600; }
.lang-chip {
  display: inline-block; padding: .05rem .55rem; border-radius: 999px;
  color: var(--chip-fg); font-size: .74rem; font-weight: 600;
}
.cls-chip {
  display: inline-block; padding: .05rem .55rem; border-radius: 999px;
  border: 1px solid var(--border); color: var(--fg-dim); font-size: .74rem; font-weight: 500;
  white-space: nowrap;
}
/* Per-gateway freshness signal (matrix-sole-source: each gateway is measured + published
   independently, so the board carries mixed per-gateway ages — surfaced, not hidden). */
.row-measured { margin-top: .15rem; }
.measured-at { font-size: .7rem; color: var(--fg-mute); }
.measured-at.stale { color: var(--fg-mute); }
.drawer-measured { margin-top: .35rem; font-size: .74rem; color: var(--fg-mute); }
.stale-pill {
  display: inline-block; margin-left: .3rem; padding: .02rem .4rem; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg); color: var(--fg-mute);
  font-size: .66rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  vertical-align: middle;
}
td.sel { width: 1.6rem; }
td.sel input { accent-color: var(--accent); cursor: pointer; }
thead th.nosort { cursor: default; }
/* THE SPANNING GROUP HEADER over the Frontier tab's six per-bound columns. Centred over its span, with a
   hairline under it so the eye reads "these six belong together" rather than "one very wide column".
   It is `top: 0` and its sub-header row sticks BELOW it (the sub row's own offset is its height), so both
   rows survive a vertical scroll of a fourteen-row board. */
thead th.colgroup {
  text-align: center; font-weight: 600; color: var(--fg-dim); letter-spacing: .01em;
  border-bottom: 1px solid var(--border); white-space: nowrap; max-width: none;
}
#results-table thead tr.subhead th { top: 2.2rem; font-weight: 500; }
tbody tr { cursor: pointer; }

/* Gateways overview roster: a neutral roster, not a ranked perf table. Sortable by any column,
   defaulting to name A→Z; rows open nothing; no sorted-column tint. */
#gateways-table thead th, #gateways-table tbody td { text-align: left; }
#gateways-table thead th.stars, #gateways-table tbody td.stars { text-align: right; }
#gateways-table thead th.lastrun, #gateways-table tbody td.lastrun { white-space: nowrap; }
/* Run-mode mark (docker vs native binary) that prefixes the last-benchmarked date. Muted, so it
   reads as quiet context next to the date, not a badge competing with it. */
#gateways-table td.lastrun .runmode { margin-right: .45em; color: var(--fg-mute); cursor: help; }
.rm-ico { width: 13px; height: 13px; display: inline-block; vertical-align: -2px; }
#gateways-table thead th[data-sort] { cursor: pointer; user-select: none; }
/* Active-sort arrow: ▲ ascending / ▼ descending, appended after the header label. */
#gateways-table thead th[data-dir="asc"]::after { content: " \25B2"; font-size: .7em; color: var(--fg-mute); }
#gateways-table thead th[data-dir="desc"]::after { content: " \25BC"; font-size: .7em; color: var(--fg-mute); }
#gateways-table tbody tr { cursor: pointer; }
/* Contributors cell: outside people who submitted/maintain an entrant. A first-party row renders a
   muted dash; a credited row shows @handle chips linking to GitHub. The link colour is the normal
   accent so it reads as "click me", and it stops the row-click so the profile opens instead of the
   drawer. */
#gateways-table td.contrib { color: var(--fg-dim); white-space: nowrap; }
#gateways-table td.contrib .contrib-link { color: var(--accent); text-decoration: none; }
#gateways-table td.contrib a.contrib-link:hover { text-decoration: underline; }

/* drawer (per-gateway slide-over) */
.backdrop { position: fixed; inset: 0; background: var(--overlay); z-index: 60; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(560px, 94vw); z-index: 61;
  background: var(--bg-raise); border-left: 1px solid var(--border);
  overflow-y: auto; padding: 1.1rem 1.3rem 2rem; font-size: .88rem;
  box-shadow: -12px 0 32px var(--shadow-strong);
}
.close {
  position: absolute; top: .7rem; right: .8rem; appearance: none; border: none; background: none;
  color: var(--fg-mute); font: inherit; font-size: 1rem; cursor: pointer; z-index: 2;
}
.close:hover { color: var(--fg); }
.drawer-head h3 { margin: 0 0 .4rem; font-size: 1.15rem; }
.drawer-head h3 a { color: var(--fg); }
.drawer-head .chips { display: flex; gap: .4rem; margin-bottom: .5rem; }
.stamp { font-size: .78rem; margin: .3rem 0 0; }
.drawer-lane { border-top: 1px solid var(--border); margin-top: .9rem; padding-top: .7rem; }
.drawer-lane h4 { margin: 0 0 .45rem; font-size: .92rem; }
/* one-line disclosure of WHICH path a lane's canonical numbers were measured on */
.lane-note { font-size: .78rem; margin: 0 0 .4rem; }
.cmp-table td.lane-note { font-size: .74rem; font-weight: 400; }
/* recovery sparkline: an inline idle→peak→recovery RSS curve under the memory lane's numbers */
.rss-spark { margin: .5rem 0 .2rem; }
.rss-spark svg { display: block; max-width: 100%; color: var(--accent, currentColor); }
.rss-spark .stamp { margin-top: .15rem; }
.drawer-lane dl { margin: 0; }
.drawer-lane dl > div { display: flex; justify-content: space-between; gap: 1rem; padding: .16rem 0; }
.drawer-lane dt { color: var(--fg-dim); }
.drawer-lane dd { margin: 0; font-weight: 600; }
.matrix-list { list-style: none; margin: 0; padding: 0; }
.matrix-list li { margin-bottom: .4rem; }
.matrix-list .cell { width: .9rem; height: .9rem; vertical-align: -2px; cursor: default; }
.matrix-list .cell.na { background: var(--bg-hover); border: 1px solid var(--border); }
.matrix-list .evidence { font-size: .78rem; margin-left: 1.3rem; }
/* drawer: folded multi-line diagnostic (first line shows; the rest opens on demand) */
.drawer-lane details.evidence-fold { margin: .3rem 0 .2rem; font-size: .78rem; }
.drawer-lane details.evidence-fold summary { cursor: pointer; color: var(--fg-mute); }
.drawer-lane details.evidence-fold pre { white-space: pre-wrap; word-break: break-word; margin: .4rem 0 0; }
.matrix-list pre {
  margin: .25rem 0 0 1.3rem; padding: .45rem; background: var(--bg); border: 1px solid var(--border);
  border-radius: 6px; white-space: pre-wrap; word-break: break-all; font-size: .72rem;
  color: var(--fg-dim); max-height: 8rem; overflow: auto;
}

/* OOTB config artifact: monospace, scrollable, copy-friendly */
.config-block { position: relative; }
.config-block .config-pre {
  margin: 0; padding: .55rem .6rem; background: var(--bg); border: 1px solid var(--border);
  border-radius: 6px; white-space: pre; overflow: auto; max-height: 22rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .74rem;
  line-height: 1.5; color: var(--fg-dim); tab-size: 2;
}
.config-block .config-copy {
  position: absolute; top: .4rem; right: .4rem; z-index: 1; font: inherit; font-size: .72rem;
  font-weight: 600; cursor: pointer; color: var(--fg-dim); background: var(--bg-raise);
  border: 1px solid var(--border); border-radius: 5px; padding: .12rem .55rem;
}
.config-block .config-copy:hover { color: var(--fg); border-color: var(--fg-mute); }
.config-correct { font-size: .74rem; margin: .45rem 0 0; }
/* Download results button: the config-copy look (font/border/weight) but in normal flow (no <pre> to
   anchor an absolute button against), so it sits as a plain inline-block control in the Results lane. */
.results-download {
  display: inline-block; font: inherit; font-size: .72rem; font-weight: 600; cursor: pointer;
  color: var(--fg-dim); background: var(--bg-raise); border: 1px solid var(--border);
  border-radius: 5px; padding: .22rem .6rem;
}
.results-download:hover { color: var(--fg); border-color: var(--fg-mute); }

/* sweep charts */
.sweeps { display: flex; flex-direction: column; gap: .9rem; }
.sweeps figure.sweep { margin: 0; }
.sweeps figcaption { color: var(--fg-dim); font-size: .8rem; margin-bottom: .25rem; }
.sweeps canvas { width: 100%; height: auto; display: block; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; }

/* compare bar + panel */
.compare-bar {
  position: sticky; bottom: 0; z-index: 40; display: flex; justify-content: space-between;
  align-items: center; gap: 1rem; max-width: 1180px; margin: 0 auto; padding: .6rem 1.1rem;
  background: var(--bg-raise); border: 1px solid var(--border); border-radius: 8px 8px 0 0;
  font-size: .86rem; box-shadow: 0 -8px 24px var(--shadow-soft);
}
.compare-bar button {
  font: inherit; font-size: .82rem; font-weight: 600; cursor: pointer;
  border: 1px solid transparent; border-radius: 6px; padding: .3rem .9rem;
  background: var(--accent); color: var(--accent-fg);
}
.compare-bar button:disabled { opacity: .45; cursor: default; }
.compare-bar button.ghost { background: none; border-color: var(--border); color: var(--fg-dim); }
.compare-panel {
  position: fixed; inset: 0; z-index: 70; background: var(--overlay);
  overflow-y: auto; padding: 3vh 1rem;
}
.compare-inner {
  position: relative; max-width: 880px; margin: 0 auto; background: var(--bg-raise);
  border: 1px solid var(--border); border-radius: 10px; padding: 1.1rem 1.4rem 1.6rem;
}
.compare-inner h2 { margin: 0 0 .8rem; font-size: 1.15rem; }
.cmp-table td, .cmp-table th { text-align: right; }
.cmp-table td.metric, .cmp-table th:first-child { text-align: left; color: var(--fg-dim); }
.cmp-table .lane-row td { font-weight: 600; color: var(--fg); background: var(--bg-hover); text-align: left; }
.cmp-table td.best { background: var(--best); font-weight: 700; }
.cmp-table .dot { display: inline-block; width: .6rem; height: .6rem; border-radius: 50%; margin-right: .35rem; }
#cmp-sweeps { margin-top: 1rem; }

/* matrix */
#matrix-grid { overflow-x: auto; }
.matrix-gw { margin-bottom: 1.4rem; }
.matrix-gw-head { display: flex; align-items: baseline; gap: .8rem; margin-bottom: .4rem; }
.matrix-gw-head h3 { margin: 0; font-size: 1rem; }
.matrix-gw-head .pass-count { color: var(--green); }
.matrix-table { border: 1px solid var(--border); border-radius: 8px; background: var(--bg-raise); }
.matrix-table th { cursor: default; }
.matrix-table th.axis { font-weight: 400; color: var(--fg-dim); font-size: .74rem; }
.matrix-table td.na { color: var(--fg-dim); font-size: .74rem; }
.cell {
  display: inline-block; width: 1.1rem; height: 1.1rem; border-radius: 3px; cursor: pointer;
}
.cell.served { background: var(--green); }
.cell.failed { background: var(--red); }
.cell.unprobed { background: var(--grey); }
/* not configurable: neutral, visibly distinct from both a red fail and the grey unprobed_auth */
.cell.notconf { background: var(--bg-hover); border: 1px solid var(--border); }
/* not verified: the harness could not get the gateway serving under this egress config; a dashed
   grey border reads as "we could not test this", visibly distinct from the solid "not declared". */
.cell.unverified { background: var(--bg-hover); border: 1px dashed var(--fg-dim); }
/* untestable: the gateway supports the pair in production but pins the real cloud host (no
   base-URL override), so our mock cannot stand in - a rig limit, dotted border, never a red. */
.cell.untestable { background: var(--bg-hover); border: 1px dotted var(--grey); }
/* diagonal cells (same dialect in and out, no translation required): subtle ring */
.cell.diag { outline: 1px solid var(--fg-dim); outline-offset: 1px; }
/* compact cell legend: one line of colored squares + short labels, mirrors the cell classes */
.matrix-legend {
  list-style: none; margin: .2rem 0 .1rem; padding: 0;
  display: flex; flex-wrap: wrap; gap: .3rem 1rem; font-size: .8rem; color: var(--fg-dim);
}
.matrix-legend li { display: flex; align-items: center; gap: .4rem; }
.matrix-legend .lg { display: inline-block; width: .9rem; height: .9rem; border-radius: 3px; }
.matrix-legend .lg-pass { background: var(--green); }
.matrix-legend .lg-notconf { background: var(--bg-hover); border: 1px solid var(--border); }
.matrix-legend .lg-unverified { background: var(--bg-hover); border: 1px dashed var(--fg-dim); }
.matrix-legend .lg-untestable { background: var(--bg-hover); border: 1px dotted var(--grey); }
.matrix-legend .lg-diag { background: var(--bg-hover); outline: 1px solid var(--fg-dim); outline-offset: 1px; }
.matrix-legend-note { font-size: .78rem; margin: 0 0 .8rem; }
.matrix-detail {
  margin-top: 1rem; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg-raise); padding: .9rem 1.1rem; font-size: .86rem;
}
.matrix-detail h4 { margin: 0 0 .4rem; }
.matrix-detail pre {
  margin: .5rem 0 0; padding: .6rem; background: var(--bg); border-radius: 6px;
  border: 1px solid var(--border); white-space: pre-wrap; word-break: break-all;
  font-size: .76rem; color: var(--fg-dim); max-height: 12rem; overflow: auto;
}

/* "Tested on" pill: the same-dialect passthrough the perf numbers were measured on. Neutral
   (bordered, muted) - it is a measurement label, NOT a claim that the gateway is that protocol. */
.tested-pill {
  display: inline-block; font-size: .72rem; padding: .1rem .5rem; border-radius: 999px;
  border: 1px solid var(--border); color: var(--fg-dim); background: var(--bg);
  white-space: nowrap; font-variant: small-caps; letter-spacing: .02em;
}

/* The size of the set a Min/Max extremum was selected from, beside the Tested-on pill ("of 26 served").
   An extremum without its candidate count is not comparable across rows, so it rides next to the cell. */
.tested-of {
  font-size: .68rem;
  white-space: nowrap;
  /* ON ITS OWN LINE, not beside the pill. The set size only appears in Min/Max mode, but it widened
     the "Tested on" column for EVERY mode and every tab, because a table column is as wide as its
     widest cell: "OpenAI  of 3 served" is roughly twice the pill it annotates, and the memory table
     has five numeric columns plus a sparkline fighting for the same width. Stacking keeps the
     disclosure (an extremum over 26 cells and one over 1 are different searches, which is the whole
     reason it is shown) while the column costs only the pill. */
  display: block;
  margin-top: .15rem;
}
/* What a MEASURED ZERO means, on the cell itself. "0" beside a real maximum reads as "does nothing";
   the short reason (no load held the gate / measured failure) rides under the number so the state is
   visible without hovering. Full sentence on the tooltip. */
/* A MEASURED FAILURE, in red with its counts ("failed · 0/14,201"). Distinct from an untested n/a:
   the measurement ran and the gateway failed all of it. Tooltip carries the engine's full evidence. */
td.failcell {
  color: #b91c1c;
  font-weight: 600;
  font-size: .8rem;
  white-space: nowrap;
}

.zero-why {
  display: block;
  font-size: .66rem;
  color: var(--fg-dim);
  /* WRAPS, unlike the number above it. td.reading/td.shape set nowrap so a rate never splits into two
     numbers, and this sub-line inherits it - which made a 22-character reason, not the rate, the thing
     sizing a numeric column. A reason reads fine on two lines; a number does not. */
  white-space: normal;
  line-height: 1.2;
}

/* Gateway-level plateau verdict on the Memory tab: RSS never went steady on ANY cell this gateway serves.
   Warm-toned rather than neutral, because unlike the freshness pill it is a finding about the gateway. */
.noplateau-pill {
  display: inline-block; margin-left: .35rem; padding: .02rem .4rem; border-radius: 999px;
  border: 1px solid var(--red); background: var(--bg); color: var(--red);
  font-size: .64rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  vertical-align: middle; cursor: help;
}
/* ...unless nothing was GROWING. An unsettled-but-oscillating gateway is a finding, not a defect, and
   red is the wrong colour for it: the reader should see it without reading it as a leak. */
.noplateau-pill.neutral { border-color: var(--fg-mute); color: var(--fg-mute); }

/* A gateway that produced NO protocol matrix still gets a row (all n/a); this carries the reason. */
.matrix-nores { color: var(--fg-mute); font-style: italic; }

/* Cell chooser (Performance, Streaming, Memory): the dialect dropdowns beside the segmented mode control.
   No box of its own: the axis is laid out by .filters (display: contents), and its show/hide is
   .filters > .control-group.hidden. */
.cell-chooser label { display: inline-flex; align-items: center; gap: .4rem; color: var(--fg-dim); font-size: .85rem; }
.cell-chooser label.hidden { display: none; }
.cell-chooser select {
  background: var(--bg-raise); color: var(--fg); border: 1px solid var(--border);
  border-radius: 6px; padding: .25rem .5rem; font-size: .85rem; font-family: inherit;
}
.cell-chooser .xlate-arrow { color: var(--fg-mute); }
/* Segmented Peak | Same | Custom control. */
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.seg-btn {
  appearance: none; border: 0; border-right: 1px solid var(--border); background: var(--bg-raise);
  color: var(--fg-dim); font: inherit; font-size: .82rem; padding: .3rem .8rem; cursor: pointer;
}
.seg-btn:last-child { border-right: 0; }
.seg-btn:hover { color: var(--fg); }
.seg-btn.active { background: var(--accent, #4cc38a); color: #06120c; font-weight: 600; }

/* ---- THE FILTER BLOCK: two labelled axes, one left edge -----------------------
   THE OWNER, ON THE LIVE FRONTIER TAB: "filter formatting is ugly, align them", "generally needs some ui
   help". What shipped was two sibling flex rows - "Tail-latency bound [chips] showing the req/s..." and
   below it a bare "[Own cell][Same][Custom]". The first row's text label indented its chips; the second
   row's chips started flush at the far left; the trailing sentence ran to a width nothing else shared.

   WHY A GRID AND NOT AN INDENT ON THE SECOND ROW. These are two DIFFERENT axes - WHICH TAIL BOUND, and
   WHICH CELL - and the second one had no label at all, which is the real reason it read as loose chips.
   Naming both and putting the names in a `max-content` gutter makes the shared left edge a consequence of
   the layout: it is exactly as wide as the longer label, so it cannot drift when either label's wording
   changes, and no magic indent exists to be forgotten. A hand-set padding on the second row would have
   aligned today's two strings and nothing else.

   THE AXES STAY SEPARATE ELEMENTS because they hide independently (the bound axis exists only on
   Performance + Frontier). `display: contents` makes each group's label and controls direct items of the ONE
   grid, so a hidden axis takes its whole row with it and the surviving axis does not move sideways. */
.filters {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: .5rem .9rem; align-items: center;
  margin: .9rem 0; padding: .7rem .9rem;
  background: var(--bg-raise); border: 1px solid var(--border); border-radius: 8px;
}
/* The block itself disappears when NEITHER axis applies (matrix/method have no table), rather than leaving
   an empty framed box above the data. */
.filters:not(:has(> .control-group:not(.hidden))) { display: none; }
.filters > .control-group { display: contents; }
.filters > .control-group.hidden { display: none; }
/* The axis name. Small caps rather than sentence case: it labels an axis, and at this size a run of
   lowercase words beside a row of chips reads as prose the reader should be following. */
.filters .flabel {
  color: var(--fg-dim); font-size: .74rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em; white-space: nowrap;
}
.filters .fcontrols { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }
/* The sentence under the bound chips ("showing the req/s each gateway's chosen cell carried while 99% of
   requests finished under 10 ms"). ITS OWN LINE, in the CONTROLS column, so it starts at the same left edge
   as the chips it explains rather than trailing off the end of them - and capped to a measure, because a
   line of 0.76rem text run to 1440px is not a sentence anyone reads.
   The bound must be legible as WORDS and not only as a highlighted button: the defect this whole surface
   exists to prevent is a reader inferring a bar the test never enforced. */
/* IN THE CONTROLS COLUMN, ON THE ROW AFTER ITS OWN AXIS - not pinned to a fixed row.
   This was `grid-row: 3`, correct while exactly ONE axis had a sentence: it put the note below both axes
   so they read as a pair. A second axis with a sentence (concurrency) then landed in the SAME cell and the
   two rendered on top of each other, illegibly - a hardcoded row cannot hold more than one occupant.
   Column 2 keeps every sentence starting at its chips' left edge; the row is left to auto-placement so
   each note follows the axis it explains, which is also what makes it obvious WHICH control it describes
   once there is more than one. A hidden axis still takes its own note with it. */
.filters .fnote { grid-column: 2; font-size: .76rem; max-width: 46rem; margin: 0; }
.filters .fnote:empty { display: none; }
/* CONSISTENT CHIP GEOMETRY WITHIN AN AXIS. "1 ms" and "no bound" are the same kind of choice, so they are
   the same size of target; six chips of six different widths was the other half of "ugly". Centred, because
   a min-width with left-aligned text just moves the ragged edge inside the chip. */
#bound-seg .seg-btn { min-width: 4.4rem; text-align: center; }
#mode-seg .seg-btn { min-width: 5rem; text-align: center; }

/* ---- the latency-throughput frontier ---------------------------------------- */

/* THE CURVE, per row. `color: currentColor` throughout the SVG, so the line inherits the accent here and
   stays legible in both themes without a second palette to keep in sync (same approach as .rss-spark). */
.frontier-spark { display: block; color: var(--accent, currentColor); }
/* nowrap for the NUMBER; the sub-line under it wraps (see .shape-gain) - a column sized by a sentence is
   the column-width defect one element down from where it was fixed. */
td.shape { white-space: nowrap; overflow: hidden; }
/* The gain factor beside the curve: the shape as one sortable, readable-aloud number. Small, because the
   line is what the eye should reach first - but present, because a picture cannot be sorted or spoken. */
.shape-gain { display: block; font-size: .7rem; color: var(--fg-dim); line-height: 1.2; white-space: normal; }
/* The frontier tab's per-bound cells: the rate, with the tail it ACTUALLY produced under it. A column of
   rates alone cannot tell 4 ms under a 100 ms bound from 99 ms under it. */
td.reading { white-space: nowrap; }
.reading-tail { display: block; font-size: .66rem; color: var(--fg-dim); line-height: 1.2; white-space: normal; }
/* "SERVED, BUT NOTHING HELD THIS TAIL" - a measured nothing. It must not read as missing data: on the
   slowest gateways five of six columns are this state, and "no data" is a neutral impression of a damning
   measurement. The number stays muted (it is a zero) while the reason is stated in ink beneath it. */
td.reading-zero { color: var(--fg-dim); }
.reading-none { display: block; font-size: .66rem; color: var(--red); line-height: 1.2; white-space: normal; }
/* The column the board is currently ranked at, marked so the Performance tab's number is locatable in the
   full curve. Deliberately the same tint the sorted column uses: it IS the sorted column by default, and
   the mark has to survive the reader sorting by something else. */
td.bound-col { background: var(--sorted-col-bg); }
/* The curve in the drawer / compare panel / matrix popup, with its gain sentence under it. */
.frontier-block { margin: .5rem 0 .2rem; }
.frontier-block .frontier-spark { max-width: 100%; }
.frontier-block .stamp { margin-top: .15rem; }
.frontier-block.compact { margin: .2rem 0; }

/* THE ENGINE'S OWN METRIC DEFINITIONS, folded. Collapsed by default so it costs one line of height until a
   reader asks for it; the prose is verbatim from the engine's constants, so it is set as body text rather
   than as fine print - it is the definition of the number above it, not a footnote to it. */
.metric-defs { margin: .35rem 0 .2rem; font-size: .8rem; }
.metric-defs > summary { cursor: pointer; color: var(--fg-dim); }
.metric-defs > summary:hover { color: var(--fg); }
.metric-def { margin: .5rem 0; padding-left: .6rem; border-left: 2px solid var(--border); }
.metric-def p { margin: .2rem 0 0; color: var(--fg-dim); line-height: 1.5; }

/* Floating hover popup for matrix cells: a small card that follows the hovered cell. */
.matrix-pop {
  position: absolute; z-index: 50; max-width: 24rem; pointer-events: none;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-raise);
  padding: .55rem .7rem; font-size: .8rem; box-shadow: 0 6px 20px rgba(0,0,0,.28);
}
.matrix-pop.hidden { display: none; }
.matrix-pop h4 { margin: 0 0 .35rem; font-size: .82rem; }
.matrix-pop .pop-perf { font-weight: 600; margin-bottom: .2rem; }
.matrix-pop .pop-metrics { display: grid; grid-template-columns: auto auto; gap: .05rem .8rem; margin-bottom: .3rem; }
.matrix-pop .pop-metrics > div { display: contents; }
.matrix-pop .pop-metrics span { color: var(--fg-dim); }
.matrix-pop .pop-metrics b { text-align: right; font-variant-numeric: tabular-nums; }
.matrix-pop .pop-delta { font-size: .77rem; color: var(--fg); margin-bottom: .2rem; }
.matrix-pop .pop-delta.muted { color: var(--fg-dim); }
.matrix-pop .pop-note { color: var(--fg-dim); font-size: .76rem; }
.matrix-pop .pop-cta { font-size: .72rem; margin-top: .3rem; }
.cell { cursor: default; }
/* A served (green) matrix cell is clickable → Performance Custom. */
.cell.served { cursor: pointer; }

/* Memory tab: the recovery-curve column cell + the memory chart region under the table.
   CAPPED AT WHAT THE SPARKLINE NEEDS. The svg is a fixed 260px, but the column rendered at 446 - roughly half
   the table - because `tbody td { white-space: nowrap }` applies to the caption under it, and the longest
   caption is one long line. So the column was sized by a sentence, and the four numeric columns (the actual
   content) were crushed to 70-93px between it and the Tested-on pill. The cap plus a wrapping caption keeps
   the picture at full size and gives the width back to the numbers. */
td.memcurve { width: 180px; min-width: 180px; max-width: 180px; padding-top: .2rem; padding-bottom: .2rem; }
td.memcurve.na { color: var(--fg-mute); }
/* The caption wraps INSIDE the capped column instead of widening it (it inherits nowrap from tbody td).
   It only appears in the DRAWER now; the table row carries no caption at all. */
.rss-spark .stamp { white-space: normal; }
/* THE INLINE LIFECYCLE CURVE, and the control that makes it reachable.
   A memory row stood ~350px tall because this one cell stacked six block elements. It now holds a single
   ~34px curve, which is why the row can be as short as a frontier row. The <button> is the reachability
   guarantee, not decoration: it carries the whole summary as its accessible name, so a keyboard or screen
   reader user gets every figure the captions used to show, and activating it opens the drawer. Styled to
   disappear - the row must not look like it is full of buttons - but it keeps a real focus ring. */
.rss-life-btn {
  display: block; width: 100%; padding: 0; margin: 0; border: 0; background: none;
  color: var(--accent, currentColor); cursor: pointer; border-radius: 4px; line-height: 0;
}
.rss-life-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rss-life { display: block; max-width: 100%; }
/* The axis-break glyph sits between the two segments. Muted, because it is notation rather than data - but
   it must be VISIBLE: it is the disclosure that the two halves are drawn at different time scales. */
.rss-break { pointer-events: none; }
/* "Tested on" on the memory table: capped, because it was sized by its longest pill
   ("OpenAI→Bedrock Converse") for every row. The pill truncates with an ellipsis and its full value stays on
   the tooltip it already carries, so nothing is lost but the width. */
#results-table td.tested { max-width: 8.5rem; }
#results-table td.tested .tested-pill { max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
/* THE RELEASE MARK: how much of what it gained under load the gateway gave back, as a tick between two
   levels already plotted (peak → last sample). Thick and faint so it reads as a magnitude behind the curve
   rather than as another line on it; a gateway that released nothing draws none at all. */
.rss-release { color: var(--accent, currentColor); }
.memory-charts.hidden { display: none; }
.memory-charts { margin-top: 1.5rem; }

/* charts (now folded into the Method tab, below the suite cards) */
.charts-heading { margin: 2.2rem 0 .2rem; padding-top: 1.4rem; border-top: 1px solid var(--border); }
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 1.1rem; }
.gallery figure {
  margin: 0; background: var(--bg-raise); border: 1px solid var(--border);
  border-radius: 8px; padding: .7rem; cursor: zoom-in;
}
.gallery img { width: 100%; height: auto; display: block; border-radius: 4px; background: var(--img-bg); }
.gallery figcaption { color: var(--fg-dim); font-size: .8rem; padding: .55rem .2rem 0; }

/* ---- method: a long-form essay, not a spec sheet ----------------------------
   The methodology page is prose. Its hierarchy comes from type (size, weight,
   spacing) rather than from borders, so there are deliberately no cards, no
   panels and no grid here. One column, held near 68ch so a line lands in the
   65-75 character range that stays comfortable to read, with the line height
   opened up from the site's 1.55 to 1.75 for sustained reading. Everything
   inherits the shared palette variables, so light and dark come for free. */
.essay { max-width: 68ch; }
.essay-title { font-size: 1.6rem; line-height: 1.25; letter-spacing: -.015em; margin: .2rem 0 1.1rem; max-width: 22ch; }
/* Standfirst: the conflict-of-interest disclosure. An accent rule on the left
   rather than a box, so it reads as an editorial note above the piece. */
.essay-standfirst {
  margin: 0 0 1.6rem; padding: .1rem 0 .1rem 1.1rem;
  border-left: 3px solid var(--accent); color: var(--fg-dim); font-size: .94rem; line-height: 1.65;
}
.essay-standfirst strong { color: var(--fg); }
.essay-lede { font-size: 1.14rem; line-height: 1.7; color: var(--fg); margin: 0 0 2rem; }
.essay h3 {
  font-size: 1.22rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em;
  color: var(--fg); margin: 2.6rem 0 .8rem; max-width: 30ch;
}
.essay h3:first-of-type { margin-top: 2.2rem; }
.essay p { margin: 0 0 1.15rem; font-size: 1.02rem; line-height: 1.75; color: var(--fg-dim); }
.essay p strong, .essay p b { color: var(--fg); font-weight: 600; }
/* Inline code inside prose sits at the text's own weight and never forces a
   horizontal scroll on a narrow screen. */
.essay code { font-size: .9em; word-break: break-word; }
.essay a { text-decoration: underline; text-underline-offset: .16em; }

/* footer */
.site-footer { border-top: 1px solid var(--border); background: var(--bg-raise); padding: 1.4rem 0 2rem; font-size: .84rem; }
.site-footer p { margin: .35rem 0; }

/* lightbox */
.lightbox {
  position: fixed; inset: 0; background: rgba(0,0,0,.82); display: flex;
  align-items: center; justify-content: center; cursor: zoom-out; z-index: 50; padding: 2rem;
}
.lightbox img { max-width: 100%; max-height: 100%; border-radius: 6px; background: var(--img-bg); }

/* theme toggle (top-right of header) */
.header-top { position: relative; }
.theme-toggle {
  position: absolute; top: 0; right: 0; appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  min-width: 40px; min-height: 40px; padding: 0 .7rem;
  background: var(--bg); border: 1px solid var(--border); border-radius: 999px;
  color: var(--fg-dim); font: inherit; font-size: .82rem; font-weight: 500; line-height: 1;
}
.theme-toggle:hover { color: var(--fg); border-color: var(--accent); }
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme-toggle .tt-icon { font-size: 1rem; line-height: 1; }
.theme-toggle .tt-label { white-space: nowrap; }
/* show the icon of the theme the click will switch TO */
[data-theme="dark"] .theme-toggle .tt-sun { display: none; }
[data-theme="light"] .theme-toggle .tt-moon { display: none; }

/* ---- responsive: phones and narrow tablets --------------------------------- */
@media (max-width: 760px) {
  html { font-size: 14px; }
  .wrap { padding: 0 1rem; }
  .site-header { padding-top: 1.2rem; }
  .site-header h1.brand { font-size: 1.35rem; padding-right: 3rem; }
  .brand a { flex-wrap: wrap; gap: .45rem; }
  .brand-mark { width: 24px; height: 24px; border-radius: 6px; }
  /* wordmark line first; the sub-line wraps beneath, aligned under the wordmark */
  .brand-sub { flex-basis: 100%; margin: 0 0 0 calc(24px + .45rem); padding-bottom: 0; font-size: .78rem; }
  .hero { padding: 1.6rem 0 .2rem; }
  .hero-word { font-size: 1.7rem; }
  .catnav { flex-wrap: wrap; gap: .4rem; }
  .theme-toggle .tt-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .theme-toggle { min-width: 44px; padding: 0; }
  .tabs { gap: 0; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .tabs::-webkit-scrollbar { display: none; }
  .tab { padding: .6rem .7rem; white-space: nowrap; }

  /* Narrow: unstack — caption above, search back to a normal full-width row. */
  .table-head { flex-direction: column; gap: .5rem; }
  .table-head .controls { flex-direction: row; align-items: center; width: 100%; }
  .controls { gap: .7rem; }
  #search { flex: 1 1 100%; min-width: 0; padding: .5rem .6rem; }
  .count { margin-left: 0; flex: 1 1 100%; }

  thead th, tbody td { padding: .5rem .55rem; }

  .drawer { width: 100vw; padding: 1rem 1.1rem 2rem; }
  .compare-bar { flex-wrap: wrap; gap: .5rem; padding: .6rem .8rem; }
  .compare-inner { padding: 1rem 1rem 1.4rem; }
  .compare-panel { padding: 2vh .5rem; }
  .gallery { grid-template-columns: 1fr; }
  /* The essay column is already narrower than a phone viewport at this size, so
     it just fills the wrap; only the type scale needs easing back. */
  .essay { max-width: 100%; }
  .essay-title { font-size: 1.4rem; max-width: none; }
  .essay-lede { font-size: 1.06rem; }
  .essay h3 { font-size: 1.14rem; margin-top: 2.1rem; max-width: none; }
  .essay p { font-size: 1rem; line-height: 1.72; }
  .matrix-table th, .matrix-table td { padding: .35rem .4rem; }
  .cell { width: 1.2rem; height: 1.2rem; }
}
@media (max-width: 400px) {
  .site-header h1.brand { font-size: 1.2rem; }
  .tab { padding: .55rem .55rem; font-size: .9rem; }
}

/* Benchmark version per row. Quiet when the row was measured by the same harness as the rest of the
   board; red when it was not, because numbers from two harnesses are not comparable and that has to
   be visible rather than known only to the build guard. */
.engine-pill {
  display: inline-block;
  margin-left: .4rem;
  padding: 0 .35rem;
  border-radius: 3px;
  font: 500 11px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--muted, #6b7280);
  background: rgba(127, 127, 127, .12);
  vertical-align: baseline;
}
.engine-pill.old {
  color: #b42318;
  background: rgba(217, 45, 32, .12);
  font-weight: 600;
}

/* The memory window as two curves side by side: what the process cost doing nothing, then what work
   cost it. They share one axis (0 to twice idle) so the pair is directly comparable - a flat idle
   curve under a rising load curve is healthy, a rising idle curve is a leak with nothing asked of
   it. Stacked rather than side by side because the RSS column is already the widest on the row. */
.rss-pair { display: flex; flex-direction: column; gap: .35rem; }
.rss-half { display: flex; flex-direction: column; }
.rss-label { font-size: .68rem; line-height: 1.2; text-align: left; }

/* ROWS THE SORTED COLUMN CANNOT SEPARATE.
   A table sorted by a column implies the order carries information. When two adjacent values are
   closer than the rig's own measurement resolution (derived from how far identical boxes drift on
   the same qualification), the order between them records which box they landed on. The rule is
   drawn ABOVE the tied row rather than colouring it: a tint reads as a status the gateway has, and
   this is a statement about the BOUNDARY between two rows, not about either one. */
tbody tr.tied-above > td {
  border-top: 1px dashed var(--grid, rgba(154, 164, 178, .45));
}

/* ---- the Charts tab ---------------------------------------------------------
   The metric picker sits above the canvas rather than beside it: the chart is the
   content and the control is how you ask for it, so the reading order is question
   then answer. */
.chart-controls { margin: 0 0 .75rem; }
.chart-metric { font-size: .85rem; color: var(--fg-dim); }
.chart-metric select { margin-left: .4rem; padding: .25rem .4rem; font: inherit; }
#chart-panel canvas { width: 100%; height: auto; max-width: 900px; }
#chart-note { margin-top: .6rem; font-size: .82rem; line-height: 1.5; }
