/* ==========================================================================
   Generative Biologics Benchmarks — tokens and shared components

   THIS FILE IS BYTE-IDENTICAL IN BOTH BUILDS. `tools/check_sync.py` fails if it
   drifts. Anything shape-specific belongs in shape.css, which is where the two
   builds are allowed to differ — layout and branding are separate axes here, and
   this pair isolates layout, so the identity must be literally the same file.

   BRAND. Every colour below comes from the Generative Biologics frontend
   (work/genbio_frontend/src/app/WithMUITheme/biologyTheme.ts) or from a darker
   step of one of its ramps, never from a guess:
     primary 700 #699633, 800 #57822A, 900 #36611A, 500 #8CB944, 300 #AECD7C
     success #82AC3D (the logo mark), warning #E8A728, text #1A1A1A / #666
   Roboto is the brand face and ships here as the same three woff2 files the
   product frontend serves.

   LIGHT IS THE PRIMARY THEME, unlike the cofold builds, which are dark-first.
   That is deliberate: the Generative Biologics product UI is a light surface, so
   a dark-first benchmark site would not look like it belonged to the brand. It
   also happens to be the cheapest honest way to stop this site looking like a
   recolour of its siblings.
   Consequence for anyone adding overrides: the base theme is declared on bare
   `:root` (0-1-0) and dark on `:root[data-theme="dark"]` (0-2-0) — the reverse of
   the cofold builds. A later block that means "dark only" must SAY so, as
   `:root[data-theme="dark"]`; a bare `:root` block added below will leak into
   light mode and beat nothing, which is the confusing half of the failure.

   COLOUR WAS MEASURED, NOT PICKED. `tools/check_palette.py` recomputes every
   number in the comments below from this file. Run it after any colour change.
   ========================================================================== */

@font-face {
  font-family: "Roboto";
  src: url("../fonts/Roboto-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* The brand frontend maps 500 to Roboto-Bold and 700 to Roboto-ExtraBold. That
   mapping is odd but it is the brand's own, and matching it means a heading here
   has the same weight as the same heading in the product. */
@font-face {
  font-family: "Roboto";
  src: url("../fonts/Roboto-Bold.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Roboto";
  src: url("../fonts/Roboto-ExtraBold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ---------- tokens: LIGHT is the base theme ---------- */
:root {
  --font-sans: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  --maxw: 1320px;
  --r-sm: 4px;

  /* ONE width for every block of running text on the page, whatever its font size.
     There were three: prose at 573px (68ch at 14.5px), figure captions at 504px (78ch at 11.5px),
     and notes inside cards at 1030px with no cap at all — 179 characters a line, more than twice a
     comfortable measure, which was an oversight rather than a decision. Worse than the bad line
     length was the near-miss: 504 against 573 is close enough that the difference read as sloppy
     rather than as a choice, which is what made the page feel arbitrary.
     Expressed in rem, NOT in ch: `ch` scales with font-size, so the same `68ch` gives three
     different pixel widths at three different text sizes, which is how the widths diverged in the
     first place. 36rem is the old prose width to within 3px, so no prose reflows.

     THE TRADE, measured, so nobody "fixes" it later without knowing what it costs. One shared
     PIXEL width means small text holds more characters per line than body text does: prose runs
     79 characters, captions and card notes run 100. The comfortable range for sustained reading is
     45-75, so 100 is wide — but the alternative is a character-based measure, which puts captions
     at ~356px under a 1030px figure. That is a narrow orphaned block under a wide one, which is
     the exact thing this change was made to remove, traded for a reading benefit that barely
     applies: these are one-to-three-line captions, not passages. The line length that actually
     hurt was the uncapped 179, and that is gone.
     If it ever wants improving, the lever is the FONT SIZE of small text (12.5px at 576px is ~92
     characters and is more legible in absolute terms), not the width. */
  --measure: 36rem;
  --r-md: 8px;
  --r-lg: 12px;
  --r-pill: 999px;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  color-scheme: light;

  /* Surfaces carry a trace of the brand green rather than neutral grey, so the
     page reads as part of the product without spending the accent. */
  --page: #ffffff;
  --surface: #f7f9f4;
  --surface-2: #eff3e9;
  --surface-3: #e5ebdb;
  --ink: #1a1a1a;           /* the brand wordmark black */
  --ink-2: #454a40;         /* 8.59:1 on --surface */
  --ink-3: #666666;         /* brand text.secondary, 5.42:1 */
  --ink-4: #7b8175;         /* 3.78:1 — micro-labels only, never a sentence */
  --border: rgba(26, 26, 26, 0.13);
  --border-2: rgba(26, 26, 26, 0.24);
  --grid: rgba(26, 26, 26, 0.08);

  /* The ground a strip dot's separation ring has to match. Declared once, themed through
     --surface, and overridden by any panel that sits on a different surface (the hero does).
     It is a variable rather than a literal because every dot state re-declares box-shadow. */
  --dot-ring: var(--surface);

  /* --accent      marks and rules (3.30:1 on --surface — a mark, not text)
     --accent-ink  accent used AS TEXT (4.28:1 on --surface, 4.53:1 on --page)
     --accent-fill filled controls carrying WHITE text (white on it: 4.53:1, AA)
     No contrast was traded for brand fidelity here: #57822A is brand primary-800,
     so AA and the brand agree. */
  --accent: #699633;
  --accent-ink: #57822a;
  --accent-fill: #57822a;
  --accent-deep: #36611a;
  --accent-rgb: 105 150 51;
  --mark: #82ac3d;          /* the logo mark's own green */

  /* --amber is a FILL only (1.98:1 on --surface). Amber as text uses --amber-ink,
     a darkened step of the brand warning hue: 5.66:1. */
  --amber: #e8a728;
  --amber-ink: #8f5600;

  /* ---- the rho ramp ----------------------------------------------------
     Spearman's rho is signed, but this is NOT a diverging ramp, on purpose.
     A diverging ramp gives the negative arm its own hue and its own resolution,
     which would claim that rho = -0.22 is meaningfully worse than rho = -0.05.
     With no confidence interval and no sample size in the feed, that claim
     cannot be supported: both mean "no usable signal". So everything at or
     below 0.10 is one recessive state, and the sign is carried by TEXTURE (the
     inverted band is hatched) plus the printed value's minus sign — never by
     hue alone. Hue-only red/green has failed twice in this project.
     Above 0.10, three steps of one hue, monotone in OKLab lightness:
       #8cb944 / #699633 / #36611a — validated by the dataviz skill's
       validate_palette.js --ordinal (monotone L, adjacent dL >= 0.06,
       light end 2.17:1 on the light surface, hue spread 8 degrees). */
  --sp-inv: #dcdcd6;
  --sp-inv-2: #b9b9b0;      /* the hatch stripe */
  --sp-inv-ink: #4a4a44;    /* 6.48:1 */
  --sp-nil: #edede8;
  --sp-nil-ink: #55554e;    /* 6.40:1 */
  --sp-weak: #8cb944;
  --sp-weak-ink: #182008;   /* 7.32:1 */
  --sp-mod: #699633;
  --sp-mod-ink: #182008;    /* 4.81:1 — white on this is only 3.49:1 */
  --sp-strong: #36611a;
  --sp-strong-ink: #ffffff; /* 7.29:1 */
  --sp-empty: rgba(26, 26, 26, 0.045);

  /* ---- the agreement ramp ---------------------------------------------
     This one IS diverging, and legitimately so: it holds the rank correlation
     between two BENCHMARKS, where the sign is the entire finding (+0.67 means
     the two assays rank models alike, -0.69 means they rank them opposite) and
     both arms are equally resolved. Two hues plus a neutral grey midpoint, per
     the dataviz colour formula. Blue for agreement, red for disagreement —
     deliberately not green, so a reader can never mistake an agreement cell for
     a performance cell. Each arm is monotone in lightness away from the
     midpoint, and every cell prints its value. */
  --ag-n2: #b02a2c;
  --ag-n2-ink: #ffffff;     /* 6.53:1 */
  --ag-n1: #efa3a2;
  --ag-n1-ink: #3a1010;     /* 8.27:1 */
  --ag-0: #f0efec;
  --ag-0-ink: #55554e;      /* 6.53:1 */
  --ag-p1: #a7c8ef;
  --ag-p1-ink: #0e2740;     /* 8.79:1 */
  --ag-p2: #1d5fae;
  --ag-p2-ink: #ffffff;     /* 6.36:1 */
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --page: #10140d;
  --surface: #171c11;
  --surface-2: #1d2416;
  --surface-3: #242c1b;
  --ink: #ffffff;
  --ink-2: #c8cebf;         /* 10.77:1 on --surface */
  --ink-3: #969c8d;         /* 6.14:1 */
  --ink-4: #6b7163;         /* 3.44:1 — micro-labels only */
  --border: rgba(255, 255, 255, 0.12);
  --border-2: rgba(255, 255, 255, 0.22);
  --grid: rgba(255, 255, 255, 0.07);

  --accent: #8cb944;        /* 7.55:1 on --surface */
  --accent-ink: #9dc360;    /* 8.60:1 */
  --accent-fill: #57822a;   /* white on it: 4.53:1, same step as light */
  --accent-deep: #3f5c25;
  --accent-rgb: 140 185 68;

  /* Bright amber clears 8.25:1 on the dark surface, so dark mode can use the
     brand warning colour as text directly. */
  --amber-ink: #e8a728;

  /* Dark ramp — the same hue, re-stepped and re-validated against the dark
     surface (light end #3f5c25 at 2.29:1, monotone, hue spread 6 degrees).
     Not an automatic inversion of the light values. */
  --sp-inv: #2a2a26;
  --sp-inv-2: #43433c;
  --sp-inv-ink: #a9a9a0;    /* 6.09:1 */
  --sp-nil: #24241f;
  --sp-nil-ink: #8c8c84;    /* 4.60:1 */
  --sp-weak: #3f5c25;
  --sp-weak-ink: #ffffff;   /* 7.58:1 */
  --sp-mod: #6f9c33;
  --sp-mod-ink: #10150b;    /* 5.71:1 */
  --sp-strong: #aecd7c;
  --sp-strong-ink: #10150b; /* 10.43:1 */
  --sp-empty: rgba(255, 255, 255, 0.04);

  --ag-n2: #f08e8d;
  --ag-n2-ink: #2a0b0b;     /* 7.78:1 */
  --ag-n1: #9e4443;
  --ag-n1-ink: #ffffff;     /* 6.25:1 */
  --ag-0: #383835;
  --ag-0-ink: #a9a9a0;      /* 4.97:1 */
  --ag-p1: #2e5f97;
  --ag-p1-ink: #ffffff;     /* 6.56:1 */
  --ag-p2: #86b8f0;
  --ag-p2-ink: #0a1b2b;     /* 8.41:1 */
}

* { box-sizing: border-box; }

/* The UA rule [hidden]{display:none} is author-beatable: any class that sets
   `display` silently defeats el.hidden. This makes the attribute mean what it
   says. Keep it. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; line-height: 1.15; letter-spacing: -0.015em; font-weight: 700; }
p { margin: 0; }
a { color: inherit; }
table { border-collapse: collapse; }

.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--accent-fill); color: #fff; padding: 10px 16px;
  border-radius: 0 0 var(--r-md) 0; font-weight: 500; text-decoration: none;
}
.skip:focus { left: 0; }

:where(a, button, input, select, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ---------- brand lockup ---------- */
.brand { display: inline-flex; align-items: center; gap: 10px; text-decoration: none; color: inherit; }
.brand__mark { color: var(--mark); flex: none; display: block; }
.brand__name { display: flex; flex-direction: column; line-height: 1.15; }
.brand__name b { font-weight: 700; font-size: 15px; letter-spacing: -0.02em; }
.brand__name span { font-size: 11px; color: var(--ink-3); letter-spacing: 0.01em; }

/* ---------- coverage notice ----------
   NOT a sample-data banner: every number on this site is real. What is partial
   is the coverage — 8 of ~30 planned benchmarks — so the notice says that and
   nothing stronger. Rendered only when meta.status is PARTIAL. */
.notice {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: baseline;
  padding: 9px 20px; font-size: 12.5px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  color: var(--ink-2);
}
.notice b { color: var(--amber-ink); font-weight: 500; }
.notice i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--amber);
  flex: none; align-self: center;
}

/* ---------- controls ---------- */
.iconbtn {
  width: 32px; height: 32px; display: grid; place-items: center;
  background: var(--surface-2); color: var(--ink-2);
  border: 1px solid var(--border); border-radius: var(--r-pill);
  cursor: pointer; transition: color 0.18s, border-color 0.18s;
}
.iconbtn:hover { color: var(--accent-ink); border-color: var(--border-2); }

.minibtn {
  font-family: inherit; font-size: 11.5px; color: var(--ink-2);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-pill); padding: 4px 12px; cursor: pointer;
  transition: background 0.16s, color 0.16s, border-color 0.16s;
}
.minibtn:hover { color: var(--ink); }
/* Active AFTER hover: both are 0-2-0, so order decides, and hovering an active
   control must not repaint it. */
.minibtn[aria-pressed="true"], .minibtn[aria-pressed="true"]:hover {
  background: var(--accent-fill); border-color: var(--accent-fill);
  color: #fff; font-weight: 500;
}

.fgroup { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.fgroup__label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-3);
}
.seg {
  display: inline-flex; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: 2px;
  flex-wrap: wrap;
}
.seg button {
  font-family: inherit; font-size: 12.5px; color: var(--ink-3);
  background: transparent; border: 0; border-radius: var(--r-pill);
  padding: 5px 13px; cursor: pointer; transition: color 0.16s, background 0.16s;
  white-space: nowrap;
}
.seg button:hover { color: var(--ink); }
.seg button[aria-pressed="true"] { background: var(--accent-fill); color: #fff; font-weight: 500; }
.seg--sm button { font-size: 11.5px; padding: 4px 10px; }

.check {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--ink-3); cursor: pointer;
}
.check input { accent-color: var(--accent-fill); }

/* ---------- cards ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden;
}
.card--pad { padding: 20px; }
.card__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px 20px; flex-wrap: wrap;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.card__title { font-size: 14px; font-weight: 700; }
.card__sub { font-size: 12px; color: var(--ink-3); font-weight: 400; }
.card__foot {
  padding: 11px 16px; border-top: 1px solid var(--border);
  font-size: 11.5px; color: var(--ink-3); line-height: 1.55;
  /* Measured, like every other block of running text. Uncapped it ran the full width of the card —
     179 characters a line. The card's own padding means its text column starts 16px inside the
     prose column rather than flush with it; that reads as the card's inset, not as a third width. */
  max-width: var(--measure);
}
.card__foot em { color: var(--ink-2); font-style: normal; }
.card__foot + .card__foot { border-top: 0; padding-top: 0; }

/* ---------- stat figures ----------
   A hero number, not a chart: one big value, its unit, and the sentence that
   makes it mean something. The sentence is the point — three bare numbers in a
   row is the thing this site is trying not to be. */
.figs { display: grid; gap: 1px; background: var(--border); border-radius: var(--r-lg); overflow: hidden; }
.fig { background: var(--surface); padding: 16px 18px; }
.fig b {
  display: block; font-size: 34px; font-weight: 700; line-height: 1;
  letter-spacing: -0.03em; font-variant-numeric: tabular-nums;
}
.fig b small { font-size: 17px; font-weight: 500; color: var(--ink-3); letter-spacing: 0; }
.fig p { margin-top: 7px; font-size: 12.5px; color: var(--ink-3); line-height: 1.5; }
.fig p strong { color: var(--ink-2); font-weight: 500; }
.fig--alert b { color: var(--amber-ink); }

/* ---------- leaderboard ---------- */
.tablewrap { overflow-x: auto; }
.lb { width: 100%; min-width: 780px; font-size: 13px; }
.lb thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-2);
  text-align: left; font-weight: 400; font-size: 10.5px;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-3);
  padding: 0; border-bottom: 1px solid var(--border); white-space: nowrap;
}
.lb thead th.num { text-align: right; }
.lb thead th button {
  font: inherit; text-transform: inherit; letter-spacing: inherit; color: inherit;
  background: none; border: 0; cursor: pointer; padding: 10px 11px;
  width: 100%; text-align: inherit;
}
.lb thead th button:hover { color: var(--ink); }
.lb thead th button[aria-sort] { color: var(--accent-ink); font-weight: 500; }
.lb thead th button[aria-sort]::after { content: " \2193"; }
.lb thead th button[aria-sort="ascending"]::after { content: " \2191"; }
.lb thead th:not(:has(button)) { padding: 10px 11px; }
.lb__rank { width: 1%; }

.lb tbody td { padding: 8px 11px; border-bottom: 1px solid var(--grid); vertical-align: middle; }
.lb tbody tr:last-child td { border-bottom: 0; }
.lb tbody tr:hover td { background: var(--surface-2); }
.lb td.num {
  text-align: right; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; font-size: 12.5px;
}
.lb .rank { font-family: var(--font-mono); color: var(--ink-3); font-size: 12px; }
.lb .rank--top { color: var(--accent-ink); font-weight: 500; }
.lb .dev { color: var(--ink-3); font-size: 11.5px; margin-left: 7px; font-weight: 400; }
/* Effort is a property of the run, not of the model, so it is set apart from the
   name rather than folded into it. NOT scoped to .lb: the same badge appears in
   the matrix row headers and in the rank spread, and scoping it left those two
   rendering the effort as bare unstyled text that read like part of the name. */
.eff {
  font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-3);
  border: 1px solid var(--border-2); border-radius: 3px; padding: 0 4px; margin-left: 7px;
}
.flag {
  font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--amber-ink);
  border: 1px solid color-mix(in srgb, var(--amber-ink) 45%, transparent);
  border-radius: 3px; padding: 0 4px; margin-left: 7px; white-space: nowrap;
}

/* mean rho, as a value plus a bar. The bar is anchored at zero, not at the
   minimum, because zero is where the metric stops meaning anything — a bar
   growing from the worst value would make every model look like it had some. */
.meancell { display: flex; align-items: center; gap: 9px; justify-content: flex-end; }
.zbar {
  position: relative; width: 92px; height: 7px; flex: none;
  background: var(--sp-empty); border-radius: 2px; overflow: hidden;
}
.zbar__zero { position: absolute; top: -1px; bottom: -1px; width: 1px; background: var(--border-2); }
.zbar i { position: absolute; top: 0; bottom: 0; border-radius: 2px; background: var(--accent); }
.zbar i.neg { background: var(--sp-inv-2); }

/* ---------- rank-spread ----------
   One row per model: a bar from its best rank to its worst rank across the
   benchmarks it appears in, with the mean marked. This is the figure that
   carries the site's main finding, so it is a chart, not a table column: the
   overlap between the bars IS the message.
   Rank 1 sits at the left, so shorter-and-lefter is better and the eye reads it
   the same way as the leaderboard above it. */
/* One grid PER ROW, sharing a column template — not one grid over all the rows.
   (It was the latter first: with each row wrapped in its own element, the 27 rows
   became the 27 grid items and flowed into three columns of names with no tracks
   at all.) A shared template keeps the tracks aligned; `--lab` is the label
   column so the axis row can reserve exactly the same width. */
.spread { --lab: 128px; display: flex; flex-direction: column; gap: 3px; }
.spread__row,
.spread__axis {
  display: grid; grid-template-columns: var(--lab) minmax(0, 1fr) 46px;
  gap: 12px; align-items: center;
}
.spread__name {
  font-size: 12px; color: var(--ink-2); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; text-align: right;
}
.spread__track {
  position: relative; height: 15px; min-width: 0;
  background: linear-gradient(var(--grid), var(--grid)) center / 100% 1px no-repeat;
}
.spread__bar {
  position: absolute; top: 4px; height: 7px; border-radius: 4px;
  background: color-mix(in srgb, var(--accent) 42%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
}
.spread__mean {
  position: absolute; top: 1px; width: 3px; height: 13px; border-radius: 2px;
  background: var(--accent-ink);
  /* a 2px page-coloured ring so the marker stays visible where bars overlap */
  box-shadow: 0 0 0 1.5px var(--surface);
}
.spread__val {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The axis reuses the row template, so its ends line up with the tracks above
   rather than with the card. The empty first and last cells are the label and
   value columns. */
.spread__axis { margin-top: 4px; padding-top: 5px; border-top: 1px solid var(--grid); }
.spread__axis i {
  grid-column: 2; display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-4); font-style: normal;
}
.spread__row:hover .spread__bar,
.spread__row.is-hot .spread__bar { background: color-mix(in srgb, var(--accent) 62%, transparent); }

/* ---------- per-benchmark dot strip ----------
   Every model's score for one benchmark, on a shared axis with zero marked.
   A strip rather than 25 bars: the question a reader has about a benchmark is
   "how much signal is there at all", which is a distribution, not a ranking. */
.strip { position: relative; height: 43px; min-width: 0; }
.strip__zero {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: var(--border-2);
}
.strip__zlab {
  position: absolute; bottom: -3px; transform: translateX(-50%);
  font-family: var(--font-mono); font-size: 9px; color: var(--ink-4);
}
.strip__dot {
  /* `top` is set inline by the beeswarm lane assignment in core.js */
  position: absolute; width: 9px; height: 9px; border-radius: 50%;
  margin-left: -4.5px; background: var(--accent);
  /* Separation ring: with 25 dots on one axis they overlap, and this is what keeps two
     adjacent models from merging into one blob. Its colour is a variable because the ring
     has to match whatever surface the strip is sitting on — the hero panel is --surface-2,
     not --surface — and every state below re-declares box-shadow, so the alternative was
     restating the whole stack per context. */
  box-shadow: 0 0 0 1.5px var(--dot-ring);
  cursor: pointer;
}
.strip__dot.neg { background: var(--sp-inv-2); }

/* ---- the three emphasis states, on three different channels ----------------------
   These are different KINDS of thing, so they get different channels rather than
   different values of one channel. That is not a stylistic preference: it was measured.
   All three used to be "big and dark", and in dark mode the fill channel has no room for
   a third green at all — every step lighter than the ordinary dot lands within dE 20 of
   it, and every step darker lands within dE 10 of the GREY negative dot, so the top model
   was rendering as one that ranks backwards (dE 9.7) at 2.29:1 on its own ground.

     is-best     a fact about the DATA, one per benchmark  -> SIZE only
     is-hot      the pointer is here, transient            -> a neutral --ink ring
     is-pinned   the reader's own choice, persistent       -> --ink fill + --accent-ink ring

   The neutral-for-hover / accent-for-pinned split is the convention this file already uses
   for leaderboard rows and matrix cells; the strip dot was the one place that collapsed the
   two into a single rule, so a pinned dot and a hovered dot were pixel-identical (dE 0.0)
   and there was no way to tell which was which.

   is-best takes SIZE and leaves the fill alone, so the fill goes on saying what the value
   is — green positive, grey negative — instead of overwriting it with a third green that
   neither theme has room for. The best model is named in text anyway: in the table's own
   "best" column, and by the label above the dot in the hero.

   They grow about their own centre: a size change that moved the dot's centre would shift
   it along the value axis and misreport its score. Hover deliberately does NOT resize, so
   a hovered best dot stays best-sized and the ordering traps below do not arise. */
.strip__dot.is-best {
  width: 13px; height: 13px; margin: -2px 0 0 -6.5px;
}
.strip__dot.is-hot {
  z-index: 3;
  box-shadow: 0 0 0 1.5px var(--dot-ring), 0 0 0 3.5px var(--ink);
}
/* pinned AFTER hot, so a pin outranks a passing hover — same ordering, and the same
   reason, as the leaderboard rows below. Equal specificity means order decides.

   THE FILL IS LEFT ALONE HERE TOO, and that is not a detail. An earlier version set
   `background: var(--ink)`, which sits after `.neg` and therefore repainted a pinned
   NEGATIVE dot as though it were positive. Three costs, in increasing order of badness:
   the sign is the most load-bearing attribute on this site (a whole section argues that
   negative means ranked backwards, and the rule elsewhere is that sign is never carried
   by hue alone); a reader could not see which of their pinned model's benchmarks were
   negative at all; and pinning one model while hovering another made the pinned one look
   uniformly better, because its dots were all one colour while the hovered model's still
   split into green and grey. A user-selected state must not overwrite the datum.

   So pinned is TWO CONCENTRIC RINGS instead: an inner --ink ring, which is what gives it
   contrast against any fill (dE >= 31 from both the green and the grey), and an outer
   --accent-ink ring, which is this file's convention for "the reader chose this". Against
   hover's single ink ring that is a difference of ring count, ring colour and size, so the
   two cannot be confused — which was the original complaint. */
.strip__dot.is-pinned {
  z-index: 4; width: 13px; height: 13px;
  margin: -2px 0 0 -6.5px;
  box-shadow:
    0 0 0 1.5px var(--dot-ring),
    0 0 0 3.5px var(--ink),
    0 0 0 5px var(--accent-ink);
}
/* Ticks are absolutely placed by the same scale function as the dots, not spread
   with space-between: zero is at 30% of this track, not at the middle, so an
   evenly-spread axis would put the "0" label under the wrong line. */
/* Empty-slice state for a strip: a sentence, not a blank cell, so the reason is
   visible where the dots would be. */
.strip__none {
  position: absolute; top: 12px; left: 0; font-size: 11px; color: var(--ink-4);
  white-space: nowrap;
}
.bench tr.is-empty th, .bench tr.is-empty td { opacity: 0.72; }

.strip__axis {
  position: relative; height: 13px;
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-4);
}
.strip__axis span { position: absolute; top: 0; white-space: nowrap; }

/* ---------- matrix (shared by the rho matrix and the agreement matrix) ---------- */
.scale { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 11px; color: var(--ink-3); }
.scale__label {
  font-family: var(--font-mono); color: var(--ink-4);
  letter-spacing: 0.06em; text-transform: uppercase;
}
.scale__step { display: inline-flex; align-items: center; gap: 6px; }
/* hairline ring so the recessive low steps are still visible in the KEY, which
   is the one place they are not meant to recede */
.scale__step i {
  width: 20px; height: 11px; border-radius: 2px; display: inline-block;
  box-shadow: inset 0 0 0 1px var(--border-2);
}

/* ---------- a key for the STRIP charts, which do not use bands ----------
   `.scale--marks` swaps the band swatches for the REAL marks: each swatch carries `.strip__dot`
   itself, so a change to a dot's fill, size or ring cannot leave its key describing the old one.

   It exists because the five-step band scale was being emitted above BOTH charts, and it is only
   true of one of them. Matrix cells really are filled by `bandOf()`. A strip dot never is: it is
   green for rho >= 0 and grey when the model ranked backwards, and that is the whole colour
   encoding — two states under a key advertising five, with one green silently spanning
   negligible, weak, moderate and strong, which are four different claims.

   Two specificity notes. The dots are `position: absolute` with recentring margins because they
   live inside a `.strip`, so they need static placement here; and this selector is (0,2,0) so it
   out-specifies `.scale__step i` at (0,1,1), which would otherwise impose the band swatch's
   rectangle on them. */
.scale--marks .strip__dot {
  position: static; margin: 0; cursor: default; flex: none;
  width: 9px; height: 9px; border-radius: 50%;
  box-shadow: 0 0 0 1.5px var(--dot-ring);
}
/* `best` is marked by SIZE and keeps the fill that carries its sign, so its key entry has to be
   the larger dot rather than another colour. */
.scale--marks .strip__dot.is-best { width: 13px; height: 13px; }

/* CSS uppercases Greek too: text-transform on a header containing "ρ" produces
   "Ρ", capital rho, which is drawn identically to a Latin P in Roboto — so
   "Mean ρ" was rendering as "MEAN P" and reading as a different quantity. Any
   symbol inside an uppercased label has to opt out. */
.nocase { text-transform: none; }

.mxwrap { overflow: auto; }
.mx { border-collapse: separate; border-spacing: 2px; font-size: 11px; }
.mx th, .mx td { padding: 0; }
.mx thead th {
  position: sticky; top: 0; z-index: 3;
  background: var(--surface); color: var(--ink-2);
  font-weight: 400; font-size: 10.5px; text-align: center;
  padding: 7px 6px; min-width: 78px; vertical-align: bottom; line-height: 1.25;
}
.mx thead th small { display: block; color: var(--ink-4); font-size: 9.5px; }
.mx thead th.mx__corner {
  left: 0; z-index: 4; text-align: left; min-width: 200px;
  vertical-align: bottom; color: var(--ink-4);
}
.mx tbody th {
  position: sticky; left: 0; z-index: 2;
  background: var(--surface); text-align: left; font-weight: 400;
  padding: 4px 10px 4px 6px; min-width: 200px; max-width: 200px;
  font-size: 11.5px; color: var(--ink-2);
}
.mx tbody th .sub { display: block; font-size: 10px; color: var(--ink-4); }

.mx td.cell {
  position: relative; text-align: center; height: 28px; border-radius: 3px;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  outline: 1px solid transparent; outline-offset: -1px;
  transition: outline-color 0.12s;
  cursor: pointer;
}
.mx td.cell:hover { outline-color: var(--ink); z-index: 1; }
.mx.hide-values td.cell { color: transparent !important; }

/* rho bands. The two "no signal" bands share a lightness and differ by texture:
   inverted is hatched, negligible is flat. Hue is doing no work here at all,
   which is the point. */
.b-inv {
  background: repeating-linear-gradient(
    135deg, var(--sp-inv) 0 3px, var(--sp-inv-2) 3px 6px);
  color: var(--sp-inv-ink);
}
.b-nil    { background: var(--sp-nil);    color: var(--sp-nil-ink); }
.b-weak   { background: var(--sp-weak);   color: var(--sp-weak-ink); }
.b-mod    { background: var(--sp-mod);    color: var(--sp-mod-ink); }
.b-strong { background: var(--sp-strong); color: var(--sp-strong-ink); }
/* absent, not zero — see the matrix footnote */
.b-absent { background: var(--sp-empty); color: var(--ink-4); cursor: default; }

/* agreement bands */
/* ---------- agreement arcs ----------
   See renderAgArcs() in core.js for why this is an arc diagram and not a chord diagram, and why
   the pairs drawn are the matrix's own outer bands rather than a threshold invented here.

   FIXED HEIGHT, and the svg stretches horizontally inside it (preserveAspectRatio="none"). The
   label band in the middle must not be squeezed at a narrow width, and only the arcs' SIDE and
   THICKNESS carry meaning — an ellipse says exactly what a circle would. `non-scaling-stroke` in
   the markup keeps thickness in screen pixels regardless of the stretch. */
/* The fixed height belongs to the PLOT, not to this wrapper: the wrapper also holds the legend,
   and `.card` clips its overflow, so a legend sitting outside a fixed-height box vanishes. */
.arcs { padding: 6px 16px 12px; }
.arcs__plot { position: relative; height: 244px; }
.arcs__svg { display: block; width: 100%; height: 244px; overflow: visible; }
.arcs__axis { stroke: var(--border-2); stroke-width: 1; }

.arc {
  fill: none; stroke-linecap: round;
  /* The band classes set `background` for matrix cells, so the stroke has to be taken from the
     same tokens rather than from those rules. Same values, one source. */
  opacity: 0.9;
  transition: opacity 0.16s var(--ease);
}
.arc.a-p2 { stroke: var(--ag-p2); }
.arc.a-n2 { stroke: var(--ag-n2); }
/* The mild and neutral bands are never drawn (only the outer two are), but if that ever changes
   these keep the figure from falling back to an invisible default stroke. */
.arc.a-p1 { stroke: var(--ag-p1); }
.arc.a-n1 { stroke: var(--ag-n1); }
.arc.a-0 { stroke: var(--ag-0); }

/* Fading is driven by a CLASS that wireArcs() sets, not by `:hover` on the svg. The pure-hover
   version faded the whole figure whenever the pointer was merely inside it — which said nothing,
   made the figure flicker as the pointer crossed it, and made hovering a NAME impossible, because
   the labels had to pass the pointer through to the svg to be hovered at all. `.is-focused` is only
   present when something is actually being pointed at. */
#ag-arcs.is-focused .arc { opacity: 0.12; }
#ag-arcs.is-focused .arc.is-lit { opacity: 1; }

/* The nodes are real HTML text, not svg <text>: the svg is stretched, and stretched text is
   unreadable. Positioned by percentage, which lines up with the arc endpoints because both are
   the same fraction of the same width. */
.arcs__node {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  font-family: var(--font-mono); font-size: 10.5px; line-height: 1.25;
  color: var(--ink-2); text-align: center; white-space: nowrap;
  background: var(--surface); padding: 2px 5px; border-radius: var(--r-sm);
  /* The label sits ON the axis line, so it needs its own ground to break the line behind it. */
  /* It RECEIVES the pointer, which it did not at first: hovering a name now lights every arc that
     touches that benchmark, and `pointer-events: none` sent the pointer through to the svg instead,
     so the one gesture a reader tries on a node did nothing useful. The cost is that arcs are not
     hoverable in the few pixels behind a label, which is fine — they are hoverable along the whole
     rest of their length. */
  cursor: default;
  transition: color 0.14s var(--ease);
}
/* Discoverability: the name has to look like it does something, or nobody hovers it. */
.arcs__node:hover { color: var(--ink); }
#ag-arcs.is-focused .arcs__node { color: var(--ink-4); }
#ag-arcs.is-focused .arcs__node:hover { color: var(--ink); }

.arcs__legend {
  display: flex; align-items: center; gap: 8px 16px; flex-wrap: wrap;
  margin: 8px 0 0; font-size: 11px; color: var(--ink-4);
}
.arcs__legend span { display: inline-flex; align-items: center; gap: 6px; }
.arcs__key { width: 16px; height: 8px; display: inline-block; border-radius: 2px 2px 0 0; }
/* The key swatches say SIDE, which is the encoding sign actually uses: a bar rising from a
   baseline for "above", hanging from one for "below". */
.arcs__key--up { border-top: 2.5px solid var(--ag-p2); border-bottom: 1px solid var(--border-2); }
.arcs__key--down { border-bottom: 2.5px solid var(--ag-n2); border-top: 1px solid var(--border-2); }
.arcs__legend-n b { color: var(--ink-2); font-weight: 500; }
.arcs__none {
  margin: 0; padding: 18px 2px; font-size: 13px; color: var(--ink-3);
}
/* The empty state replaces the plot entirely, so the wrapper must not reserve the plot's height
   for a box that is not there. */
.arcs:has(.arcs__none) { padding-block: 4px; }

.a-n2 { background: var(--ag-n2); color: var(--ag-n2-ink); }
.a-n1 { background: var(--ag-n1); color: var(--ag-n1-ink); }
.a-0  { background: var(--ag-0);  color: var(--ag-0-ink); }
.a-p1 { background: var(--ag-p1); color: var(--ag-p1-ink); }
.a-p2 { background: var(--ag-p2); color: var(--ag-p2-ink); }
.a-self { background: var(--surface-2); color: var(--ink-4); cursor: default; }

/* the best model in a benchmark column, marked by shape so it survives the
   whole ramp from near-surface to deep green */
.mx .best {
  position: absolute; top: 1px; right: 2px; font-size: 8px; line-height: 1;
  /* INHERITS the cell's colour, which is that band's own ink, and that is the whole fix. It was
     `var(--amber)` — a fixed value with no dark-theme override, sitting on five different band
     fills. Measured, it landed at dE 13.3 / 1.09:1 on `weak` in light and dE 11.7 / 1.18:1 on
     `strong` in dark, against this project's dE >= 24 and lightness-gap >= 16 for a small mark.
     It reached the bad band 3 of 8 times in light and 5 of 8 in dark, and a 1px drop-shadow was
     doing the work of making it visible at all.

     Theming --amber would NOT have fixed it, only moved which band is bad: the five fills tile the
     lightness axis (span 50 in light, 55 in dark, adjacent gaps as small as 3), so no single
     lightness is 16 away from all five. There is no fixed colour that works here.
     Each band already guarantees its own ink at >= 4.5:1 on its own fill — check_palette.py
     asserts exactly that — so inheriting is legible on every band in both themes by construction,
     and the drop-shadow crutch is gone. 7px -> 8px keeps it prominent now that it is not shouting
     in a different hue. */
  color: inherit;
}

/* ---------- linked highlighting + pinning ---------- */
.pinbtn {
  font: inherit; color: inherit; text-align: left;
  background: none; border: 0; padding: 0; cursor: pointer;
}
.pinbtn:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 3px; }

.pinbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pinbar__label {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3);
}
.pinchip {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: inherit; font-size: 12.5px; color: var(--ink);
  background: color-mix(in srgb, var(--accent) 15%, var(--surface-2));
  border: 1px solid color-mix(in srgb, var(--accent) 48%, transparent);
  border-radius: var(--r-pill); padding: 4px 11px; cursor: pointer;
}
.pinchip:hover { border-color: var(--accent-ink); }
.pinchip__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent-ink); flex: none; }
.pinchip__meta { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-3); }
.pinchip__x { color: var(--ink-3); margin-left: 2px; font-size: 11px; }
.pinchip:hover .pinchip__x { color: var(--ink); }

.lb tbody tr.is-hot td { background: var(--surface-2); }
.lb tbody tr.is-hot td:first-child { box-shadow: inset 2px 0 0 var(--border-2); }
/* pinned AFTER hot, so a pin outranks a passing hover */
.lb tbody tr.is-pinned td { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
.lb tbody tr.is-pinned td:first-child { box-shadow: inset 3px 0 0 var(--accent-ink); }
.lb tbody tr.is-pinned .pinbtn { color: var(--accent-ink); font-weight: 500; }

.mx tbody th.is-hot { color: var(--ink); background: var(--surface-2); }
.mx td.cell.is-hot { box-shadow: inset 0 0 0 1.5px var(--ink); }
.mx tbody th.is-pinned { color: var(--accent-ink); background: var(--surface-2); }
/* Rails with a 2px gap in the surface colour, so the marker survives both the
   near-surface and the deep-green end of the ramp. Later insets paint behind
   earlier ones. */
.mx td.cell.is-pinned {
  box-shadow:
    inset 0 2px 0 var(--surface), inset 0 -2px 0 var(--surface),
    inset 0 4px 0 var(--accent-ink), inset 0 -4px 0 var(--accent-ink);
}

/* ---------- tooltip ---------- */
.tip {
  position: fixed; z-index: 90; pointer-events: none;
  background: var(--surface-3); border: 1px solid var(--border-2);
  border-radius: var(--r-md); padding: 8px 11px; font-size: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3); max-width: 280px;
}
:root[data-theme="dark"] .tip { box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6); }
.tip b { display: block; margin-bottom: 3px; }
.tip dl { display: grid; grid-template-columns: auto auto; gap: 2px 12px; margin: 0; }
.tip dt { color: var(--ink-3); }
.tip dd {
  margin: 0; text-align: right; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ---------- table view (the required non-colour fallback for every chart) ---------- */
.tableview { border-top: 1px solid var(--border); }
.tableview summary {
  padding: 10px 16px; cursor: pointer; font-size: 12.5px; color: var(--ink-3); list-style: none;
}
.tableview summary::-webkit-details-marker { display: none; }
.tableview summary::before { content: "\25B8 "; color: var(--ink-4); }
.tableview[open] summary::before { content: "\25BE "; }
.tableview summary:hover { color: var(--ink); }
.tableview table { width: 100%; font-size: 12.5px; }
.tableview th, .tableview td { padding: 6px 16px; border-top: 1px solid var(--grid); text-align: right; }
.tableview th:first-child, .tableview td:first-child { text-align: left; }
.tableview thead th {
  color: var(--ink-3); font-weight: 400; font-size: 10.5px;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.tableview td { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.tableview td:first-child { font-family: var(--font-sans); }

/* ---------- detail dialog ---------- */
.detail {
  border: 1px solid var(--border-2); border-radius: var(--r-lg);
  background: var(--surface); color: var(--ink);
  padding: 0; width: min(720px, calc(100vw - 32px)); max-height: 86vh; overflow: auto;
}
.detail::backdrop { background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(3px); }
.detail__x {
  position: absolute; top: 11px; right: 13px;
  background: var(--surface-2); color: var(--ink-2);
  border: 1px solid var(--border); border-radius: var(--r-pill);
  width: 29px; height: 29px; cursor: pointer; font-size: 12px;
}
.detail__x:hover { color: var(--ink); }
.dhead { padding: 19px 22px 15px; border-bottom: 1px solid var(--border); }
.dhead h3 { font-size: 18px; }
.dhead .dmeta {
  margin-top: 7px; display: flex; gap: 13px; flex-wrap: wrap;
  font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-3);
}
.dbody { padding: 17px 22px 22px; }
.dbody > p { font-size: 13.5px; color: var(--ink-2); line-height: 1.6; }
.dscore { display: flex; align-items: baseline; gap: 12px; margin-bottom: 12px; }
.dscore b { font-size: 38px; font-weight: 700; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.dband { font-size: 12px; border-radius: var(--r-pill); padding: 3px 11px; font-weight: 500; }
.dnote {
  margin-top: 15px; padding: 11px 13px;
  border: 1px dashed var(--border-2); border-radius: var(--r-md);
  font-size: 12.5px; color: var(--ink-3); line-height: 1.55;
}
.dall { margin-top: 17px; }
.dall h4 {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-3); margin-bottom: 8px; font-weight: 400;
}
.dall table { width: 100%; font-size: 12.5px; }
.dall td { padding: 5px 0; border-top: 1px solid var(--grid); }
.dall td:last-child { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.dall .pip {
  display: inline-block; width: 9px; height: 9px; border-radius: 2px;
  margin-right: 8px; vertical-align: -1px;
  box-shadow: inset 0 0 0 1px var(--border-2);
}
.dall tr.is-pinned td { color: var(--accent-ink); }

/* ---------- prose ---------- */
.prose p { color: var(--ink-2); font-size: 14.5px; line-height: 1.65; }
.prose p + p { margin-top: 12px; }
.prose h4 { font-size: 13px; margin: 18px 0 6px; }
.prose strong { color: var(--ink); font-weight: 500; }
.prose code {
  font-family: var(--font-mono); font-size: 12.5px;
  background: var(--surface-2); padding: 1px 5px; border-radius: 4px;
}
.prose a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 3px; }
.link {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--accent-ink); text-decoration: none; font-weight: 500;
}
.link:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ---------- data-load failure ---------- */
.loadfail {
  max-width: 620px; margin: 60px auto; padding: 22px;
  border: 1px solid var(--border-2); border-radius: var(--r-lg);
}
.loadfail h2 { font-size: 18px; margin-bottom: 10px; }
.loadfail p { color: var(--ink-2); font-size: 14px; }
.loadfail pre {
  margin-top: 12px; background: var(--surface-2); padding: 11px;
  border-radius: var(--r-md); font-size: 12.5px; overflow: auto;
}
