/* ===========================================================================
   Ersilia in numbers — site layer.

   Cascades AFTER assets/ersilia.css (the canonical house style, which owns the
   brand tokens and the component library: .card, .stat, .pill, .badge,
   table.data, .hovertip, .modal, .credit, .brandhead, .eyebrow).

   Single light theme by house rule — there is deliberately no dark palette.

   Two principles, both deliberate:
     1. NEUTRAL CHROME, COLOUR IN THE DATA. Navigation, headers and cards are grey;
        the palette belongs to the marks. So colour always means "this is a
        category" and never "this is page four".
     2. Chart marks use snapped brand hues, because the raw ones fail the
        colour-vision gates. See the note above --chart-1.

   Chrome is also LIGHT: nav, buttons and pills sit at weight 450, and selection is
   shown with a quiet fill rather than a filled pill. Weight belongs on data.
   =========================================================================== */

:root {
  /* --- Tab hues -----------------------------------------------------------
     Each sidebar tab carries its own colour, applied LIGHTLY: the label is tinted
     toward its hue but stays quiet, and the hue only comes forward on hover and for
     the current tab. No dots and no heavy edge — those read as decoration and as a
     stray dark bar respectively.

     This is navigation colour only. The charts do NOT inherit it: painting a whole
     page in one hue made every page monochrome, which is the opposite of using a
     palette. Chart colour is categorical and global — see the chart steps below.

     Same hues as the chart set, in nav order, so the sidebar doubles as a key to
     the palette. */
  --tab-overview:     #6B6675;  /* muted — home is not a category */
  --tab-models:       #6d5de7;  /* periwinkle */
  --tab-projects:     #22bbad;  /* turquoise  */
  --tab-publications: #734080;  /* plum       */
  --tab-repositories: #67bb55;  /* lime       */
  --tab-community:    #af5cc7;  /* orchid     */
  --tab-reach:        #d19710;  /* amber      */
  --tab-outreach:     #1c7db0;  /* cobalt     */

  /* --- Multi-series chart marks -------------------------------------------
     The global categorical set. Any chart on any page may draw on it: a one-series
     chart takes slot 1, a composition takes slots in order.

     Snapped from the brand hues by the dataviz procedure and validated against the
     card surface: adjacent CVD ΔE 20.0 (target >= 8) · normal-vision ΔE 20.6
     (floor 15). The ORDER is the safety mechanism — never cycle past slot 6; a 7th
     series folds into "Other" or the chart gets faceted. All-pairs forms (scatter,
     choropleth, heatmap) carry at most two of these.

     CRIMSON IS LAST, AND THAT IS THE WHOLE POINT OF THIS ORDER. It used to be slot
     2, so every two-series chart on the site came out periwinkle-versus-red and
     half the dashboard read as an alarm: "Left", "External", "Blog posts",
     "Featurization" and "Science" were all painted the same red as a failure. Red
     carries a verdict whether or not one is meant, so it now sits where only a
     genuine sixth category can reach it. Slots 1+2 — the common case — are
     periwinkle and amber, which say "two different things" and nothing more.

     Reordering is NOT free: the gates above are adjacency-sensitive. Cobalt in slot
     2 fails the normal-vision floor beside periwinkle (ΔE 14.7, both read blue) and
     orchid beside cobalt fails deutan (ΔE 5.8). Re-run
     scripts/validate_palette.js from the dataviz skill before touching this list.

     ACCEPTED CHECKER FINDING: check_html.py reports T1-COLOR-OFFBRAND here,
     because these are not literal ersilia.css entries. They cannot be — the raw
     brand hues fail the legibility gates (amber at OKLCH L 0.84 is outside the
     0.43–0.77 band; cobalt at chroma 0.078 is under the 0.10 floor where a hue
     stops reading as a hue) and color-mix() can lift neither a chroma floor nor
     produce a validated order. Do not "fix" it by substituting the raw hexes:
     that trades a cosmetic pass for charts colourblind readers cannot read. */
  --chart-1: #6d5de7;  /* periwinkle */
  --chart-2: #e2a72e;  /* amber      */
  --chart-3: #247dad;  /* cobalt     */
  --chart-4: #6cbf5a;  /* lime       */
  --chart-5: #af5cc7;  /* orchid     */
  --chart-6: #e63745;  /* crimson — last on purpose; see above */

  /* Sequential ramp for magnitude (choropleth, heatmaps, ordinal buckets):
     one hue, monotone lightness, light end >= 2:1 on white. Validated
     with --ordinal. */
  --seq-1: #b2b4d7;
  --seq-2: #9495d1;
  --seq-3: #7876ca;
  --seq-4: #5f55c2;
  --seq-5: #492eb8;

  /* Neutrals for "no data" regions and non-encoding marks — derived from the
     house neutrals rather than picked, and resolved to rgb() at runtime by
     js/tokens.js so ECharts can parse them. */
  --chart-null: color-mix(in srgb, var(--border) 62%, var(--surface));
  --chart-axis: color-mix(in srgb, var(--border) 78%, var(--surface));

  /* --- Type scale ---------------------------------------------------------
     SIX sizes, and a 12px floor. This replaced nineteen distinct sizes, eleven of
     them squeezed between 8px and 14px with half-pixel steps (10.5, 11.5, 12.5,
     13.5). A half-pixel step is not hierarchy — nobody perceives it — but a dozen
     of them together read as an unsystematic page, which is most of why this site
     looked amateur while every individual chart was fine.

     Datawrapper's guidance is two clearly-different levels, "like a 12px gray and
     a 14px black", and warns that many font sizes "can quickly look messy". Chart
     text uses exactly two: --fs-meta in --muted for labels, --fs-body in --ink for
     anything to be read first.

     12px is also the floor every mainstream design system sets for body text; this
     file previously went down to 8px and set every chart axis label at 10px. Do not
     add a seventh size, and never go below --fs-meta. If something does not fit,
     show less of it — shorten the label or drop every other tick. */
  --fs-meta:  12px;   /* chart axis + value labels, meta chips, legends, table cells */
  --fs-body:  13px;   /* captions, nav, controls, footer — the workhorse */
  --fs-title: 15px;   /* card titles */
  --fs-view:  20px;   /* view heading */
  --fs-kpi:   26px;   /* hero stat values */
  --fs-hero:  32px;   /* the single largest number on the landing page */

  /* --- Spacing scale ------------------------------------------------------
     IBM Carbon's scale. Every padding, margin and gap comes from here. The file
     previously used 22 different values, 13 of them off any 4px grid (3, 5, 6, 7,
     9, 10, 11, 13, 14, 15, 18, 22, 26px) — hand-nudged one-offs, which is the
     visual signature of "adjusted until it looked right".

     1px and 2px still appear directly for hairlines and borders: those are strokes,
     not spacing. */
  --sp-1:  2px;
  --sp-2:  4px;
  --sp-3:  8px;
  --sp-4: 12px;
  --sp-5: 16px;
  --sp-6: 24px;
  --sp-7: 32px;

  /* --- Text colour floor --------------------------------------------------
     --faint (#9A93A6) is 2.96:1 against white. That is below the WCAG AA floor for
     normal text (4.5:1) and below even the large-text floor (3:1), and it was being
     used for every chart axis label, every value label and eight kinds of UI text at
     9.5–11px. It is why the charts looked washed out as well as small.

     --faint is now for MARKS ONLY — gridlines, hairline leaders, the "no data" map
     fill — where no contrast minimum applies. Any text uses --muted (5.55:1) or
     --ink (10.98:1). Do not put --faint on a colour property that paints glyphs. */

  --sidebar-w: 264px;
  --content-w: 1080px;
  --gap: var(--sp-4);
}

/* Off-screen until focused. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 60;
  padding: var(--sp-3) var(--sp-4); background: var(--surface);
  border: 1px solid var(--brand); border-radius: var(--radius-sm);
  font-size: var(--fs-body); color: var(--ink); text-decoration: none;
}
.skip:focus { left: var(--sp-3); top: var(--sp-3); }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
main:focus { outline: none; }

/* ===========================================================================
   Shell — fixed sidebar, content scrolls
   =========================================================================== */
.shell { min-height: 100vh; }

.sidebar {
  position: fixed; inset: 0 auto 0 0; width: var(--sidebar-w); z-index: 40;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  background: var(--surface);
  border-right: 1px solid var(--border);
  overflow-y: auto;
}
.sidebar .brandhead { padding: 0 var(--sp-4); margin-bottom: var(--sp-6); gap: 2px; }
/* The organisation name links out; the title below it does not — it is where you are. */
.sidebar .eyebrow { font-size: var(--fs-meta); font-weight: 500; text-decoration: none; }
.sidebar a.eyebrow:hover { text-decoration: underline; text-underline-offset: 2px; }
/* The wordmark is the page's single <h1>, so it needs the heading margin off. */
.sidebar .wordmark { font-size: var(--fs-title); margin: 0; letter-spacing: -.015em; }

/* Read before any figure, so it sits under the title rather than at the foot of the
   column. A caveat, not an alarm: amber on pale amber, with a left rule to catch the
   eye on the way down the nav. It does not shout and it cannot be missed. */
.sidebar .devnote {
  margin: -var(--sp-3) 0 var(--sp-5); padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-sm);
  border-left: 2px solid var(--warn);
  font-size: var(--fs-meta); line-height: 1.45; color: var(--ink);
  background: color-mix(in srgb, var(--warn) 14%, var(--surface));
}
.sidebar .devnote strong { font-weight: 600; color: color-mix(in srgb, var(--warn) 78%, #000); }

.sidebar nav ul { list-style: none; margin: 0; padding: 0; }
/* Each tab has its own colour, but the WORDS stay neutral — coloured label text
   reads as decoration and makes a column of eight look busy. The hue lives in the
   fill behind the current tab and in a light wash on hover, so the colour is
   present and calm and the type is always plain ink. No dots, no heavy edge. */
.sidebar nav a {
  display: block;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-sm);
  /* Light chrome: no semibold anywhere in the furniture. */
  font-size: var(--fs-body); font-weight: 450; color: var(--muted);
  transition: color var(--tap), background var(--tap);
}
.sidebar nav a:hover {
  text-decoration: none; color: var(--ink);
  background: color-mix(in srgb, var(--tab, var(--brand)) 9%, var(--surface));
}
.sidebar nav a[aria-current="page"] {
  font-weight: 500; color: var(--ink);
  background: color-mix(in srgb, var(--tab, var(--brand)) 16%, var(--surface));
}
/* Keyboard focus must be obvious and must not look like selection. */
.sidebar nav a:focus-visible,
.sidebar .foot a:focus-visible, .sidebar .foot button:focus-visible,
.toggle button:focus-visible, a.jump:focus-visible, a.dl:focus-visible,
.info:focus-visible, .methods-btn:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--radius-sm);
}

.sidebar .foot { margin-top: auto; padding: var(--sp-5) var(--sp-3) 0; border-top: 1px solid var(--border); }
.sidebar .stalewarn {
  display: block; margin-top: var(--sp-1);
  color: color-mix(in srgb, var(--warn) 80%, #000);
  font-family: var(--sans); font-size: var(--fs-meta);
}
.sidebar .foot .snapshot {
  display: block; font-family: var(--mono); font-size: var(--fs-meta);
  color: var(--muted); margin-bottom: var(--sp-3);
}
.sidebar .foot a, .sidebar .foot button {
  display: block; width: 100%; text-align: left; padding: 0; margin-bottom: var(--sp-3);
  background: none; border: 0; border-radius: 0;
  font-family: var(--sans); font-size: var(--fs-body); font-weight: 450; color: var(--muted);
  cursor: pointer;
}
.sidebar .foot a:hover, .sidebar .foot button:hover {
  color: var(--brand); background: none; text-decoration: none;
}

main {
  margin-left: var(--sidebar-w);
  padding: var(--sp-6) clamp(var(--sp-5), 3vw, var(--sp-7)) var(--sp-7);
}
main > * { max-width: var(--content-w); }

/* ---------- Section header --------------------------------------------- */
/* Title and blurb on the left, "go and see it" links on the right, closed by a
   hairline that spans both. */
.viewhead {
  margin: 0 0 var(--sp-5);
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: start; column-gap: var(--sp-6);
}
.viewhead h2 {
  font-size: var(--fs-view); font-weight: 600; letter-spacing: -.015em; margin: 0 0 var(--sp-2);
  grid-column: 1;
}
.viewhead p { margin: 0; color: var(--muted); max-width: 74ch; font-size: var(--fs-body);
  grid-column: 1; }
.headlinks {
  grid-column: 2; grid-row: 1 / span 2;
  display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: flex-end;
  padding-top: 1px;
}
.headlinks a {
  font-size: var(--fs-body); font-weight: 450; color: var(--muted); text-decoration: none;
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border); border-radius: 999px;
  white-space: nowrap; background: var(--surface);
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.headlinks a:hover {
  color: var(--tab, var(--brand));
  border-color: color-mix(in srgb, var(--tab, var(--brand)) 40%, var(--border));
  background: color-mix(in srgb, var(--tab, var(--brand)) 7%, var(--surface));
}
/* A hairline, not a coloured band. The heading is the signal; the rule just closes it. */
.viewhead::after {
  content: ""; display: block; height: 1px; margin-top: var(--sp-5);
  background: var(--border); grid-column: 1 / -1;
}
@media (max-width: 720px) {
  .viewhead { grid-template-columns: minmax(0, 1fr); }
  .headlinks { grid-column: 1; grid-row: auto; justify-content: flex-start; margin-top: var(--sp-4); }
}

/* ===========================================================================
   Stat tiles
   =========================================================================== */
.stat-row {
  display: grid; gap: var(--gap); margin: 0 0 var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
}
.stat-row.primary { grid-template-columns: repeat(4, 1fr); }
.stat { position: relative; overflow: hidden; padding: var(--sp-4) var(--sp-5) var(--sp-4); }
.stat .k { font-size: var(--fs-meta); font-weight: 500; }
.stat .v {
  font-size: var(--fs-kpi); margin-top: 1px;
  /* Proportional figures on a large standalone number: tabular-nums makes 121
     look loose at display sizes. Tabular alignment is for table rows and ticks. */
  font-variant-numeric: normal;
}
.stat.hero { padding: var(--sp-5) var(--sp-5) var(--sp-4); }
.stat.hero .v { font-size: var(--fs-hero); line-height: 1.06; }
.stat .d { display: flex; align-items: baseline; gap: var(--sp-2); margin-top: 2px; }
/* Quiet delta: no mono, no semibold, no loud green. A hue arrow carries direction. */
.stat .delta { font-size: var(--fs-meta); font-weight: 450; color: var(--muted); }
.stat .delta::before { content: "▲ "; color: var(--good); font-size: var(--fs-meta); }
.stat .delta.down::before { content: "▼ "; color: var(--muted); }
.stat .delta.flat::before { content: "· "; color: var(--muted); }
.stat .spark { display: block; width: 100%; height: 24px; margin-top: var(--sp-3); }

/* ===========================================================================
   Rows of twelve columns.

   The layout is a complete tessellation, not a flow: charts are grouped into rows
   whose spans sum to 12, and every card in a row stretches to the same height with
   its chart flexing to fill. That is the difference between a dashboard and a pile
   of cards — no gaps, no ragged bottoms, every edge aligned.
   =========================================================================== */
.chart-grid { display: flex; flex-direction: column; gap: var(--gap); }
.crow {
  display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gap);
  align-items: stretch;
}
/* Row heights. The chart inside absorbs whatever the title and caption leave. */
.crow.h-sm   { min-height: 188px; }
.crow.h-md   { min-height: 264px; }
.crow.h-lg   { min-height: 344px; }
.crow.h-xl   { min-height: 420px; }
.crow.h-map  { min-height: 452px; }
.crow.h-tall { min-height: 640px; }
.crow > .card { height: 100%; }
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; }
.span-6 { grid-column: span 6; }
.span-7 { grid-column: span 7; }
.span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }

/* ===========================================================================
   Chart panels
   =========================================================================== */
.card { display: flex; flex-direction: column; padding: var(--sp-4) var(--sp-5) var(--sp-4); }
.card.lead { padding: var(--sp-5) var(--sp-5) var(--sp-4); }
.phead { display: flex; align-items: flex-start; gap: var(--sp-3); margin: 0 0 1px; }
.phead h3 {
  margin: 0; flex: 1; font-size: var(--fs-title); font-weight: 500;
  letter-spacing: -.005em; color: var(--ink);
  /* Clamped at two lines so a long title can never push the chart out of the card,
     but with NO reserved height: reserving it left an empty line under every
     one-line title, which was far more visible than the ~19px of chart-top
     misalignment it was there to prevent. The card edges still align because
     .chart absorbs the difference. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* The lead card is distinguished by weight, not by another size — adding a seventh
   step to the scale to make one card bigger is how the old nineteen happened. */
.card.lead .phead h3 { font-weight: 600; }
/* The narrowest cards take the smaller of the two text sizes for their caption. This
   is not a third size — it is the other level of the same two-level scale — and it buys
   the ~8% of characters that stops a 3-column caption sitting one glyph from the clamp.
   Font metrics differ between platforms (the CI runner's Linux fonts are slightly wider
   than macOS's), and a layout should not depend on which machine rendered it. */
.span-3 .insight, .span-4 .insight { font-size: var(--fs-meta); }
.span-3 .insight { min-height: calc(2 * 1.4 * var(--fs-meta)); }
.span-4 .insight { min-height: calc(2 * 1.4 * var(--fs-meta)); }
.pmeta { display: flex; align-items: center; gap: var(--sp-3); flex: 0 0 auto; }
.pcount {
  font-family: var(--mono); font-size: var(--fs-meta); color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The computed takeaway — the chart's caption, not chrome.
   TWO lines, always reserved. It used to be clamped to one, which kept the cards
   aligned but meant a three-column card could only carry ~40 characters before the
   ellipsis ate the sentence; captions were being written to fit the clamp rather
   than to say something. Reserving a fixed two-line box gets the alignment without
   the truncation: a one-line caption simply leaves the second line empty, so every
   card in a row still starts its chart at the same y. */
.insight {
  margin: var(--sp-1) 0 var(--sp-3);
  font-size: var(--fs-body); line-height: 1.4; color: var(--muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(2 * 1.4 * var(--fs-body));
}

/* The chart takes the space the card has left. A floor stops ECharts initialising
   into a zero-height box before the flex layout settles; a ResizeObserver in
   cards.js re-fits it once it has real dimensions. */
/* The chart box takes the space the card has left; the chart itself is taken OUT OF
   FLOW inside it.
   
   Both halves matter. flex-basis 0 (not auto) stops the box asking for as much room as
   its content. Absolute positioning stops the content asking at all: echarts.init()
   writes an explicit pixel height onto an in-flow inner div, and because card heights
   are fractional, every resize() rounded that up by a pixel, which grew the card, the
   row, and therefore the chart — a ratchet that took a row from 309px past 800px and
   kept climbing. Out of flow, the wrapper's height comes from the row and nothing
   inside it can push back. */
/* overflow: hidden is the load-bearing declaration, not decoration.
   A flex item that clips cannot be inflated by its own content, and the content here
   inflates: echarts.init() appends an IN-FLOW inner div and writes an explicit pixel
   height onto it. Card heights are fractional (a 410.594px card leaves the chart
   290.59px), so every resize() rounded that up by a pixel and rewrote the div; in flow,
   that pixel counted toward the card's content height, grew the auto-sized grid row,
   gave the chart a bigger share, and rounded up again next frame. A 1px-per-frame
   ratchet took one row from 309px past 800px and kept climbing — the panels looked like
   they were leaking.
   Clipping breaks the feedback path: the box's height comes from the row, its content
   cannot push back, and a stray rounded-up pixel is simply cut off.
   Do NOT position .chart absolutely to achieve this — echarts.init() writes
   `position: relative` inline on its container and would override it, which collapses
   the chart to zero height and renders nothing at all. */
.chartbox {
  position: relative; width: 100%;
  flex: 1 1 0%; min-height: 110px;
  overflow: hidden;
}
/* `position: absolute` takes the chart OUT OF FLOW, which is the only thing that
   actually breaks the loop — clipping the wrapper is not enough, because the wrapper
   still sizes to its in-flow content.
   
   The !important is deliberate and is the one place in this stylesheet that needs it:
   echarts.init() writes `position: relative` as an INLINE style on its container, and an
   inline style beats a normal author rule. An important author rule beats an inline one,
   which is exactly the cascade level this calls for — overriding a library's inline
   style. Dropping the !important collapses the chart to zero height and it renders
   nothing; dropping `absolute` restores the ratchet. Both were observed. */
.chart { position: absolute !important; inset: 0; }
/* HTML components grow into the card and centre their content. Left at natural
   height they sat at the top of a stretched card with dead space beneath, which is
   exactly the ragged look the row tessellation is meant to remove. */
.meters, .shares { flex: 1 1 auto; justify-content: center; }
table.ranked { flex: 0 0 auto; }

/* Methodology marker — tier 2 of progressive disclosure. */
.info {
  flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface-2);
  color: var(--muted); font: 450 var(--fs-meta)/18px var(--sans); text-align: center;
  cursor: help; padding: 0;
}
.info:hover { color: var(--brand); border-color: var(--brand); }
.hovertip[data-tip]::after { left: auto; right: 0; }

/* Metric toggles — quiet until pressed, then the section hue. */
.toggle { display: flex; gap: 2px; }
.toggle button {
  padding: 2px var(--sp-3); font-size: var(--fs-meta); font-weight: 450; border-radius: 999px;
  color: var(--muted); background: none; border: 1px solid transparent;
}
.toggle button:hover { background: var(--surface-2); }
.toggle button[aria-pressed="true"] {
  background: var(--surface-2); border-color: var(--border); color: var(--ink);
}

/* ===========================================================================
   Meter rows — a label, a number and a thin bar. Not every ratio is a chart.
   =========================================================================== */
.meters { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-2) 0 2px; }
.meter .top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4); }
.meter .lbl { font-size: var(--fs-body); color: var(--ink); font-weight: 450; }
.meter .val {
  font-family: var(--mono); font-size: var(--fs-meta); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.meter .track {
  height: 4px; border-radius: 999px; background: var(--surface-2); margin-top: var(--sp-2);
  overflow: hidden;
}
.meter .fill { height: 100%; border-radius: 999px; background: var(--chart-1); }

/* ===========================================================================
   Ranked table — top-N with an inline microbar. Replaces three bar charts.
   =========================================================================== */
table.ranked { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
table.ranked th {
  font-size: var(--fs-meta); font-weight: 500; color: var(--muted); text-align: right;
  padding: 0 0 var(--sp-3); border-bottom: 1px solid var(--border);
}
table.ranked th:first-child { text-align: left; }
table.ranked td { padding: var(--sp-2) 0; border-bottom: 1px solid var(--surface-2); }
table.ranked td.name { color: var(--ink); max-width: 0; width: 42%; }
table.ranked td.name span {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
table.ranked td.num {
  text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--muted); width: 52px;
}
/* Adjacent right-aligned columns need a gutter or they touch: at 390px "389" and "2018"
   rendered as "3892018", which is not a near-miss but an unreadable number. */
table.ranked th + th, table.ranked td.num, table.ranked td.txt { padding-left: var(--sp-3); }
/* A verbatim column: a year or a Yes/No. Right-aligned like the numbers so the columns
   line up, but not thousands-separated and not treated as a quantity. */
table.ranked td.txt {
  text-align: right; font-family: var(--mono); color: var(--muted); width: 52px;
}
/* On a phone the microbar is the first thing to go. It is a redundant encoding — the
   number it duplicates is in the adjacent column — and it was taking 28% of the width
   that four columns of text needed. */
@media (max-width: 720px) {
  table.ranked td.barcell, table.ranked th:last-child { display: none; }
  table.ranked td.name { width: 44%; }
}
table.ranked td.barcell { width: 28%; padding-left: var(--sp-4); }
table.ranked .microbar { height: 4px; border-radius: 999px; background: var(--chart-1); min-width: 2px; }
table.ranked tbody tr:hover td { background: var(--surface-2); }

/* ===========================================================================
   Share rows — several two-category splits stacked compactly in one card.
   =========================================================================== */
.shares { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-2) 0 2px; }
.share .top {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-2);
}
.share .lbl { font-size: var(--fs-body); color: var(--ink); font-weight: 450; }
.share .pct {
  font-family: var(--mono); font-size: var(--fs-meta); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.share .track { display: flex; height: var(--sp-3); border-radius: 999px; overflow: hidden; gap: 2px; }
.share .a { background: var(--chart-1); }
.share .b { background: var(--surface-2); }
.share .legend { display: flex; gap: var(--sp-4); margin-top: var(--sp-2); font-size: var(--fs-meta); color: var(--muted); }
.share .legend b { font-weight: 450; color: var(--muted); font-family: var(--mono); }

/* ===========================================================================
   Drill-down table (progressive disclosure + the contrast relief for the two
   sub-3:1 chart hues)
   =========================================================================== */
/* The affordance is a button opening a dialog, so it has a FIXED height: whether or
   not the table is showing, the card is exactly as tall. That is the point — as a
   <details> it expanded in place and stretched every chart in its grid row. */
.drill { margin-top: var(--sp-3); border-top: 1px solid var(--surface-2); flex: 0 0 auto; }
.drill > button {
  cursor: pointer; padding: var(--sp-3) 0 0; margin: 0;
  background: none; border: 0; border-radius: 0;
  font-family: var(--sans); font-size: var(--fs-meta); font-weight: 450; color: var(--muted);
  display: flex; align-items: center; gap: var(--sp-2);
}
.drill > button::before { content: "▤"; color: var(--muted); font-size: var(--fs-meta); }
.drill > button:hover { color: var(--brand); background: none; }
.drill > button:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--radius-sm);
}
/* In the dialog the table has the whole width, so it can be taller before scrolling. */
#datamodal .scrollwrap { max-height: 58vh; }
/* The methodology note, when the ⓘ is opened rather than hovered. */
#datamodal .notetext { margin: 0; font-size: var(--fs-body); line-height: 1.55; color: var(--ink); }
#datamodal .dl { font-size: var(--fs-body); margin-top: var(--sp-4); display: inline-block; }

/* ===========================================================================
   Landing
   =========================================================================== */
.strip {
  display: flex; flex-wrap: wrap; gap: 0; margin: 0 0 var(--sp-6);
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-sm); overflow: hidden;
}
.strip .cell {
  flex: 1 1 130px; padding: var(--sp-4) var(--sp-5);
  border-right: 1px solid var(--border);
}
.strip .cell:last-child { border-right: 0; }
.strip .k { font-size: var(--fs-meta); color: var(--muted); font-weight: 450; }
.strip .v {
  font-family: var(--mono); font-size: var(--fs-title); color: var(--ink);
  font-variant-numeric: normal; margin-top: 1px;
}

.section-label {
  margin: var(--sp-6) 0 var(--sp-4); font-size: var(--fs-meta); font-weight: 500; color: var(--muted);
}

/* 12 columns so seven cards tile as 3+3+3+3 then 4+4+4 — no empty slot. */
.jump-grid { display: grid; gap: var(--gap); grid-template-columns: repeat(12, 1fr); }
.jump-grid > a:nth-child(-n+4) { grid-column: span 3; }
.jump-grid > a:nth-child(n+5) { grid-column: span 4; }
a.jump {
  display: flex; flex-direction: column; gap: var(--sp-2);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--sp-4) var(--sp-5) var(--sp-4);
  box-shadow: var(--shadow-sm); color: var(--ink);
  border-top: 2px solid var(--slot, var(--chart-1));
  transition: box-shadow var(--tap), transform var(--tap);
}
a.jump:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-1px); }
a.jump .t {
  font-size: var(--fs-body); font-weight: 500; display: flex;
  align-items: baseline; justify-content: space-between; gap: var(--sp-3);
}
a.jump .n {
  font-family: var(--mono); font-variant-numeric: normal;
  color: var(--slot, var(--chart-1)); font-size: var(--fs-title);
}
a.jump .s { font-size: var(--fs-meta); color: var(--muted); line-height: 1.45; }
a.jump .spark { height: 20px; margin-top: 2px; }

/* ===========================================================================
   Downloads
   =========================================================================== */
.dl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--sp-5) var(--sp-6); }
.dl-group h4 { margin: 0 0 var(--sp-3); font-size: var(--fs-body); font-weight: 500; color: var(--ink); }
.dl-group h4::before {
  content: ""; display: inline-block; width: var(--sp-3); height: var(--sp-3); border-radius: 50%;
  background: var(--slot, var(--chart-1)); margin-right: var(--sp-3);
}
.dl-links { display: flex; flex-direction: column; gap: var(--sp-2); }
a.dl { display: inline-flex; align-items: baseline; gap: var(--sp-3); font-size: var(--fs-body); color: var(--muted); }
a.dl::before { content: "↓"; color: var(--muted); }
a.dl:hover { color: var(--ink); text-decoration: none; }

/* ===========================================================================
   Methods dialog
   =========================================================================== */
.modal h3 { margin: var(--sp-5) 0 var(--sp-2); font-size: var(--fs-body); font-weight: 600; }
.modal .mrow { font-size: var(--fs-body); color: var(--muted); }
.modal .mrow b { color: var(--ink); font-weight: 500; }
.modal .close {
  position: absolute; top: var(--sp-4); right: var(--sp-5);
  font-weight: 450; font-size: var(--fs-body);
}
.methods-body .meters { margin-top: var(--sp-3); }
.modal[hidden], .scrim[hidden] { display: none; }

/* ===========================================================================
   States
   =========================================================================== */
.empty {
  display: flex; align-items: center; justify-content: center; text-align: center;
  color: var(--muted); font-size: var(--fs-meta); padding: var(--sp-6) var(--sp-4);
  border: 1px dashed var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.loading { color: var(--muted); font-size: var(--fs-meta); padding: var(--sp-7) 0; }
/* A neutral note. Deliberately NOT .wip — that is amber and reads as a warning,
   which is wrong for "this table is not in the snapshot yet". */
.note {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font-size: var(--fs-meta); font-weight: 450; color: var(--muted);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; padding: var(--sp-2) var(--sp-4); margin-bottom: var(--sp-5);
}

/* ===========================================================================
   Responsive — the sidebar becomes a scrolling strip above the content
   =========================================================================== */
@media (max-width: 1100px) {
  .jump-grid { grid-template-columns: repeat(2, 1fr); }

  /* ONE COLUMN, and this is deliberate rather than lazy.
     The previous mapping halved some spans and full-widthed others (3/4/5 -> 6,
     7/8 -> 12), which meant a row built to sum to 12 now summed to 18 and wrapped —
     leaving an orphan card beside an empty half. An incomplete tessellation is the
     specific thing this layout exists to avoid, and no per-card CSS mapping can
     preserve it, because the collapse cannot see which cards shared a row: a
     three-cell row orphans one card under any two-column scheme.
     At 1024px a single column is still ~740px wide, which is more than any of these
     charts had at 12 columns on a 1440px screen. */
  .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-12 { grid-column: span 12; }
  /* One card per line means a row's min-height would be divided between them and every
     chart would fall to its 110px floor, so the height moves to the card. */
  .crow.h-sm, .crow.h-md, .crow.h-lg, .crow.h-xl, .crow.h-map { min-height: 0; }
  .crow > .card { min-height: 320px; }
  /* A facets card holds two stacked panels, so it needs the height it had in the grid;
     at 320px each panel was ~90px and the axis labels ate most of that. */
  .crow.h-xl > .card { min-height: 430px; }
  .crow.h-map > .card { min-height: 420px; }
  .crow.h-tall > .card { min-height: 620px; }
}
@media (max-width: 900px) {
  .sidebar {
    position: static; width: auto; inset: auto;
    flex-direction: row; align-items: center; gap: var(--sp-3) var(--sp-4);
    /* Wrap, so the nav gets its own full-width line. Without this the footer links
       claimed the row and pushed the section nav off-screen entirely — the nav was
       unreachable on a phone. */
    flex-wrap: wrap; overflow: visible;
    border-right: 0; border-bottom: 1px solid var(--border);
    padding: var(--sp-4) var(--sp-5);
  }
  .sidebar .brandhead { margin: 0; flex: 1 1 auto; }
  /* Full-width second line. */
  .sidebar nav { flex: 1 0 100%; order: 3; min-width: 0; }
  .sidebar nav ul { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; }
  .sidebar nav ul::-webkit-scrollbar { display: none; }
  /* Wrap rather than scroll: with eight tabs and both scrollbars suppressed, the
     later sections were simply invisible with nothing to hint they existed. */
  .sidebar nav ul { flex-wrap: wrap; overflow-x: visible; }
  .sidebar nav a { border-bottom: 2px solid transparent; white-space: nowrap; }
  .sidebar nav a[aria-current="page"] { border-bottom-color: var(--tab, var(--ink)); }
  /* The footer links used to be `display: none` here, on the assumption that Downloads
     was reachable from the nav. It is not — renderNav() only adds the views — so hiding
     them made the Downloads view and the source link unreachable on a phone except by
     typing the hash. They stay, inline, costing one line rather than three.
     `flex: 1 0 100%` gives them their own full-width line in the sidebar's flex row;
     without it the row sized to its content and pushed the page 184px wider than the
     viewport, which is sideways scroll. */
  .sidebar .foot {
    margin: 0; padding: 0; border: 0;
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-4);
    flex: 1 0 100%; min-width: 0; order: 4;
  }
  .sidebar .foot .snapshot { margin: 0; }
  .sidebar .foot a, .sidebar .foot button { display: inline; width: auto; margin: 0; }
  main { margin-left: 0; padding-top: var(--sp-6); }
  .stat-row.primary { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-12 { grid-column: span 12; }
  .jump-grid { grid-template-columns: 1fr; }
  /* Cards stack one per row here, so a row's min-height would be split across all of
     them and every chart would collapse to its 110px floor. Give each stacked card a
     real height of its own instead. */
  .crow.h-sm, .crow.h-md, .crow.h-lg, .crow.h-xl, .crow.h-map { min-height: 0; }
  .crow > .card { min-height: 300px; }
  /* The choropleth needs the height more than anything else here: at 300px the map
     itself got ~150px, which on a phone is a world where most countries are a few
     pixels across. It cannot pan or zoom (roam is off, so it never fights the page
     scroll), so size is the only lever. */
  .crow.h-map > .card { min-height: 400px; }
  .crow.h-tall > .card { min-height: 560px; }
  main { padding-left: var(--sp-5); padding-right: var(--sp-5); }
}

/* ===========================================================================
   Motion

   Navigation should feel continuous rather than like a page reload. The view fades
   and rises a few pixels; cards follow in a short stagger so a dense page resolves
   in order instead of snapping into place all at once. Everything is fast enough to
   feel immediate — nothing here exceeds a quarter of a second.
   =========================================================================== */
:root { --ease: cubic-bezier(.2, .7, .2, 1); }

@keyframes viewIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes cardIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

#view { animation: viewIn .22s var(--ease) both; }
/* Re-trigger on every route change: app.js flips this attribute. */
#view[data-entering] { animation: viewIn .22s var(--ease) both; }

.crow > .card { animation: cardIn .26s var(--ease) both; }
.crow:nth-child(1) > .card { animation-delay: .02s; }
.crow:nth-child(2) > .card { animation-delay: .06s; }
.crow:nth-child(3) > .card { animation-delay: .10s; }
.crow:nth-child(4) > .card { animation-delay: .14s; }
.crow:nth-child(n+5) > .card { animation-delay: .16s; }

.stat, a.jump, .strip { animation: cardIn .24s var(--ease) both; }
.stat-row .stat:nth-child(2) { animation-delay: .03s; }
.stat-row .stat:nth-child(3) { animation-delay: .06s; }
.stat-row .stat:nth-child(4) { animation-delay: .09s; }

.card { transition: box-shadow .18s var(--ease), border-color .18s var(--ease); }
.card:hover { box-shadow: var(--shadow); }
a.jump:hover { transform: translateY(-2px); }
a.jump { transition: box-shadow .18s var(--ease), transform .18s var(--ease), border-color .18s var(--ease); }

/* The house standard honours prefers-reduced-motion globally for transitions;
   animations need saying separately. */
@media (prefers-reduced-motion: reduce) {
  #view, #view[data-entering], .crow > .card, .stat, a.jump, .strip {
    animation: none !important;
  }
  a.jump:hover { transform: none; }
  html { scroll-behavior: auto; }
}

/* ===========================================================================
   Print
   =========================================================================== */
@media print {
  .sidebar, .toggle, .drill, .jump-grid, .modal, .scrim { display: none !important; }
  body { background: #fff; }
  main { margin: 0; padding: 0; max-width: none; }
  main > * { max-width: none; }
  .card { break-inside: avoid; box-shadow: none; border-color: var(--faint); }
  .span-3, .span-4, .span-5 { grid-column: span 6; }
}
