/* Design tokens. Dark values are declared under both scopes: the media query covers
   the OS setting, the [data-theme] scope covers the in-page toggle, which must win
   both ways. Dark is separately stepped for the dark surface, not an inverted flip. */
:root {
  color-scheme: light;
  --surface-1: #fcfcfb;
  --page: #f9f9f7;
  --ink-1: #0b0b0b;
  --ink-2: #52514e;
  --ink-muted: #898781;
  --grid: #e1e0d9;
  --axis: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10);
  --hover: rgba(11, 11, 11, 0.04);

  /* Ink for text that sits *on* a data fill, where the page's ink and surface are not
     behind it and cannot help. These are the extremes on purpose: between #0b0b0b and
     #fcfcfb there is a band of mid-tone fills (L in .178-.190, which is exactly where
     --series-1 lands) that neither reaches 4.5:1 on. Between #000 and #fff no such band
     exists — measured worst case over every ramp step and every interpolation is 4.59:1.
     Not theme-scoped: an extreme is an extreme in both modes. */
  --ink-on-fill-dark: #000000;
  --ink-on-fill-light: #ffffff;

  /* Categorical, in the reference palette's fixed slot order. How many of the eight a
     chart may spend depends on its pairlist, and the two answers are far apart — measured
     with the palette validator, both modes:
       adjacent (stacked/grouped bars, sunburst rings, legends): all 8 PASS.
         worst CVD dE 9.1 light / 8.4 dark, worst normal-vision 19.6 / 19.3.
       all pairs (the spatial scatter, where any two colours can land side by side): 3.
         blue+orange+aqua pass at CVD 9.2/9.4, normal 24.0/20.9. No 4-subset of these
         eight clears it — the best available sits at CVD 6.9 dark, inside the band that
         is legal only with secondary encoding, and a 2.4px dot cannot carry any.
     So the slot count is per-form, not global: classColors() takes an explicit cap.
     Light-mode aqua, yellow and magenta sit below 3:1 on the surface (2.74 / 2.11 / 2.62),
     which is the palette's relief rule — every card here ships direct labels or a table. */
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
  --series-6: #008300;
  --series-7: #4a3aa7;
  --series-8: #e34948;
  --series-gray: #898781;

  /* The remainder ink for an *all-pairs* form. --series-gray cannot do that job: against
     --series-3 it measures CVD dE 5.1 light / 2.0 dark and normal-vision 14.5 / 12.6 —
     a hard FAIL in both modes, i.e. a gray dot and an aqua dot are the same dot. Stepped
     per mode instead, and then slots 1-3 plus this one clear all pairs in both: CVD 9.2 /
     9.4, normal-vision 20.2 / 20.9. Like --series-gray it is a role, not a slot, and like
     --series-gray it fails the chroma floor on purpose. */
  --series-rest: #5b5a55;

  --seq-100: #cde2fb; --seq-150: #b7d3f6; --seq-200: #9ec5f4; --seq-250: #86b6ef;
  --seq-300: #6da7ec; --seq-350: #5598e7; --seq-400: #3987e5; --seq-450: #2a78d6;
  --seq-500: #256abf; --seq-550: #1c5cab; --seq-600: #184f95; --seq-650: #104281;
  --seq-700: #0d366b;

  --div-low: #2a78d6;
  --div-mid: #f0efec;
  --div-high: #d03b3b;

  --good: #0ca30c; --warning: #fab219; --serious: #ec835a; --critical: #d03b3b;
  --delta-up: #006300;

  --radius: 4px;
  --font: system-ui, -apple-system, "Segoe UI", "Noto Sans SC", sans-serif;

  /* The atlas pair's shared media box. The H&E and the cell map show the same field of
     view, so they must also occupy the same rectangle on screen — same height, same
     inset, same letterbox — or the reader is asked to compare two different scales.
     atlas.js reads this back rather than keeping a second copy of the number. */
  --map-h: 560px;
  --map-inset: 8px;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --surface-1: #1a1a19;
    --page: #0d0d0d;
    --ink-1: #ffffff;
    --ink-2: #c3c2b7;
    --ink-muted: #898781;
    --grid: #2c2c2a;
    --axis: #383835;
    --border: rgba(255, 255, 255, 0.10);
    --hover: rgba(255, 255, 255, 0.06);
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-6: #008300;
    --series-7: #9085e9;
    --series-8: #e66767;
    --series-rest: #c9c8c1;
    --div-mid: #383835;
    --delta-up: #0ca30c;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --surface-1: #1a1a19;
  --page: #0d0d0d;
  --ink-1: #ffffff;
  --ink-2: #c3c2b7;
  --ink-muted: #898781;
  --grid: #2c2c2a;
  --axis: #383835;
  --border: rgba(255, 255, 255, 0.10);
  --hover: rgba(255, 255, 255, 0.06);
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-6: #008300;
  --series-7: #9085e9;
  --series-8: #e66767;
  --series-rest: #c9c8c1;
  --div-mid: #383835;
  --delta-up: #0ca30c;
}

/* Sequential ramp for dark: the same blue hue, stepped for the dark surface, so the
   lightest step recedes toward the surface in both modes rather than glaring. */
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    --seq-100: #0d366b; --seq-150: #104281; --seq-200: #184f95; --seq-250: #1c5cab;
    --seq-300: #256abf; --seq-350: #2a78d6; --seq-400: #3987e5; --seq-450: #5598e7;
    --seq-500: #6da7ec; --seq-550: #86b6ef; --seq-600: #9ec5f4; --seq-650: #b7d3f6;
    --seq-700: #cde2fb;
  }
}
:root[data-theme="dark"] {
  --seq-100: #0d366b; --seq-150: #104281; --seq-200: #184f95; --seq-250: #1c5cab;
  --seq-300: #256abf; --seq-350: #2a78d6; --seq-400: #3987e5; --seq-450: #5598e7;
  --seq-500: #6da7ec; --seq-550: #86b6ef; --seq-600: #9ec5f4; --seq-650: #b7d3f6;
  --seq-700: #cde2fb;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

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

a { color: var(--series-1); }

/* ---------------------------------------------------------------- masthead */
.masthead {
  position: sticky; top: 0; z-index: 40;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
}
.masthead-inner {
  max-width: 1440px; margin: 0 auto; padding: 10px 24px;
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
}
.brand { display: flex; align-items: baseline; gap: 10px; margin-right: auto; }
.brand h1 { font-size: 16px; margin: 0; letter-spacing: -0.01em; }
.brand .tag { font-size: 12px; color: var(--ink-muted); }

.nav { display: flex; gap: 2px; flex-wrap: wrap; }
.nav a {
  padding: 6px 11px; border-radius: var(--radius);
  color: var(--ink-2); text-decoration: none; font-size: 13px; white-space: nowrap;
}
.nav a:hover { background: var(--hover); color: var(--ink-1); }
.nav a[aria-current="page"] {
  background: var(--series-1); color: #fff; font-weight: 600;
}

.toggles, .links { display: flex; gap: 6px; }
.toggle {
  font: inherit; font-size: 12px; cursor: pointer;
  padding: 5px 10px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-1); color: var(--ink-2);
}
.toggle:hover { background: var(--hover); color: var(--ink-1); }

/* The paper and code chips. Same shape as a toggle, so the masthead has one control
   language; an icon plus its own text label, so the glyph never carries the meaning
   alone. Inert until its URL is filled in — see LINKS in app.js. */
.toggle.link {
  display: inline-flex; align-items: center; gap: 6px;
  text-decoration: none; color: var(--ink-2);
}
.toggle.link:hover { color: var(--ink-1); }
.toggle.link[aria-disabled="true"] { color: var(--ink-muted); cursor: default; }
.toggle.link[aria-disabled="true"]:hover { background: var(--surface-1); }
.toggle.link svg { flex: none; }

/* ------------------------------------------------------------------ layout */
main { max-width: 1440px; margin: 0 auto; padding: 22px 24px 72px; }

.page-head { margin-bottom: 18px; }
.page-head h2 { font-size: 22px; margin: 0 0 4px; letter-spacing: -0.02em; }
.page-head p { margin: 0; color: var(--ink-2); max-width: 78ch; }

/* One filter row above everything it scopes — never per-chart, never inside a card. */
.filters {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end;
  padding: 12px 14px; margin-bottom: 18px;
  background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.field { display: flex; flex-direction: column; gap: 4px; }
.field > span {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-muted);
}
.field select, .field input[type="search"] {
  font: inherit; font-size: 13px; padding: 5px 8px; min-width: 132px;
  color: var(--ink-1); background: var(--surface-1);
  border: 1px solid var(--axis); border-radius: var(--radius);
}
.field select:disabled { opacity: 0.45; cursor: not-allowed; }
.filters .note { font-size: 12px; color: var(--ink-muted); margin-left: auto; max-width: 34ch; }

.grid { display: grid; gap: 18px; }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(430px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); }

/* -------------------------------------------------------------------- card */
.card {
  background: var(--surface-1);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px 18px;
}
.card-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 2px; }
.card-head h3 { font-size: 15px; margin: 0; }
.card-head .sub { font-size: 12px; color: var(--ink-2); margin: 3px 0 0; max-width: 70ch; }
.card-actions { margin-left: auto; display: flex; gap: 4px; flex-shrink: 0; }
.card-actions button {
  font: inherit; font-size: 11px; cursor: pointer; padding: 4px 9px;
  border: 1px solid var(--border); background: transparent; color: var(--ink-2);
  border-radius: var(--radius);
}
.card-actions button:hover { background: var(--hover); color: var(--ink-1); }
.card-actions button[aria-pressed="true"] {
  background: var(--ink-1); color: var(--surface-1); border-color: var(--ink-1);
}
.card-note { font-size: 12px; color: var(--ink-muted); margin: 10px 0 0; }

/* Charts hold their previous render at reduced opacity while data reloads —
   no skeleton flash, no layout jump. */
.chart { width: 100%; transition: opacity 140ms ease; }
.chart.is-stale { opacity: 0.45; }
.chart-wrap[hidden], .table-wrap[hidden], .chart[hidden] { display: none; }

/* ------------------------------------------------------------- stat tiles */
.tiles {
  display: grid; gap: 12px; margin-bottom: 18px;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
}
.tile {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 13px 15px;
}
.tile .label { font-size: 12px; color: var(--ink-2); }
.tile .value {
  font-size: 27px; line-height: 1.15; margin-top: 3px; letter-spacing: -0.02em;
  /* proportional figures: tabular-nums makes large standalone numbers read loose */
}
.tile .unit { font-size: 12px; color: var(--ink-muted); margin-top: 2px; }
.hero { font-size: 52px; line-height: 1.05; letter-spacing: -0.03em; }

/* ------------------------------------------------------------------ tables */
.table-wrap { overflow: auto; max-height: 620px; }
table.data { border-collapse: collapse; width: 100%; font-size: 13px; }
table.data th, table.data td {
  padding: 6px 10px; text-align: right; border-bottom: 1px solid var(--grid);
  white-space: nowrap;
}
table.data th {
  position: sticky; top: 0; background: var(--surface-1); z-index: 1;
  font-weight: 600; color: var(--ink-2); text-align: right;
  border-bottom: 1px solid var(--axis);
}
table.data th:first-child, table.data td:first-child { text-align: left; }
table.data th button {
  font: inherit; font-weight: 600; color: inherit; text-align: inherit;
  background: none; border: 0; padding: 0; cursor: pointer;
}
table.data th button:hover { color: var(--ink-1); }
table.data th button::after { content: ""; font-size: 8px; margin-left: 4px; opacity: 0.6; }
table.data th[aria-sort="ascending"] button::after { content: "▲"; }
table.data th[aria-sort="descending"] button::after { content: "▼"; }
table.data td.num { font-variant-numeric: tabular-nums; }
table.data tbody tr:hover { background: var(--hover); }
table.data td.name { white-space: normal; min-width: 190px; }

/* --------------------------------------------------------------- swatches */
.legend { display: flex; gap: 14px; flex-wrap: wrap; margin: 10px 0 0; font-size: 12px; }
.legend .item { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-2); }
.legend .key { width: 11px; height: 11px; border-radius: 2px; flex-shrink: 0; }
.legend .key.line { height: 2px; width: 15px; border-radius: 1px; }
/* The hatch that marks a partially covered mark: same texture, same angle, same stripe
   colour as the decal the chart draws. `--stripe` is set inline from tokens.hatchInk() on
   the swatch's own hue; the fallback is for a swatch that arrives without one. */
.legend .key.hatched {
  background-image: repeating-linear-gradient(
    -45deg, var(--stripe, rgba(255, 255, 255, 0.85)) 0 1px, transparent 1px 3px);
}

/* --------------------------------------------------------------- gallery */
.gallery { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); }
.shot {
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; background: var(--surface-1); cursor: pointer; padding: 0;
  font: inherit; color: inherit; text-align: left; display: block;
}
.shot:hover { border-color: var(--series-1); }
.shot[aria-pressed="true"] { outline: 2px solid var(--series-1); outline-offset: -2px; }
.shot img { display: block; width: 100%; height: 132px; object-fit: cover; background: var(--grid); }
.shot .cap { padding: 7px 9px; font-size: 11.5px; line-height: 1.35; }
.shot .cap b { display: block; font-weight: 600; }
.shot .cap span { color: var(--ink-muted); }

.pair {
  display: grid; gap: 18px; margin-bottom: 18px;
  grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
}
.pair figure { margin: 0; }
/* The H&E adopts the cell map's box, not its own intrinsic one: `contain` inside a
   --map-h tall box with --map-inset padding reproduces the map's letterbox exactly, so the
   tissue lands in the same rectangle in both panels. With the image at its intrinsic
   aspect instead, a wide section drew 430x179 beside a 560-tall canvas and a tall one drew
   430x1503 — the same coordinates at two scales, which reads as misalignment because it is. */
.pair figure img {
  width: 100%; height: var(--map-h); padding: var(--map-inset);
  object-fit: contain; object-position: center;
  border-radius: var(--radius); border: 1px solid var(--border);
  display: block; background: var(--surface-1);
}
/* Both media boxes must start at the same y or the panels are offset by however many lines
   the two subtitles happened to wrap to. Two lines of `.sub` are reserved in both. */
.pair .card-head { min-height: 52px; }
.pair figcaption { font-size: 12px; color: var(--ink-2); margin-top: 6px; }

/* ----------------------------------------------------------------- facets */
/* Small multiples inside one card. Every panel keeps the same box, so the tissue
   outlines stay comparable across them, and every panel carries its own legend —
   which is what licenses the same three hues in all four: dots in different panels
   can never land side by side, so each panel's pairlist is its own. */
/* Carries the gap to whatever follows it. No display rule of its own — the view hides
   this host at the coarse level, and a display declaration would beat [hidden]. */
.facet-host { margin-bottom: 18px; }
.facets {
  display: grid; gap: 14px; margin-top: 10px;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.facet {
  border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 10px 10px;
}
.facet h4 {
  font-size: 13px; margin: 0 0 4px; display: flex; align-items: center; gap: 7px;
}
/* The panel's mark is a rule, not a square, because it means something different from the
   swatches below it: it says which band of the map above this panel opens up. Under Other/QC
   the two would otherwise be the same gray in the same shape, one standing for the parent
   and one for the pooled tail. */
.facet h4 .key { width: 3px; height: 13px; border-radius: 1px; flex: none; }
.facet h4 .badge { font-weight: 400; font-size: 11.5px; color: var(--ink-muted); }
.facet .legend { margin-top: 6px; gap: 10px; font-size: 11.5px; }
.facet .card-note { margin-top: 6px; }

/* ------------------------------------------------------------------- plate */
/* A figure taken from the paper, shipped as a raster. It keeps a light panel in dark mode
   on purpose: the diagram's own strokes are dark and its background is part of the
   artwork, so tinting the panel would leave the drawing on the wrong ground. Padding
   inside the border is what makes that read as a printed plate rather than as a hole. */
.plate { margin: 10px 0 0; }
.plate .sheet {
  display: block; padding: 10px; background: #fcfcfb;
  border: 1px solid var(--border); border-radius: var(--radius);
}
.plate img { display: block; width: 100%; }
.plate figcaption {
  font-size: 12px; color: var(--ink-2); margin-top: 8px; max-width: 96ch; line-height: 1.5;
}

/* ------------------------------------------------------------------- prose */
.prose { max-width: 82ch; }
.prose h3 { font-size: 16px; margin: 26px 0 6px; }
.prose h3:first-child { margin-top: 0; }
.prose p, .prose li { color: var(--ink-2); }
.prose code {
  font-size: 12.5px; padding: 1px 5px; border-radius: 3px;
  background: var(--hover); color: var(--ink-1);
}
.prose table.data th, .prose table.data td { white-space: normal; }
.callout {
  border-left: 3px solid var(--warning); padding: 10px 14px; margin: 14px 0;
  background: var(--hover); border-radius: 0 var(--radius) var(--radius) 0;
}
.callout p { margin: 0; color: var(--ink-1); }

.footnote { color: var(--ink-muted); font-size: 12px; margin-top: 34px; max-width: 82ch; }

@media (max-width: 720px) {
  .masthead-inner, main { padding-left: 14px; padding-right: 14px; }
  .grid.cols-2, .grid.cols-3 { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .chart { transition: none; }
}
