/* OpenDelta — design system.
 *
 * The scene: a resident in Ogbobagbene opens this outdoors, in Delta daylight, on a
 * ₦100,000 Android and metered data, to find out whether the road they were promised
 * exists. That scene forces the theme — a dark console is unreadable in sun — and it
 * forces the weight: nothing decorative, nothing that costs a byte it does not earn.
 *
 * The register is a public-records instrument, so the material is a printed ledger:
 * paper-white page, deep sky-blue substrate carrying the structure, stone panels tinted
 * toward the same hue, solid opaque fills throughout. Green is the obvious reflex for
 * Nigerian civic software when it is worn as a badge; used as the substrate that carries
 * headers, primary actions and active state, it reads as a survey document instead.
 *
 * SOFT is tonal: no pure black, no hairline black borders, shadows tinted with the brand
 * hue, generous radii, comfortable measure.
 * SOLID is literal: every surface is opaque. No glass, no blur, no gradient, no
 * translucency standing in for hierarchy.
 */

/* ------------------------------------------------------------------ type ---
 * Public Sans, self-hosted. One family, figures included.
 *
 * The site used to pair a humanist sans with a monospace companion for figures —
 * two typefaces arguing rather than one voice at two widths. A reader who found the
 * monospace figures typewriter-ish was right: a face built for code is not built for
 * ₦2,178,408,252 read outdoors on a cheap screen. Public Sans is the US Web Design
 * System's typeface, drawn for exactly that reader — clear and sturdy at small
 * sizes, on a device with no guarantee of a good screen or a good connection.
 *
 * Figures no longer need a second, wider face to line up: Public Sans carries both
 * `tnum` (tabular figures, so a column of naira lines up) and `lnum` (lining
 * figures, so digits sit on the baseline beside capitals rather than descending like
 * old-style numerals). `font-variant-numeric: tabular-nums lining-nums` turns both
 * on wherever a figure appears, in the same voice as the words around it.
 *
 * Monospace remains, unshipped, for genuine code — `code` and `pre` — set in the
 * system's own monospace stack. Nothing here asks a reader to download a second
 * typeface just to render a `<code>` tag nobody but a developer reads.
 *
 * Self-hosted, not from a font CDN: a page on this site should not need a second
 * origin to be legible, and the service worker precaches this so the second visit
 * — the one standing at a project site with no signal — costs nothing.
 *
 * One file across the whole weight range, because this is a variable font: 100 to
 * 900 rather than one file per weight. The ₦ sign lives in the Latin Extended
 * block; Public Sans carries it directly, so unlike the old pair there is no
 * separate naira face to keep in step.
 *
 * SIL Open Font License 1.1. https://openfontlicense.org/
 */

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;           /* text is readable immediately, in the fallback */
  src: url("/static/vendor/fonts/public-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20A6, U+20AC, U+2122, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: light;

  /* --- surfaces: paper, then stone tinted toward the brand hue ------------- */
  --paper:        oklch(1 0 0);                 /* literal white. no hidden warmth */
  --stone:        oklch(0.975 0.006 236);       /* panels, table headers, sidebars */
  --stone-deep:   oklch(0.951 0.009 236);       /* nested / pressed */
  --edge:         oklch(0.906 0.010 236);       /* borders are tonal, never black */
  --edge-soft:    oklch(0.944 0.008 236);

  /* --- ink ---------------------------------------------------------------- */
  --ink:          oklch(0.245 0.019 236);       /* body — 14.2:1 on paper */
  --ink-soft:     oklch(0.438 0.017 236);       /* secondary — 6.8:1 on paper */
  /* Meta text. 4.6:1 on paper was fine and 4.2:1 on every tinted surface underneath it
     was not — the washes and --stone-deep are all slightly darker than paper, and this
     token sits on all of them. Measured against the darkest one it can land on. */
  --ink-faint:    oklch(0.520 0.015 236);       /* 5.4:1 on paper, 4.7:1 on --stone-deep */

  /* --- brand: the substrate ----------------------------------------------- */
  /* The state's own blue, verified rather than guessed: the Delta State Government logo
     is #00a0e0, which is oklch(0.667 0.144 236). That exact value is 2.96:1 on white and
     so cannot carry text — it is here as --delta-sky for fills, the map and chart marks,
     where it is the identity doing the work. The interactive blue is the same hue walked
     down the lightness ramp until it can be read. */
  --delta-sky:   oklch(0.667 0.144 236);       /* the logo's blue, for fills only */
  --sky:         oklch(0.455 0.132 236);       /* primary — 7.0:1 on paper */
  --sky-deep:    oklch(0.352 0.112 236);       /* header — 10.5:1, carries white text */
  --sky-lift:    oklch(0.530 0.135 236);       /* active/pressed on solid fills */
  --sky-wash:    oklch(0.958 0.024 236);       /* selected rows, active nav */
  --sky-line:    oklch(0.868 0.052 236);

  /* Two chart series, drawn side by side on the councils compare page. Checked with the
     dataviz palette validator — lightness band, chroma floor, separation under protan,
     deutan and tritan vision, contrast against the surface — light #006a9b / #8a5a12,
     dark #3298cc / #c48525. Never used for text: a value beside a mark stays ink. */
  --series-1:    #006a9b;
  --series-2:    #8a5a12;

  /* --- the header ---------------------------------------------------------
     Its own tokens, and deliberately not --sky-deep. That token has to be dark in
     the light theme and light in the dark one, because on paper it is an ink; the
     header is a surface carrying white text, which needs the opposite. One token
     cannot do both, and when it tried, the dark theme shipped a header at 1.9:1.

     There is no dark-theme override below. A masthead is a constant: only the page
     under it changes, so the header stays this navy in both and every pair on it
     keeps the ratio it was measured at. */
  --header:       oklch(0.372 0.116 252);   /* toward the logo's navy, #104080 */
  --header-ink:   oklch(0.960 0.012 236);
  --header-soft:  oklch(0.900 0.020 236);   /* nav links, placeholder — 7.9:1 */
  --header-focus: oklch(0.860 0.130 64);    /* keyboard ring, 5.9:1 on the header */

  /* The brandmark. Gold at rest so it separates from the navy header it sits on;
     sky on hover, at the same lightness, so what reads as changing is the hue alone. */
  --mark:        oklch(0.860 0.140 92);
  --mark-lift:   oklch(0.880 0.135 236);

  /* --- evidence vocabulary -------------------------------------------------
     Seven provenance classes need seven readable identities. They sit at one
     lightness so they read as a single family rather than a traffic light, and every
     one is a solid tint — no neon, no glow. */
  --t0:   oklch(0.455 0.132 236);  --t0-wash:  oklch(0.958 0.024 236);
  --t1:   oklch(0.440 0.075 165);  --t1-wash:  oklch(0.962 0.020 165);
  --t2:   oklch(0.450 0.110 275);  --t2-wash:  oklch(0.962 0.026 275);
  --t3:   oklch(0.455 0.098 345);  --t3-wash:  oklch(0.963 0.022 345);
  --t4:   oklch(0.470 0.098 64);   --t4-wash:  oklch(0.960 0.030 64);
  --t5:   oklch(0.480 0.062 64);   --t5-wash:  oklch(0.966 0.018 64);
  --t6:   oklch(0.455 0.130 26);   --t6-wash:  oklch(0.958 0.026 26);

  /* --- chart series --------------------------------------------------------
     Project status, and the only palette on the site that is read as shapes rather
     than as text. Two constraints pull against each other: each bar needs 3:1 against
     the panel it sits on, and the segments of the stacked bar sit edge to edge with
     nothing between them, so they need to be separable from EACH OTHER. Three swatches
     cannot be both maximally distinct from one background and from one another; these
     are the widest spread that holds 3:1 on the panel.

     The spread is in lightness, not hue, so the stack survives being read by someone
     who cannot separate green from amber. The key underneath names every segment
     regardless — colour is never the only thing carrying the meaning. */
  --series-done:    oklch(0.300 0.085 162);
  --series-running: oklch(0.470 0.120 236);
  --series-new:     oklch(0.650 0.115 64);

  /* --- state -------------------------------------------------------------- */
  --ok:      var(--t0);
  --caution: oklch(0.470 0.098 64);
  --danger:  oklch(0.455 0.130 26);
  --danger-wash: var(--t6-wash);
  --focus:   oklch(0.470 0.098 64);

  /* --- type: one family, fixed rem scale, ~1.2 ratio ---------------------- */
  /* The fallbacks are what a reader sees for the few hundred milliseconds before the
     face arrives, and what they see forever if it fails to. Both are ordered so the
     substitute is the closest thing already on the device. */
  --sans: "Public Sans", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica,
          Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
/* A fixed rem scale, near enough a 1.2 ratio through the middle where most of the type
     lives. Not fluid: a product surface is read at a consistent size, and a heading that
     shrinks inside a panel looks smaller rather than better. --t-4xl exists for one
     element, the homepage's opening line, and is used nowhere else. */
  --t-4xl: 2.75rem;  --t-3xl: 2.125rem; --t-2xl: 1.75rem;  --t-xl: 1.4375rem;
  --t-lg: 1.1875rem; --t-md: 1rem;      --t-sm: 0.875rem;  --t-xs: 0.8125rem;
  --t-2xs: 0.75rem;

  /* --- space -------------------------------------------------------------- */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  --s7: 48px; --s8: 72px; --s9: 104px;

  /* --- form: soft ---------------------------------------------------------- */
  --r-sm: 8px; --r-md: 12px; --r-lg: 18px; --r-xl: 26px;
  /* shadows carry the brand hue rather than black, so elevation reads as depth
     in the same material instead of a grey smudge */
  --lift-1: 0 1px 2px oklch(0.398 0.079 232 / 0.06);
  --lift-2: 0 2px 4px oklch(0.398 0.079 232 / 0.05), 0 10px 24px oklch(0.398 0.079 232 / 0.07);
  --lift-3: 0 8px 20px oklch(0.398 0.079 232 / 0.10), 0 28px 60px oklch(0.398 0.079 232 / 0.12);

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --quick: 160ms var(--ease);
  /* Replaced with the measured height on load — see base.html. This is only
     what the first paint uses before that runs. */
  --header-h: 62px;
  --calm: 240ms var(--ease);

  /* semantic stacking order — never an arbitrary 999 */
  --z-sticky: 100; --z-mapui: 200; --z-sheet: 250; --z-dropdown: 300; --z-modal: 400; --z-toast: 500;
}

[data-theme="dark"] {
  color-scheme: dark;
  --paper:      oklch(0.196 0.014 236);
  --stone:      oklch(0.243 0.016 236);
  --stone-deep: oklch(0.285 0.018 236);
  --edge:       oklch(0.339 0.019 236);
  --edge-soft:  oklch(0.294 0.017 236);
  --ink:        oklch(0.948 0.010 236);
  --ink-soft:   oklch(0.808 0.014 236);
  --ink-faint:  oklch(0.702 0.014 236);
  --sky:       oklch(0.790 0.115 236);
  --sky-deep:  oklch(0.720 0.112 236);
  --sky-lift:  oklch(0.860 0.100 236);
  --sky-wash:  oklch(0.300 0.044 236);
  --sky-line:  oklch(0.380 0.052 236);
  --series-1:  #3298cc;
  --series-2:  #c48525;
  --t0: oklch(0.790 0.115 236); --t0-wash: oklch(0.300 0.044 236);
  --t1: oklch(0.800 0.080 165); --t1-wash: oklch(0.300 0.032 165);
  --t2: oklch(0.790 0.105 275); --t2-wash: oklch(0.305 0.042 275);
  --t3: oklch(0.800 0.100 345); --t3-wash: oklch(0.305 0.040 345);
  --t4: oklch(0.820 0.110 64);  --t4-wash: oklch(0.310 0.045 64);
  --t5: oklch(0.820 0.070 64);  --t5-wash: oklch(0.300 0.030 64);
  --t6: oklch(0.760 0.130 26);  --t6-wash: oklch(0.315 0.050 26);
  --series-done:    oklch(0.520 0.085 162);
  --series-running: oklch(0.700 0.120 236);
  --series-new:     oklch(0.940 0.115 64);
  --caution: oklch(0.820 0.110 64);
  --danger:  oklch(0.760 0.130 26);
  --danger-wash: var(--t6-wash);
  --focus:   oklch(0.820 0.110 64);
  --lift-1: 0 1px 2px oklch(0 0 0 / 0.4);
  --lift-2: 0 2px 4px oklch(0 0 0 / 0.35), 0 10px 24px oklch(0 0 0 / 0.4);
  --lift-3: 0 8px 20px oklch(0 0 0 / 0.45), 0 28px 60px oklch(0 0 0 / 0.5);
}

/* =========================================================== foundations == */
* { box-sizing: border-box; }
html { scroll-padding-top: 88px; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-md);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
h1, h2, h3, h4 { margin: 0 0 0.5rem; line-height: 1.2; font-weight: 640; letter-spacing: -0.018em; }
h1 { font-size: var(--t-3xl); letter-spacing: -0.026em; text-wrap: balance; }
h2 { font-size: var(--t-2xl); text-wrap: balance; }
h3 { font-size: var(--t-xl); text-wrap: balance; }
h4 { font-size: var(--t-md); font-weight: 620; }
/* A panel's own title. It sits under the section's h2, so it is an h3; the class keeps
   the size it has always had, which the h3 above it in the page does not share. */
h3.sub { font-size: var(--t-md); font-weight: 620; }
p { margin: 0 0 0.9em; text-wrap: pretty; }
.prose, .measure { max-width: 68ch; }
ul, ol { margin: 0 0 1em; padding-left: 1.15em; }
li { margin-bottom: 0.35em; }
a { color: var(--sky); text-decoration-color: var(--sky-line); text-underline-offset: 3px; }
a:hover { color: var(--sky-deep); text-decoration-color: currentColor; }
code { font-family: var(--mono); font-variant-numeric: tabular-nums lining-nums; }
.mono, .num { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; }
code { font-size: 0.86em; background: var(--stone); padding: 0.1em 0.36em; border-radius: 5px; }
:focus-visible { outline: 2.5px solid var(--focus); outline-offset: 2px; border-radius: 4px; }
::selection { background: var(--sky-wash); color: var(--ink); }
.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: var(--s4); top: var(--s4); z-index: var(--z-toast); background: var(--sky);
  color: var(--paper); padding: var(--s3) var(--s4); border-radius: var(--r-sm); font-weight: 600;
}

/* ================================================================ header == */
/* Opaque, always. The old header blurred what was behind it; glass is decoration
   pretending to be hierarchy, and it costs paint time on the target device. */
.top {
  position: sticky; top: 0; z-index: var(--z-sticky);
  background: var(--header); color: var(--header-ink);
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s3) var(--s5);
}
.brandmark { display: flex; align-items: center; gap: var(--s3); color: inherit; text-decoration: none; }
.brandmark:hover { color: inherit; }
/* A solid mark. The previous one was a conic gradient — decoration standing in for identity. */
.brandmark .eye {
  width: 30px; height: 30px; border-radius: 50%; flex: 0 0 auto;
  background: var(--mark);
  display: grid; place-items: center; position: relative;
  transition: background var(--calm);
}
.brandmark .eye::after {
  content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--header);
}
/* A notch on the rim. Without it the turn below is invisible — a disc has no orientation,
   so a rotating circle is an animation nobody can see. */
.brandmark .eye::before {
  content: ""; position: absolute; top: 3px; left: 50%; margin-left: -2px;
  width: 4px; height: 4px; border-radius: 50%; background: var(--header);
}
/* One turn on hover, gold to sky, and then it stops. The mark is an eye that watches
   public money; a header logo that spins on its own is a loading spinner, and this page
   is not loading. The global prefers-reduced-motion rule above cuts the turn and leaves
   the colour. */
.brandmark:hover .eye,
.brandmark:focus-visible .eye {
  background: var(--mark-lift);
  animation: eye-turn 900ms var(--ease) both;
}
@keyframes eye-turn { from { transform: rotate(0turn); } to { transform: rotate(1turn); } }
.brandmark b { display: block; font-size: var(--t-md); font-weight: 650; letter-spacing: -0.02em; line-height: 1.1; }
.brandmark span { display: block; font-size: var(--t-2xs); opacity: 0.72; }
.nav { display: flex; gap: 2px; margin-left: var(--s5); flex-wrap: wrap; }
.nav a {
  font-size: var(--t-sm); font-weight: 550; color: var(--header-soft);
  padding: 7px var(--s3); border-radius: var(--r-sm); text-decoration: none;
  transition: background var(--quick), color var(--quick);
}
.nav a:hover { background: oklch(1 0 0 / 0.10); color: var(--paper); }
.nav a.on { background: oklch(1 0 0 / 0.16); color: var(--header-ink); font-weight: 620; }
.top .sp { margin-left: auto; display: flex; gap: var(--s2); align-items: center; }
/* The typed text is --header-ink rather than --paper: --paper follows the theme and
   goes near-black in the dark one, which would have put black text on this navy field. */
.top-search {
  /* The 12% fill only reaches 1.4:1 against the header, so it does not delineate this
     control on its own and the border has to. At 18% the border was 1.7:1; 44% is 3.3,
     which is what a component boundary needs to be findable. */
  background: oklch(1 0 0 / 0.12); border: 1px solid oklch(1 0 0 / 0.44);
  border-radius: var(--r-sm); padding: 7px var(--s3); color: var(--header-ink);
  font: inherit; font-size: var(--t-xs); width: 170px;
  transition: background var(--quick), border-color var(--quick);
}
/* Opaque, not the conventional dimmed placeholder. At 75% over this field it measured
   3.9:1 — placeholder text is text, and it carries the only instruction this control
   gives about what can be typed into it. */
.top-search::placeholder { color: var(--header-soft); opacity: 1; }

/* A form field on the page, which is not the header. The alerts form borrowed .top-search,
   whose near-white text is for the navy header: on paper its own choices were white on
   white. This is ink on paper with a border that can be seen against it. */
.control {
  width: 100%; background: var(--paper); border: 1px solid var(--edge);
  border-radius: var(--r-sm); padding: 10px var(--s3); color: var(--ink);
  font: inherit; font-size: var(--t-md);
  transition: border-color var(--quick), box-shadow var(--quick);
}
.control::placeholder { color: var(--ink-faint); opacity: 1; }
.control:hover { border-color: var(--sky-line); }
.control:focus { border-color: var(--sky); outline: none; box-shadow: 0 0 0 4px var(--sky-wash); }
.top-search:hover { background: oklch(1 0 0 / 0.16); }
/* Focus does not lighten the field a third time. Every step lightened the ground under
   the placeholder and took contrast with it; focus is a ring, which is also the visible
   keyboard affordance the field was missing when it set outline: none. */
.top-search:focus {
  background: oklch(1 0 0 / 0.16); border-color: oklch(1 0 0 / 0.62);
  outline: 2px solid var(--header-focus); outline-offset: 1px;
}
.icon-btn {
  background: oklch(1 0 0 / 0.12); border: 1px solid oklch(1 0 0 / 0.18); color: var(--header-ink);
  border-radius: var(--r-sm); width: 34px; height: 34px; cursor: pointer; font-size: 0.9rem;
  transition: background var(--quick);
}
.icon-btn:hover { background: oklch(1 0 0 / 0.22); }
.menu-toggle { display: none; }

/* ================================================================ layout == */
.wrap { max-width: 1220px; margin: 0 auto; padding: var(--s7) var(--s5) var(--s8); }
.wrap.narrow { max-width: 820px; }
.grid { display: grid; gap: var(--s4); }
.g2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.g3 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.g4 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.row { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }
.panel {
  background: var(--stone); border: 1px solid var(--edge-soft);
  border-radius: var(--r-lg); padding: var(--s5);
}
.panel-plain { background: var(--paper); border: 1px solid var(--edge); border-radius: var(--r-lg); padding: var(--s5); }
.rule { border: 0; border-top: 1px solid var(--edge); margin: var(--s7) 0; }

/* Section headings replace the old uppercase tracked eyebrow, which appeared above
   thirteen sections across five templates — scaffolding by reflex rather than voice. */
.section-head { margin: var(--s7) 0 var(--s4); }
.section-head h2 { margin: 0 0 0.2rem; font-size: var(--t-xl); }
.section-head p { margin: 0; color: var(--ink-soft); font-size: var(--t-sm); max-width: 62ch; }

/* ================================================================== lede == */
/* The homepage lede is one committed figure, not a row of four identical tiles.
   The money IS the subject; supporting facts differ in shape because they differ in kind. */
.lede { padding: var(--s7) 0 var(--s6); }
.lede h1 { font-size: clamp(2rem, 1.4rem + 2.2vw, 3.25rem); max-width: 17ch; margin-bottom: var(--s4); }
.lede .standfirst { font-size: var(--t-lg); color: var(--ink-soft); max-width: 58ch; }
.headline-figure {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s3);
  margin: var(--s6) 0 var(--s2);
}
.headline-figure .fig {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  font-size: clamp(2.5rem, 1.6rem + 3.4vw, 4.25rem); font-weight: 600;
  letter-spacing: -0.035em; color: var(--sky); line-height: 1;
}
.headline-figure .of { font-size: var(--t-lg); color: var(--ink-soft); }
.headline-note { color: var(--ink-soft); font-size: var(--t-sm); max-width: 60ch; }

.facts { display: grid; gap: var(--s3); margin-top: var(--s6); }
.fact {
  display: grid; grid-template-columns: minmax(96px, auto) 1fr; gap: var(--s3) var(--s5);
  align-items: baseline; padding: var(--s4) 0; border-top: 1px solid var(--edge-soft);
}
.fact .v { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-2xl);
  font-weight: 600; letter-spacing: -0.03em; color: var(--ink); line-height: 1; }
.fact .v.warn { color: var(--caution); }
.fact .v.stop { color: var(--danger); }
.fact .k { font-size: var(--t-sm); color: var(--ink-soft); }
.fact .k strong { color: var(--ink); font-weight: 600; }

/* An area's summary. A paragraph says what the figures mean; the three figures sit under
   it, each with its source; the facts about the place are a panel beside them, so both
   columns end near the same height. Stacks into one column on a phone. */
.profile {
  display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: var(--s6); align-items: start; padding-bottom: var(--s6);
}
.profile-main { display: grid; gap: var(--s5); }
.profile h1 { font-size: clamp(2rem, 1.4rem + 2.2vw, 3.25rem); max-width: 17ch; margin-bottom: 0; }
.profile-say { color: var(--ink-soft); max-width: 60ch; text-wrap: pretty; }
.profile-say strong { color: var(--ink); font-weight: 600; }
.key-strip {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0; border-block: 1px solid var(--edge);
}
.key-cell { display: flex; flex-direction: column; gap: 6px; padding: var(--s5) var(--s4) var(--s5) 0; }
.key-cell + .key-cell { padding-left: var(--s4); border-left: 1px solid var(--edge-soft); }
.key-cell dt { font-size: var(--t-sm); font-weight: 560; color: var(--ink); }
.key-cell dd { margin: 0; }
.key-cell .key-v {
  order: -1; font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-2xl);
  font-weight: 620; letter-spacing: -0.03em; line-height: 1; color: var(--ink);
}
.key-cell:first-child .key-v { color: var(--sky); }
.key-v small { font-size: var(--t-md); font-weight: 560; color: var(--ink-soft); letter-spacing: 0; }
.profile-actions { display: flex; flex-wrap: wrap; gap: var(--s2); }
.profile-panel {
  background: var(--stone); border: 1px solid var(--edge-soft); border-radius: var(--r-lg);
  padding: var(--s5);
}
.profile-panel h2 { font-size: var(--t-md); margin-bottom: var(--s2); }
.profile-panel dl { margin: 0; }
.profile-row {
  display: grid; grid-template-columns: 1fr auto; gap: var(--s1) var(--s3);
  align-items: baseline; padding: var(--s3) 0; border-top: 1px solid var(--edge);
}
.profile-row dt { font-size: var(--t-sm); color: var(--ink-soft); }
.profile-row dd {
  margin: 0; font-weight: 640; text-align: right;
  font-variant-numeric: tabular-nums lining-nums;
}
.profile-row .profile-src { grid-column: 1 / -1; justify-self: start; font-weight: 400; }
.profile-panel details.hwk { margin: var(--s3) 0 0; }
@media (max-width: 52rem) {
  .profile { grid-template-columns: 1fr; }
}
@media (max-width: 40rem) {
  .key-strip { grid-template-columns: 1fr; }
  .key-cell, .key-cell + .key-cell { padding: var(--s4) 0; border-left: 0; }
  .key-cell + .key-cell { border-top: 1px solid var(--edge-soft); }
}

/* ======================================================= evidence badges == */
/* The most reused component in the product. Identical inline, on cards, in tables and
   in map popups, so a reader learns it once. Solid tint, no glow, no border. */
.ev {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--sans); font-size: var(--t-2xs); font-weight: 600;
  padding: 2px 8px; border-radius: 999px; white-space: nowrap; vertical-align: baseline;
}
.ev b { font-weight: 700; letter-spacing: 0.02em; }
.ev.T0 { background: var(--t0-wash); color: var(--t0); }
.ev.T1 { background: var(--t1-wash); color: var(--t1); }
.ev.T2 { background: var(--t2-wash); color: var(--t2); }
.ev.T3 { background: var(--t3-wash); color: var(--t3); }
.ev.T4 { background: var(--t4-wash); color: var(--t4); }
.ev.T5 { background: var(--t5-wash); color: var(--t5); }
.ev.T6 { background: var(--t6-wash); color: var(--t6); }
.tag {
  display: inline-block; font-size: var(--t-xs); padding: 3px 10px; border-radius: 999px;
  background: var(--stone-deep); color: var(--ink-soft); margin: 3px 4px 3px 0;
}

/* ================================================================ buttons == */
/* Full state set: default, hover, active, focus, disabled. */
.btn {
  font: inherit; font-size: var(--t-sm); font-weight: 580; line-height: 1;
  padding: 10px var(--s4); border-radius: var(--r-sm); cursor: pointer;
  border: 1px solid var(--edge); background: var(--paper); color: var(--ink);
  display: inline-flex; align-items: center; gap: var(--s2); text-decoration: none;
  transition: background var(--quick), border-color var(--quick), color var(--quick);
}
.btn:hover { background: var(--stone); border-color: var(--sky-line); color: var(--ink); text-decoration: none; }
.btn:active { background: var(--stone-deep); }
.btn[disabled], .btn.is-disabled { opacity: 0.5; pointer-events: none; }
.btn.pri { background: var(--sky); border-color: var(--sky); color: var(--paper); }
.btn.pri:hover { background: var(--sky-deep); border-color: var(--sky-deep); color: var(--paper); }
.btn.pri:active { background: var(--sky-lift); border-color: var(--sky-lift); }

/* ================================================================= search == */
.searchbar { position: relative; }
.searchbar input {
  width: 100%; background: var(--paper); border: 1.5px solid var(--edge);
  border-radius: var(--r-md); padding: 14px var(--s4); color: var(--ink);
  font: inherit; font-size: var(--t-md);
  transition: border-color var(--quick), box-shadow var(--quick);
}
.searchbar input::placeholder { color: var(--ink-faint); }
.searchbar input:hover { border-color: var(--sky-line); }
.searchbar input:focus { border-color: var(--sky); outline: none; box-shadow: 0 0 0 4px var(--sky-wash); }
.hero-search {
  display: flex; gap: var(--s2); align-items: stretch;
  margin: var(--s5) 0; max-width: 46rem;
}
.hero-search .searchbar { flex: 1; }
.hero-search .btn { flex: none; }
.hero-search input { padding: 17px var(--s5); font-size: var(--t-lg); border-radius: var(--r-lg); }

/* =============================================================== how-we-know */
details.hwk {
  border: 1px solid var(--edge); border-radius: var(--r-md);
  background: var(--paper); margin: var(--s4) 0; overflow: hidden;
}
details.hwk > summary {
  padding: 11px var(--s4); cursor: pointer; font-size: var(--t-xs); font-weight: 600;
  color: var(--sky); list-style: none; display: flex; align-items: center; gap: var(--s2);
  transition: background var(--quick);
}
details.hwk > summary::-webkit-details-marker { display: none; }
details.hwk > summary::after { content: "＋"; margin-left: auto; color: var(--ink-faint); font-weight: 400; }
details.hwk[open] > summary::after { content: "−"; }
details.hwk > summary:hover { background: var(--stone); }
.hwk table { width: 100%; border-collapse: collapse; font-size: var(--t-xs); }
.hwk td { padding: 9px var(--s4); border-top: 1px solid var(--edge-soft); vertical-align: top; }
.hwk td:first-child { color: var(--ink-faint); width: 34%; }

/* ================================================================= tables == */
.tw { overflow-x: auto; border: 1px solid var(--edge); border-radius: var(--r-lg); background: var(--paper); }
/* The sources table on the About page. Its first column holds a publisher, a document
   title and the address it was read from, and a long address has no break in it: without
   a fixed layout it took the width it wanted and squeezed the terms beside it to one word
   a line. The address wraps instead. */
table.d.sources { table-layout: fixed; }
table.d.sources td:first-child a { overflow-wrap: anywhere; }
table.d.sources td:nth-child(3) { white-space: nowrap; }
table.d { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
table.d th, table.d td { padding: 12px var(--s4); text-align: left; vertical-align: top; }
table.d thead th {
  font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); background: var(--stone); font-weight: 650; white-space: nowrap;
  position: sticky; top: 0; border-bottom: 1px solid var(--edge);
}
table.d tbody td { border-top: 1px solid var(--edge-soft); }
table.d td.n, table.d th.n { text-align: right; font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; }
table.d tbody tr { transition: background var(--quick); }
table.d tbody tr:hover { background: var(--sky-wash); }

/* ============================================================ story pages == */
.crumb { font-size: var(--t-xs); color: var(--ink-faint); margin-bottom: var(--s3); }
.crumb a { color: var(--ink-soft); }
.story .meta { color: var(--ink-soft); font-size: var(--t-sm); margin-bottom: var(--s5); }
.kv { display: grid; grid-template-columns: minmax(150px, 190px) 1fr; gap: 0; font-size: var(--t-sm); }
.kv dt { color: var(--ink-faint); padding: 11px 0; border-top: 1px solid var(--edge-soft); }
.kv dd { margin: 0; padding: 11px 0; border-top: 1px solid var(--edge-soft); font-variant-numeric: tabular-nums; }
.kv dd strong { font-weight: 620; }

.timeline { display: flex; margin: var(--s5) 0 var(--s4); }
.timeline .st { flex: 1; text-align: center; font-size: var(--t-2xs); color: var(--ink-faint); position: relative; padding-top: 20px; }
.timeline .st::before {
  content: ""; position: absolute; top: 3px; left: 50%; transform: translateX(-50%);
  width: 11px; height: 11px; border-radius: 50%; background: var(--edge);
}
.timeline .st.done::before { background: var(--sky); }
.timeline .st.now::before { background: var(--caution); box-shadow: 0 0 0 5px var(--t4-wash); }
.timeline .st::after {
  content: ""; position: absolute; top: 8px; left: calc(50% + 8px); right: calc(-50% + 8px);
  height: 2px; background: var(--edge);
}
.timeline .st:last-child::after { display: none; }
.timeline .st.done::after { background: var(--sky); }
.timeline .st.done, .timeline .st.now { color: var(--ink); font-weight: 560; }

/* ================================================================== notes == */
.note {
  background: var(--stone); border: 1px solid var(--edge-soft);
  border-radius: var(--r-md); padding: var(--s4) var(--s5); margin: var(--s5) 0;
}
.note > b:first-child { display: block; font-weight: 640; color: var(--sky); margin-bottom: 4px; }
.note.am > b:first-child { color: var(--caution); }
.note.bd { background: var(--danger-wash); border-color: var(--t6-wash); }
.note.bd > b:first-child { color: var(--danger); }

/* The absence panel gets the same visual weight as data, because the absence is
   frequently the more important fact. */
.gap-panel {
  background: var(--danger-wash); border: 1px solid var(--t6-wash);
  border-radius: var(--r-lg); padding: var(--s5); margin: var(--s5) 0;
}
.gap-panel h4 { color: var(--danger); margin-bottom: var(--s2); }
.gap-panel h3.sub { color: var(--danger); margin-bottom: var(--s2); }

/* ================================================================ results == */
.list { border-top: 1px solid var(--edge-soft); }
.result {
  display: block; padding: var(--s4) var(--s4); margin: 0 calc(var(--s4) * -1);
  border-bottom: 1px solid var(--edge-soft); color: var(--ink); text-decoration: none;
  border-radius: var(--r-md); transition: background var(--quick);
}
.result:hover { background: var(--stone); color: var(--ink); text-decoration: none; }
.result .t { font-weight: 580; font-size: var(--t-sm); line-height: 1.4; }
.result .m { color: var(--ink-faint); font-size: var(--t-xs); margin-top: 3px; }

/* Office holders, with the photograph their own institution publishes. Where there is
   none the space is held open and left empty on purpose: a generic silhouette would be
   this platform inventing a face, and the absence is a fact about the publisher. */
.result.person { display: flex; gap: var(--s3); align-items: center; }
.result.person .who { display: block; min-width: 0; }
.result.person .t, .result.person .m { display: block; }
.face {
  flex: 0 0 auto; width: 46px; height: 46px; border-radius: var(--r-md);
  object-fit: cover; object-position: 50% 22%;   /* heads sit high in a press photo */
  background: var(--stone); border: 1px solid var(--edge-soft);
}
.face.none { background: repeating-linear-gradient(
    -45deg, var(--stone) 0 5px, transparent 5px 10px); }

.portrait { float: right; width: 132px; margin: 0 0 var(--s3) var(--s4); }
.portrait img {
  width: 132px; height: 132px; object-fit: cover; object-position: 50% 22%;
  border-radius: var(--r-md); background: var(--stone);
  border: 1px solid var(--edge-soft); display: block;
}
.portrait figcaption {
  font-size: var(--t-xs); color: var(--ink-faint); margin-top: var(--s2);
  line-height: 1.45;
}
@media (max-width: 560px) { .portrait { float: none; margin: 0 0 var(--s3); } }
.empty {
  padding: var(--s8) var(--s5); text-align: center; color: var(--ink-soft);
  background: var(--stone); border-radius: var(--r-lg);
}
.empty p:first-child { color: var(--ink); font-weight: 560; }

/* Skeletons rather than spinners: the layout does not jump when content lands. */
.skeleton {
  height: 12px; border-radius: 6px; background: var(--stone-deep);
  animation: pulse 1.4s ease-in-out infinite; margin-bottom: var(--s2);
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }

/* ==================================================================== map == */
.mapshell {
  position: relative; border: 1px solid var(--edge); border-radius: var(--r-xl);
  overflow: hidden; background: var(--stone); box-shadow: var(--lift-2);
}
#map { width: 100%; height: 560px; }
.mapui { position: absolute; top: var(--s4); left: var(--s4); z-index: var(--z-mapui); max-width: 252px; }
/* Opaque. The previous panel blurred the map behind it, which made both harder to read. */
.mapui .panel { background: var(--paper); border: 1px solid var(--edge); border-radius: var(--r-md);
  padding: var(--s3) var(--s4); box-shadow: var(--lift-2); }
.mapui h6 {
  margin: 0 0 var(--s2); font-size: var(--t-2xs); color: var(--ink-faint);
  font-weight: 650; letter-spacing: 0.02em;
}
.lyr {
  display: flex; align-items: center; gap: var(--s2); font-size: var(--t-xs);
  padding: 5px 0; cursor: pointer; color: var(--ink-soft);
}
.lyr:hover { color: var(--ink); }
.lyr input { accent-color: var(--sky); width: 15px; height: 15px; cursor: pointer; }
.lyr.dis { opacity: 0.55; cursor: not-allowed; }
.lyr .sw { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
/* Every rail row ends with its evidence badge hard right, whatever the label's length. */
.lyr .ev { margin-left: auto; }

/* One late project, with the ground before and since. Dense: the reader is comparing two
   pictures, and everything else on the card is context for that comparison. */
.latecard {
  background: var(--paper); border: 1px solid var(--edge-soft);
  border-radius: var(--r-lg); padding: var(--s4); margin: var(--s4) 0;
}
.late-head h3 { margin: 0 0 var(--s2); font-size: var(--t-lg); line-height: 1.3; }
.late-facts {
  display: flex; flex-wrap: wrap; gap: var(--s3); margin-bottom: var(--s4);
  font-size: var(--t-xs); color: var(--ink-faint); font-variant-numeric: tabular-nums;
}
.late-days { font-weight: 600; }
.late-who { margin: var(--s3) 0 0; font-size: var(--t-xs); color: var(--ink-soft); }

/* How much of an arm of government anybody publishes. A bar rather than a percentage:
   the shape of the shortfall is the point, and several of these are zero. */
.armbar {
  height: 7px; border-radius: 4px; background: var(--stone-deep);
  overflow: hidden; max-width: 190px;
}
.armbar i { display: block; height: 100%; background: var(--sky); border-radius: 4px; }
.armnote {
  display: block; margin-top: 4px; font-size: var(--t-2xs); color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* The one sentence the officials page exists to say. */
.seatline {
  margin: var(--s4) 0 0; padding: var(--s3) 0 0; border-top: 1px solid var(--edge-soft);
  font-size: var(--t-md); line-height: 1.6; max-width: 60ch; color: var(--ink-soft);
}
.seatline strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.seatline strong.stop { color: var(--danger); }

/* An absence, said once, with the places it covers under it. */
.nobody-row { margin: 0 0 var(--s4); }
.nobody-row:last-of-type { margin-bottom: 0; }
.nobody-why { margin: 0 0 var(--s2); font-size: var(--t-sm); line-height: 1.55; }
.nobody-where { margin: 0; display: flex; flex-wrap: wrap; gap: 4px; }

/* Narrowing the alert feed. Two selects on one line, wrapping on a phone. */
.alertfilter {
  display: flex; flex-wrap: wrap; gap: var(--s3); align-items: flex-end;
  margin: var(--s4) 0 var(--s3);
}
.alertfilter label { display: grid; gap: 4px; flex: 1 1 14rem; min-width: 0; }
.alertfilter label span { font-size: var(--t-2xs); color: var(--ink-faint); }
.alertcount {
  margin: 0 0 var(--s4); font-size: var(--t-sm); color: var(--ink-soft); line-height: 1.6;
  max-width: 70ch;
}

/* The moderation queue. Dense on purpose — a reviewer works down a list, and every row
   has to show enough to decide without opening anything. */
.rev { margin: var(--s4) 0; }
.rev-head { display: flex; justify-content: space-between; gap: var(--s3); align-items: flex-start; }
.rev-head h3 { margin: 0 0 var(--s1); font-size: var(--t-lg); }
.rev-where { margin: 0; font-size: var(--t-xs); color: var(--ink-soft); }
.rev-meta {
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--t-2xs); color: var(--ink-faint);
  white-space: nowrap;
}
.rev-note {
  margin: var(--s3) 0; padding: var(--s3); border-radius: var(--r-sm);
  background: var(--stone-deep); font-size: var(--t-sm); line-height: 1.55;
}
.rev-facts { margin: var(--s3) 0 0; display: grid; gap: var(--s2); }
.rev-facts div { display: grid; grid-template-columns: minmax(0, 20rem) 1fr; gap: var(--s3); }
.rev-facts dt { font-size: var(--t-2xs); color: var(--ink-faint); }
.rev-facts dd { margin: 0; font-size: var(--t-xs); color: var(--ink); }
.rev-photo {
  display: block; margin: var(--s3) 0 0; max-width: 320px; width: 100%; height: auto;
  border-radius: var(--r-sm); border: 1px solid var(--edge);
}
.rev-act { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s4); }
.rev-reason {
  flex: 1 1 22rem; min-width: 0; padding: var(--s2) var(--s3);
  font: inherit; font-size: var(--t-xs); color: var(--ink);
  background: var(--paper); border: 1px solid var(--edge); border-radius: var(--r-sm);
}
.rev-reason:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.rev-done { margin: var(--s4) 0 0; font-size: var(--t-xs); color: var(--ink-soft); }

@media (max-width: 640px) {
  .rev-facts div { grid-template-columns: 1fr; gap: 2px; }
}

/* Before and after at a project's coordinates. Equal width and equal treatment: two
   pictures of the same ground on different dates only mean something if nothing but the
   date differs between them. */
.sat { margin: var(--s5) 0; }
.sat h3 { font-size: var(--t-lg); margin: 0 0 var(--s3); }
.sat-pair {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--s3);
}
.sat-pair figure { margin: 0; }
.sat-pair img {
  width: 100%; height: auto; display: block; border-radius: var(--r-sm);
  border: 1px solid var(--edge); background: var(--stone-deep);
  image-rendering: auto;
}
.sat-pair figcaption {
  margin-top: var(--s2); font-size: var(--t-2xs); color: var(--ink-faint);
  line-height: 1.5;
}
.sat-pair figcaption b { display: block; color: var(--ink); font-size: var(--t-xs); }
.sat-note {
  margin: var(--s3) 0 0; font-size: var(--t-xs); color: var(--ink-soft); line-height: 1.55;
  max-width: 68ch;
}
.sat-note.tight { margin-top: var(--s2); font-size: var(--t-2xs); color: var(--ink-faint); }

/* Two areas beside each other. Row headers on the left, so a screen reader announces
   which measure each pair of figures belongs to. */
.cmp { width: 100%; border-collapse: collapse; margin: var(--s3) 0; font-size: var(--t-xs); }
.cmp th, .cmp td { padding: var(--s2) var(--s1); text-align: right; border-bottom: 1px solid var(--edge-soft); }
.cmp thead th { font-size: var(--t-2xs); color: var(--ink-faint); font-weight: 600; }
.cmp tbody th { text-align: left; font-weight: 400; color: var(--ink-soft); }
.cmp td { font-variant-numeric: tabular-nums; color: var(--ink); }
.cmp-go { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }

.zonekey { margin-top: var(--s2); }
.zonekey-row { display: flex; align-items: center; gap: var(--s2); font-size: var(--t-2xs); color: var(--ink-faint); }
.zonekey-row i { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }

/* The keyboard route into the map. Off-screen, never display:none — a hidden element is
   out of the accessibility tree, and this exists precisely to be in it. Each button comes
   back into view when focused, so a sighted keyboard user can see where they are. */
/* The container takes no space and clips nothing, so a focused button inside it can come
   back into view. The buttons hide themselves. */
.mapkeys { height: 0; overflow: visible; }
.mapkey {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.mapkey:focus-visible {
  position: fixed; top: var(--s3); left: var(--s3); z-index: var(--z-toast);
  width: auto; height: auto; clip-path: none; padding: var(--s2) var(--s4);
  background: var(--paper); color: var(--ink); font: inherit; font-size: var(--t-sm);
  border: 2px solid var(--focus); border-radius: var(--r-sm); box-shadow: var(--lift-2);
}

/* Three ways to leave with the data. Wrapped, because a 254px rail holds two per row. */
.takeaway { display: flex; flex-wrap: wrap; gap: var(--s2); }
.btn.sm { font-size: var(--t-2xs); padding: var(--s2) var(--s3); }

/* Search, inside the map rather than only in the header. The list sits over the rail on
   position:absolute, which is safe here: the rail scrolls but does not clip, and a fixed
   or portalled element would detach from the input it belongs to. */
.mapfind { position: relative; }
.mapfind-in {
  width: 100%; padding: var(--s2) var(--s3); font: inherit; font-size: var(--t-xs);
  color: var(--ink); background: var(--paper); border: 1px solid var(--edge);
  border-radius: var(--r-sm);
}
.mapfind-in:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.mapfind-list {
  position: absolute; z-index: var(--z-mapui); inset-inline: 0; top: calc(100% + 4px);
  margin: 0; padding: 4px; list-style: none; max-height: 300px; overflow-y: auto;
  background: var(--paper); border: 1px solid var(--edge);
  border-radius: var(--r-sm); box-shadow: var(--lift-2);
}
.mapfind-list li {
  padding: 6px var(--s2); border-radius: 5px; cursor: pointer; font-size: var(--t-xs);
}
.mapfind-list li b { display: block; font-weight: 600; color: var(--ink); }
.mapfind-list li span { display: block; font-size: var(--t-2xs); color: var(--ink-faint); }
.mapfind-list li:hover, .mapfind-list li.on { background: var(--stone-deep); }
.mapfind-none { color: var(--ink-faint); cursor: default; }

/* The year scrubber under the spills switch. Present only once the register is loaded —
   an empty slider promising years we do not have is worse than no slider. */
.scrub { margin-top: var(--s2); }
.scrub-head { display: flex; align-items: center; gap: var(--s2); }
.scrub-play {
  width: 26px; height: 26px; flex: 0 0 auto; border-radius: 50%;
  border: 1px solid var(--edge); background: var(--paper); color: var(--ink-soft);
  font-size: 12px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.scrub-play:hover { border-color: var(--sky); color: var(--sky); }
.scrub-play:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }
.scrub-year {
  font-family: var(--sans); font-size: var(--t-xs); font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums;
}
.scrub-range { width: 100%; margin: var(--s2) 0 0; accent-color: var(--sky); }
.scrub-range:focus-visible { outline: 2px solid var(--sky); outline-offset: 3px; }
.zlevel {
  position: absolute; bottom: var(--s4); left: var(--s4); z-index: var(--z-mapui);
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--t-2xs); color: var(--ink-soft);
  background: var(--paper); border: 1px solid var(--edge); border-radius: var(--r-sm);
  padding: 7px var(--s3); box-shadow: var(--lift-1);
}
.zlevel b { color: var(--sky); }
.mapfallback { padding: var(--s8) var(--s5); text-align: center; color: var(--ink-soft); }
.maplibregl-popup-content {
  background: var(--paper); color: var(--ink); border: 1px solid var(--edge);
  border-radius: var(--r-md); padding: var(--s4); font-family: var(--sans);
  font-size: var(--t-xs); max-width: 300px; box-shadow: var(--lift-3);
}
.maplibregl-popup-tip { border-top-color: var(--paper) !important; border-bottom-color: var(--paper) !important; }
.maplibregl-popup-close-button { color: var(--ink-faint); font-size: 1.15rem; padding: 3px 8px; }
.maplibregl-popup-close-button:hover { background: var(--stone); color: var(--ink); }
.maplibregl-ctrl-group { background: var(--paper) !important; border: 1px solid var(--edge); box-shadow: var(--lift-1) !important; }
.maplibregl-ctrl-attrib { background: var(--paper) !important; font-size: 12px; }

/* MapLibre's controls, in the page's own ink.

   Its icons are data-URI SVGs with the fill baked in — #333 for zoom, black for the
   attribution "i" — and it sets the attribution text in black. On --paper, which the dark
   theme turns dark, they measured 1.4:1 and 1.2:1: the zoom buttons were there and could
   not be seen.

   A filter would lighten them, but only to a colour no token names, and only in the theme
   somebody remembered to write it for. So each SVG becomes a mask over a token, and the
   token follows the theme on its own. Scoped through `#map` for the reason given at the
   44px buttons further down: maplibre-gl.css is linked after this file and wins every tie.

   Only outside forced-colours mode, where the browser replaces background colours — that
   would paint the icon in the canvas colour, and MapLibre ships its own high-contrast
   icons for it. And only where masks exist, so an old browser keeps the #333 icon rather
   than getting a solid square. Zoom and attribution are the only controls map.js mounts;
   a new one needs its glyph here too. */
#map .maplibregl-ctrl-attrib { color: var(--ink-soft); }
#map .maplibregl-ctrl-attrib a { color: var(--ink); }
#map .maplibregl-ctrl-attrib-button { background-color: transparent; }
#map .maplibregl-ctrl-group button + button { border-top-color: var(--edge); }
#map .maplibregl-ctrl-group button:not(:disabled):hover { background-color: var(--stone); }
@supports (mask-image: none) or (-webkit-mask-image: none) {
  @media (forced-colors: none) {
    #map .maplibregl-ctrl button .maplibregl-ctrl-icon,
    #map .maplibregl-ctrl-attrib-button::before {
      -webkit-mask: var(--glyph) center no-repeat;
      mask: var(--glyph) center no-repeat;
    }
    #map .maplibregl-ctrl button .maplibregl-ctrl-icon { background: var(--ink); }
    #map .maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon {
      --glyph: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5'/%3E%3C/svg%3E");
    }
    #map .maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon {
      --glyph: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E");
    }
    #map .maplibregl-ctrl-attrib-button { background-image: none; }
    #map .maplibregl-ctrl-attrib-button::before {
      content: ""; position: absolute; inset: 0; background: var(--ink-soft);
      --glyph: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill-rule='evenodd' viewBox='0 0 20 20'%3E%3Cpath d='M4 10a6 6 0 1 0 12 0 6 6 0 1 0-12 0m5-3a1 1 0 1 0 2 0 1 1 0 1 0-2 0m0 3a1 1 0 1 1 2 0v3a1 1 0 1 1-2 0'/%3E%3C/svg%3E");
    }
  }
}

/* ==================================================================== ask == */
.ask-answer { font-size: var(--t-lg); line-height: 1.55; margin-bottom: var(--s4); }
.ask-answer strong { font-weight: 640; }
.ask-path {
  font-family: var(--sans); font-size: var(--t-2xs); color: var(--ink-faint);
  border-top: 1px solid var(--edge-soft); padding-top: var(--s3); margin-top: var(--s4); line-height: 1.7;
}
.suggest { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s4); }
.suggest a {
  font-size: var(--t-xs); font-weight: 550; padding: 8px var(--s3); border-radius: 999px;
  border: 1px solid var(--edge); background: var(--paper); color: var(--ink-soft); text-decoration: none;
  transition: background var(--quick), border-color var(--quick), color var(--quick);
}
.suggest a:hover { background: var(--sky-wash); border-color: var(--sky-line); color: var(--sky); text-decoration: none; }

/* ================================================================= footer == */
footer.site {
  border-top: 1px solid var(--edge); background: var(--stone);
  padding: var(--s7) var(--s5); color: var(--ink-soft); font-size: var(--t-sm);
}
footer.site a { color: var(--ink); }
footer.site .inner { max-width: 1220px; margin: 0 auto; }

/* ============================================================ responsive == */
@media (max-width: 860px) {
  .nav {
    /* `top: 100%`, not a measured 58px. The header is two rows on a narrow phone now, and
       a hardcoded offset would drop the menu on top of the search instead of under it. */
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    background: var(--header); padding: var(--s3); flex-direction: column;
    margin: 0; z-index: var(--z-dropdown); box-shadow: var(--lift-3);
  }
  .nav.open { display: flex; }
  .menu-toggle { display: inline-flex; align-items: center; justify-content: center; }
  .top-search { width: 118px; }
  .wrap { padding: var(--s5) var(--s4) var(--s7); }
  .kv { grid-template-columns: 1fr; }
  .kv dt { padding-bottom: 0; border-top: 1px solid var(--edge-soft); }
  .kv dd { padding-top: 2px; border-top: 0; }
  #map { height: 400px; }
  .fact { grid-template-columns: 1fr; gap: var(--s1); }
}

/* ================================================== touch ================ */
/* Most people reading this site are on a phone, and a finger is not a cursor. Keyed on
   `pointer: coarse` rather than on width: a 1024px tablet is touched, and a narrow window
   on a desk is not. Desktop measurements are left exactly as they were.

   Two separate defects are being fixed here, and only the first is about size. */

@media (pointer: coarse) {
  /* 1. Targets a thumb can actually hit. Apple asks for 44px, Android for 48; everything
        interactive here measured 34-37, and the menu button — the only way to navigate on
        a phone — measured 27 wide, because `width: 34px` in a flex header is a suggestion
        unless the item is told not to shrink. */
  .icon-btn { width: 44px; height: 44px; flex: none; font-size: 1.05rem; }
  .btn { min-height: 44px; }
  .btn.sm { min-height: 38px; }          /* secondary, and never alone in a row */
  .nav a { min-height: 44px; display: flex; align-items: center; }
  .top-search { min-height: 44px; }
  select, .rail-select { min-height: 44px; }
  /* Fold-out toggles measured 19 to 22px tall on a phone: a row of text, not a target. */
  details.rail-group > summary, details.cw-table > summary { min-height: 44px; }
  /* The seat names heading each rung of an area's representatives were 26px tall. The
     padding makes a thumb-sized target; the equal negative margin keeps the layout. */
  .rung-head a { padding-block: 9px; margin-block: -9px; }

  /* The two buttons just took 27 extra pixels of a 375px row, which pushed the header
     wider than the screen. The search gives the width back instead: a fixed 118px field
     beside fixed-width buttons cannot fit every phone, and a 320px screen is still a
     phone people read this on.

     The width has to be taken off the `.has-suggest` wrapper, not the input. A flex item
     will not shrink below its content unless it is told `min-width: 0`, so sizing the
     input alone just pushed the wrapper past its parent instead. */
  .top .sp { min-width: 0; }
  .top .sp > form, .top .sp .has-suggest { min-width: 0; flex: 1 1 88px; }
  .top-search { width: 100%; min-width: 0; }

  /* 24px of padding either side and a 16px gap is a quarter of a 320px screen spent on
     nothing. Narrower here so the search keeps a usable width on the smallest phones. */
  .top { padding: var(--s3) var(--s4); gap: var(--s3); }

  /* MapLibre's own controls ship at 29px, and the map is the thing most people come for.
     Scoped through `#map` deliberately: maplibre-gl.css is linked after this file and its
     own `.maplibregl-ctrl-group button` rule matches at the same specificity, so an
     unscoped selector here loses on source order and silently does nothing. */
  #map .maplibregl-ctrl-group button { width: 44px; height: 44px; }
  #map .maplibregl-ctrl-group { border-radius: var(--r-sm); }

  /* 2. iOS Safari zooms the whole page when a text field smaller than 16px is focused —
        the layout jumps, and the reader has to pinch back out to carry on. Nothing about
        that is a preference; 16px is the threshold, so these two fields get 16px on touch
        and keep their smaller size on a desk where the behaviour does not exist. */
  .top-search, .rail-select { font-size: 16px; }

  /* 48px above the first heading is poise on a wide screen and a waste on a phone: with
     the 24px the wrap already adds, 72 of the first 812 pixels were blank before anything
     was said. Halved, so the sentence that answers the page's question is on screen when
     it opens. */
  .lede { padding-top: var(--s5); }
  .lede-tight { padding-top: var(--s4); }
}

/* The same room-making for any pointer. The shrinking search and the tighter padding above
   were written for touch, and a browser that does not report a touch pointer — an older or
   lighter phone browser, a narrow window — got neither. Below about 420px the full-size
   row does not fit; 440 leaves room for a system font wider than Public Sans.

   The form is named beside `.has-suggest` because suggest.js adds that class. Without
   JavaScript the form is the same flex item under no class, and would not shrink. */
@media (max-width: 440px) {
  .icon-btn { flex: none; }
  .top { padding: var(--s3) var(--s4); gap: var(--s3); }
  .top .sp { min-width: 0; }
  .top .sp > form, .top .sp .has-suggest { min-width: 0; flex: 1 1 88px; }
  .top-search { width: 100%; min-width: 0; }
}

/* Below this the row genuinely cannot hold everything: at 320px the search had been
   squeezed to 47 pixels, which is not a search box, it is a decoration in the shape of
   one. It takes a row of its own instead — the same thing every mail and shop app does at
   this width — and keeps its full width on the narrowest phone anybody reads this on.

   360, not 430. A second row costs 44px of sticky header on every screen and every page,
   and at 375 — the width of a great many phones — the single row still leaves a field
   people can type into. The second row is for where the first genuinely fails.

   Keyed on width alone. It was once `pointer: coarse` as well, and a browser that does
   not report a touch pointer — an older or lighter phone browser, a narrow window — kept
   the single row at 320px and ran 42px past the screen. */
@media (max-width: 360px) {
  /* Two rows make the header 124px, and pinned it kept that on a 568px screen all the
     way down every page. Here it scrolls away with the page. `relative`, not `static`:
     the menu drops from `top: 100%` of the header, and needs it to stay its box. */
  .top { flex-wrap: wrap; position: relative; }
  .top .sp { width: 100%; margin-left: 0; order: 3; }
  .top .sp > form, .top .sp .has-suggest { flex: 1 1 auto; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

@media print {
  .top, .mapui, .zlevel, .suggest, footer.site { display: none; }
  body { background: #fff; color: #000; }
  .panel, .note, .gap-panel { border: 1px solid #999; }
}

/* =============================================== the map stage ============ */
/* Controls, map and panel as one instrument. Three columns on a desk, stacked on a phone
   with the map first — on a small screen the map is the thing you came for. */
.mapstage {
  display: grid;
  grid-template-columns: 250px minmax(0, 1fr) 320px;
  gap: var(--s3);
  align-items: stretch;
  margin: var(--s5) 0 var(--s3);
}
.mapstage .mapshell { border-radius: var(--r-lg); }
#map { height: 620px; }
.mapside, .mappanel {
  background: var(--stone); border: 1px solid var(--edge-soft);
  border-radius: var(--r-lg); overflow-y: auto; max-height: 620px;
}
.mapside { padding: var(--s3); }
.map-reset {
  position: absolute; bottom: var(--s4); right: var(--s4); z-index: var(--z-mapui);
  box-shadow: var(--lift-2);
}
/* The map's two bottom corners: the zoom label on the left, "Back to the whole state" on
   the right. At 320px the button is wider than the room the label leaves it, and it sat
   11px over the label. It rises above the label's row instead. Placed after the rule it
   overrides, which it matches at the same specificity. */
@media (max-width: 440px) {
  .map-reset { bottom: calc(var(--s4) + 2.25rem + var(--s2)); }
}
.mapfoot {
  color: var(--ink-faint); font-size: var(--t-xs); max-width: 78ch;
  display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
}

/* control blocks */
.ctrl-block { padding: var(--s3) 0; border-top: 1px solid var(--edge-soft); }
.ctrl-block:first-child { border-top: 0; padding-top: 0; }
.ctrl-block h6 {
  margin: 0 0 var(--s2); font-size: var(--t-2xs); color: var(--ink-faint); font-weight: 700;
}
details.ctrl-block > summary {
  cursor: pointer; font-size: var(--t-2xs); color: var(--ink-faint); font-weight: 700;
  list-style: none; padding: var(--s1) 0;
}
details.ctrl-block > summary::-webkit-details-marker { display: none; }
details.ctrl-block > summary::after { content: " ＋"; }
details.ctrl-block[open] > summary::after { content: " −"; }
.ctrl-note { font-size: var(--t-2xs); color: var(--ink-faint); margin: var(--s2) 0 0; line-height: 1.45; }
.legend { margin-top: var(--s3); }
.legend-swatches { display: flex; height: 10px; border-radius: 3px; overflow: hidden; }
.legend-swatches i { flex: 1; }
.legend-ends {
  display: flex; justify-content: space-between; font-size: var(--t-2xs);
  color: var(--ink-faint); margin-top: 4px; font-family: var(--sans);
  font-variant-numeric: tabular-nums lining-nums;
}

/* the intelligence panel */
.panel-head { padding: var(--s4); border-bottom: 1px solid var(--edge-soft); background: var(--paper); }
.panel-head h3 { margin: 0 0 2px; font-size: var(--t-lg); }
.panel-head p { margin: 0; font-size: var(--t-xs); color: var(--ink-faint); }
.panel-back {
  display: inline-flex; align-items: center; gap: 0.45em;
  font-size: var(--t-2xs); color: var(--ink-soft);
  margin-bottom: var(--s2); text-decoration: none;
}
/* The arrow was a ← character, and Inter's Latin subset has no U+2190 — so it rendered
   in whatever fallback face the device happened to have. Drawn instead, which no font
   can fail to provide. */
.panel-back::before {
  content: ""; width: 0.42em; height: 0.42em; flex: none;
  border-left: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
}
.panel-back:hover { color: var(--sky); }
.panel-body { padding: var(--s4); }
.panel-body h4 { margin: var(--s4) 0 var(--s2); font-size: var(--t-xs); color: var(--ink-faint); font-weight: 700; }
.panel-body h3.sub { margin: var(--s4) 0 var(--s2); font-size: var(--t-xs); color: var(--ink-faint); font-weight: 700; }
.panel-figure {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-2xl);
  font-weight: 600; letter-spacing: -0.03em; color: var(--sky); line-height: 1.1;
}
.panel-figure span {
  display: block; font-family: var(--sans); font-size: var(--t-xs); font-weight: 400;
  letter-spacing: 0; color: var(--ink-soft); margin-top: 4px;
}
.panel-note { font-size: var(--t-xs); color: var(--ink-soft); line-height: 1.5; margin: 0 0 var(--s2); }
.panel-note.stop-note { color: var(--danger); }
.panel-stats { display: flex; gap: var(--s4); margin: var(--s3) 0; flex-wrap: wrap; }
.panel-stats div { display: flex; flex-direction: column; }
.panel-stats b { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-lg); font-weight: 600; }
.panel-stats b.stop { color: var(--danger); }
.panel-stats span { font-size: var(--t-2xs); color: var(--ink-faint); }
.panel-row {
  display: flex; justify-content: space-between; gap: var(--s3); align-items: baseline;
  padding: 8px 0; border-top: 1px solid var(--edge-soft); font-size: var(--t-xs);
  color: var(--ink); text-decoration: none;
}
a.panel-row:hover { color: var(--sky); text-decoration: none; }
.panel-row b { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }
.panel-kv { display: grid; grid-template-columns: 110px 1fr; gap: 0; font-size: var(--t-xs); margin: var(--s3) 0; }
.panel-kv dt { color: var(--ink-faint); padding: 7px 0; border-top: 1px solid var(--edge-soft); }
.panel-kv dd { margin: 0; padding: 7px 0; border-top: 1px solid var(--edge-soft); }
.panel-kv dd.stop { color: var(--danger); }
.panel-body .btn { width: 100%; justify-content: center; margin-top: var(--s2); }

/* =================================================== the sheet =========== */
/* The panel, presented as a sheet over the bottom of the screen wherever the map stage is
   stacked into one column. Below the map it sat under the whole layer rail — tapping a dot
   put its details off the bottom of the phone. map.js decides when the panel is a sheet;
   this decides what that looks like. Above the map's own controls, and below the menu, so
   the menu can still open over it.

   Keyed on the id: the stacked-stage rules that size this panel reach it through three
   classes, and a sheet has to win against all of them without an !important. */
.sheet-bar { display: none; }

@media (max-width: 1100px) {
  #map-panel.is-sheet {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-sheet);
    max-width: 640px; margin-inline: auto;
    height: auto; max-height: min(58vh, 540px); max-height: min(58dvh, 540px);
    overflow-y: auto; overscroll-behavior: contain;
    background: var(--paper);
    border-radius: var(--r-lg) var(--r-lg) 0 0; border-bottom: 0;
    box-shadow: var(--lift-3);
    padding-bottom: env(safe-area-inset-bottom);
    transform: translateY(0);
    transition: transform var(--calm);
    animation: sheet-in var(--calm);
  }
  /* Folded to its title bar. Hidden overflow, so the fold is a fold and not a second
     scrollbar sitting a few pixels tall. */
  #map-panel.is-sheet.is-peek { transform: translateY(calc(100% - 56px)); overflow: hidden; }
  /* The map is off screen (the homepage, scrolled past it): out of the way entirely. */
  #map-panel.is-sheet.is-away { transform: translateY(100%); }

  #map-panel.is-sheet .sheet-bar {
    display: flex; align-items: center;
    position: sticky; top: 0; z-index: 1;
    min-height: 56px; padding-right: var(--s2);
    background: var(--paper); border-bottom: 1px solid var(--edge-soft);
  }
  #map-panel.is-sheet .panel-head { border-top: 0; }
}

@keyframes sheet-in { from { transform: translateY(100%); } }

.sheet-grip {
  flex: 1; min-width: 0; min-height: 56px;
  display: flex; flex-direction: column; justify-content: center; gap: 7px;
  padding: 8px var(--s4); border: 0; background: none;
  font: inherit; color: var(--ink); text-align: left; cursor: pointer;
}
.sheet-handle {
  align-self: center; width: 36px; height: 4px; border-radius: 2px; background: var(--edge);
}
.sheet-title {
  font-weight: 600; font-size: var(--t-sm); max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The cross is drawn, like the back arrow above: a glyph is at the mercy of whatever face
   the device falls back to. */
.sheet-close {
  flex: none; position: relative; width: 44px; height: 44px;
  border: 0; border-radius: var(--r-sm); background: none; color: var(--ink-soft); cursor: pointer;
}
.sheet-close::before, .sheet-close::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 16px; height: 1.5px; background: currentColor;
}
.sheet-close::before { transform: translate(-50%, -50%) rotate(45deg); }
.sheet-close::after { transform: translate(-50%, -50%) rotate(-45deg); }
.sheet-close:hover { color: var(--ink); background: var(--stone); }
.sheet-grip:focus-visible, .sheet-close:focus-visible {
  outline: 2px solid var(--focus); outline-offset: -2px;
}

/* =================================================== charts ============== */
.bar-chart { display: flex; flex-direction: column; gap: 3px; margin: var(--s3) 0; }
.bar-row {
  display: grid; grid-template-columns: 128px minmax(0, 1fr) 78px; gap: var(--s2);
  align-items: center; background: none; border: 0; padding: 4px 6px; border-radius: var(--r-sm);
  font: inherit; font-size: var(--t-xs); color: var(--ink); text-align: left; cursor: pointer;
  transition: background var(--quick);
}
.bar-row:disabled { cursor: default; }
.bar-row:not(:disabled):hover { background: var(--sky-wash); }
.bar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-track { background: var(--stone-deep); border-radius: 3px; height: 12px; overflow: hidden; }
.bar-fill { display: block; height: 100%; border-radius: 3px; }
.bar-value {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-2xs);
  color: var(--ink-soft); text-align: right;
}
.chart-note { font-size: var(--t-2xs); color: var(--ink-faint); margin: var(--s2) 0 0; line-height: 1.5; }

.stack { display: flex; height: 16px; border-radius: 4px; overflow: hidden; margin: var(--s3) 0 var(--s2); }
.stack-part { display: block; }
.stack-key { display: flex; flex-wrap: wrap; gap: var(--s3); font-size: var(--t-2xs); color: var(--ink-soft); }
.stack-key i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 5px; }
.stack-key b { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; }

.cols { display: flex; align-items: flex-end; gap: var(--s3); height: 150px; margin: var(--s4) 0 0; }
.col { flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%; justify-content: flex-end; }
.col-bar { width: 100%; background: var(--sky); border-radius: 4px 4px 0 0; min-height: 4px; }
.col-value { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-2xs); color: var(--ink-soft); margin-bottom: 4px; }
.col-label { font-size: var(--t-2xs); color: var(--ink-faint); margin-top: 6px; }

/* ============================================ recently awarded =========== */
.intel-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: var(--s4); }
.intel-side { display: flex; flex-direction: column; gap: var(--s4); }
.awards { display: flex; flex-direction: column; }
.award {
  display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: var(--s4);
  padding: var(--s4) 0; border-top: 1px solid var(--edge-soft);
}
.award:first-child { border-top: 0; }
.award-value {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-lg);
  font-weight: 600; color: var(--sky); letter-spacing: -0.02em;
}
.award-body h4 { margin: 0 0 4px; font-size: var(--t-sm); line-height: 1.4; font-weight: 580; }
.award-body h3.sub { margin: 0 0 4px; font-size: var(--t-sm); line-height: 1.4; font-weight: 580; }
.award-body h4 a { color: var(--ink); text-decoration: none; }
.award-body h3.sub a { color: var(--ink); text-decoration: none; }
.award-body h4 a:hover { color: var(--sky); }
.award-body h3.sub a:hover { color: var(--sky); }
.award-meta { font-size: var(--t-xs); color: var(--ink-faint); margin: 0 0 3px; }
.award-meta a { color: var(--ink-soft); }
.award-ev { margin: var(--s2) 0 0; display: flex; gap: var(--s2); flex-wrap: wrap; }

/* ================================================= LGA grid ============== */
.lga-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--s2); }
.lga-card {
  display: flex; flex-direction: column; gap: 2px; padding: var(--s3) var(--s4);
  background: var(--stone); border: 1px solid var(--edge-soft); border-radius: var(--r-md);
  color: var(--ink); text-decoration: none; transition: background var(--quick), border-color var(--quick);
}
.lga-card:hover { background: var(--sky-wash); border-color: var(--sky-line); text-decoration: none; }
.lga-card b { font-size: var(--t-sm); font-weight: 600; }
.lga-card span { font-size: var(--t-2xs); color: var(--ink-faint); font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; }
.lga-card .lga-per { color: var(--ink-soft); }

@media (max-width: 1100px) {
  .mapstage { grid-template-columns: minmax(0, 1fr); }
  .mapside, .mappanel { max-height: none; }
  .mapstage .mapshell { order: -1; }
  .intel-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 860px) {
  #map { height: 440px; }
  .award { grid-template-columns: minmax(0, 1fr); gap: var(--s1); }
  .bar-row { grid-template-columns: 96px minmax(0, 1fr) 68px; }
}

/* ============================================ what you can do here ======= */
/* Three concrete actions under the search, so a first-time visitor knows what the site is
   for before the map has even loaded. Numbered because they are a sequence a reader
   actually follows — place, then project, then what is missing — not decorative markers. */
.doing {
  list-style: none; padding: 0; margin: var(--s5) 0 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--s4) var(--s5); max-width: 92ch; counter-reset: doing;
}
.doing li {
  margin: 0; font-size: var(--t-sm); color: var(--ink-soft); line-height: 1.5;
  padding-top: var(--s3); border-top: 1px solid var(--edge);
}
.doing li b { display: block; color: var(--ink); font-weight: 620; margin-bottom: 2px; }

/* ============================================================ representation

   The ladder is the shape of the answer to "who is answerable for my area": one rung per
   tier of government, climbed from the seat a person actually votes in up to the state.
   It is a stepped list rather than a card grid because the rungs are ordered and unequal —
   a state constituency and a senatorial district are not peers, and laying them out as
   identical tiles would say they were.

   Political zones are the one thing on this site coloured outside the brand ramp. Money is
   sky blue throughout; representation is a different question over the same geography, and
   the violet says so without competing with the choropleth underneath it. */

:root { --civic: oklch(0.455 0.128 315); --civic-wash: oklch(0.966 0.022 315); }
:root[data-theme="dark"], :root:not([data-theme="light"]) {
  --civic: oklch(0.780 0.110 315); --civic-wash: oklch(0.292 0.038 315);
}

.ladder { display: flex; flex-direction: column; gap: var(--s3); }

.rung {
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  background: var(--paper);
  padding: var(--s4) var(--s5);
}
.rung-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s3);
  padding-bottom: var(--s3); margin-bottom: var(--s3);
  border-bottom: 1px solid var(--edge-soft);
}
.rung-head a { font-weight: 600; color: var(--ink); }
.rung-head a:hover { color: var(--civic); }
.rung-head span { font-size: var(--t-xs); color: var(--ink-faint); }

.rung-person {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3);
  padding: var(--s2) 0; color: var(--ink);
}
a.rung-person:hover { text-decoration: none; }
a.rung-person:hover b { color: var(--civic); text-decoration: underline; }
.rung-person b { font-weight: 600; }
.rung-person span { font-size: var(--t-sm); color: var(--ink-soft); }
.rung-person em { font-style: normal; color: var(--caution); }
.rung-person.vacant b { color: var(--danger); }
.rung-person.vacant span { color: var(--ink-faint); }

/* Compact variants for the map panel, where the same information appears in a column
   about a third the width. */
.rep-rung { padding: var(--s3) 0; border-bottom: 1px solid var(--edge-soft); }
.rep-rung:last-of-type { border-bottom: 0; }
.rep-zone { font-weight: 600; }
.rep-level { display: block; font-size: var(--t-2xs); color: var(--ink-faint); margin-bottom: var(--s2); }
.rep-row { display: flex; flex-direction: column; gap: 2px; padding: var(--s2) 0; }
.rep-row span { font-size: var(--t-2xs); color: var(--ink-soft); }
.rep-row em { font-style: normal; color: var(--caution); }
.rep-row.vacant b { color: var(--danger); }

.tally { font-size: var(--t-lg); margin: var(--s2) 0 0; }
.tally b { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; }
.quiet { color: var(--ink-faint); }
.ctrl-note.tight, .panel-note.tight { margin-top: var(--s1); }

ul.plain { list-style: none; padding: 0; margin: var(--s2) 0 0; }
ul.plain li { padding: var(--s2) 0; border-bottom: 1px solid var(--edge-soft); font-size: var(--t-sm); }
ul.plain li:last-child { border-bottom: 0; }

/* ===================================================== earth observation

   Twelve columns, one per month, answering one question: could anybody have looked. Full
   height means a usable scene existed, a stub means the sky was shut. Drawn as a bar row
   rather than a calendar heatmap because the shape that matters is seasonal — a short
   dry-season opening and a long blind stretch either side — and a row of bars shows that
   at a glance where a grid of tinted squares hides it. */

.eo-months { display: flex; align-items: flex-end; gap: var(--s1);
  height: 120px; padding-top: var(--s2); }
.eo-month { flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: flex-end; height: 100%; gap: var(--s1); }
.eo-bar { width: 100%; border-radius: var(--r-sm) var(--r-sm) 2px 2px; min-height: 4px;
  background: var(--edge); transition: opacity var(--quick); }
.eo-month.open  .eo-bar { background: var(--t2); }
.eo-month.part  .eo-bar { background: var(--t2-wash); border: 1px solid var(--t2); }
.eo-month.shut  .eo-bar { background: var(--edge); }
.eo-label { font-size: var(--t-2xs); color: var(--ink-faint); font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; }
.eo-month:hover .eo-bar { opacity: .75; }

/* A company record that has changed carries its own past inline. Collapsed by default:
   the current state is the answer, and the history is the evidence for anyone who wants
   to see when it moved. */
details.hist { margin-top: var(--s2); }
details.hist summary { font-size: var(--t-2xs); color: var(--caution); cursor: pointer; }
details.hist p { font-size: var(--t-2xs); color: var(--ink-faint); margin: var(--s1) 0 0; }

/* ========================================================= reporting form

   The form is the only place a member of the public writes to this platform, so it is
   built to be finished on a phone in under a minute: large tap targets, one question
   visible as a question, and no field that is not genuinely needed. Radios are drawn as
   full-width rows rather than a compact list because a thumb is not a mouse pointer. */

.wrap.narrow { max-width: 46rem; }
.reportform fieldset { border: 0; padding: 0; margin: 0 0 var(--s5); }
.reportform legend { font-size: var(--t-lg); font-weight: 600; margin-bottom: var(--s3); }
.choices { display: flex; flex-direction: column; gap: var(--s2); }
.choice {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--edge); border-radius: var(--r-md);
  background: var(--paper); cursor: pointer;
  transition: border-color var(--quick), background var(--quick);
}
.choice:hover { border-color: var(--sky-line); background: var(--sky-wash); }
.choice:has(input:checked) { border-color: var(--sky); background: var(--sky-wash); }
.choice input { width: 20px; height: 20px; accent-color: var(--sky); flex: none; }
.choice span { font-size: var(--t-md); }

.reportform .field { display: block; margin-bottom: var(--s4); }
.reportform .field > span { display: block; font-weight: 600; margin-bottom: var(--s2); }
.reportform .field em { font-style: normal; font-weight: 400; color: var(--ink-faint); }
.reportform textarea {
  width: 100%; padding: var(--s3); font: inherit; color: var(--ink);
  background: var(--paper); border: 1px solid var(--edge); border-radius: var(--r-md);
}
.reportform textarea:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
.locrow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3);
  margin-bottom: var(--s5); }
.locrow .chart-note { flex: 1 1 16rem; margin: 0; }

/* Offline state. A band rather than a toast: a dropped connection is a condition the
   reader is in, not an event that happened once, and it should stay visible while it lasts. */
.netstate {
  margin: 0; padding: var(--s2) var(--s5);
  background: var(--t4-wash); color: var(--t4);
  font-size: var(--t-xs); border-bottom: 1px solid var(--edge);
}

/* ===================================================== right of reply

   A reply is set as a quotation because that is what it is: somebody else's words, carried
   whole. The rule is on the left as a single hairline rather than a thick accent bar — this
   is a quotation mark, not a callout, and the words are doing the work. */

blockquote.reply {
  margin: 0 0 var(--s4);
  padding: var(--s4) var(--s5);
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  background: var(--stone);
}
blockquote.reply p { margin: 0 0 var(--s3); font-size: var(--t-md); line-height: 1.65; }
blockquote.reply footer {
  font-size: var(--t-xs); color: var(--ink-soft);
  padding-top: var(--s3); border-top: 1px solid var(--edge-soft);
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); align-items: baseline;
}
/* What was actually checked, never hidden behind the quotation. */
.reply-check {
  margin-left: auto; font-family: var(--sans); font-size: var(--t-2xs);
  color: var(--t4); background: var(--t4-wash);
  padding: 2px var(--s2); border-radius: var(--r-sm);
}

/* ==================================================== the map rail, rebuilt

   The rail used to be three stacked blocks of radio buttons that ran past the bottom of
   the map — the political-boundary options were below the fold and effectively invisible.
   Three changes: the measure becomes a segmented control because the options are mutually
   exclusive and short, the boundary choice becomes a select because four options that are
   rarely changed do not deserve four permanent rows, and the legend gains a label per
   band instead of two endpoints.

   The legend change is the substantive one. "lowest to ₦188k+" tells a reader the range
   and nothing about where any particular LGA sits inside it, which is the only question
   the colour is there to answer. */

.rail-group { padding: var(--s4) 0; border-top: 1px solid var(--edge-soft); }
.rail-group:first-child { border-top: 0; padding-top: 0; }

/* A foldable section, and the count of what is on inside it. The rail reached nineteen
   screens of scrolling before these; a reader working on money should not have to scroll
   past the ground layers to reach a switch, nor re-close them on every visit. */
details.rail-group > summary {
  cursor: pointer; display: flex; align-items: center; gap: var(--s2);
  list-style: none; user-select: none;
}
details.rail-group > summary::-webkit-details-marker { display: none; }
details.rail-group > summary::after {
  content: ""; width: 6px; height: 6px; margin-left: 2px; flex: 0 0 auto;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--quick);
}
details.rail-group[open] > summary::after { transform: rotate(-135deg) translate(-1px, -1px); }
details.rail-group > summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.rail-on {
  margin-left: auto; font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--t-2xs); font-weight: 500; color: var(--sky); white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  details.rail-group > summary::after { transition: none; }
}
.rail-group h6, details.rail-group > summary {
  margin: 0 0 var(--s3); font-size: var(--t-2xs); color: var(--ink-faint);
  font-weight: 700; letter-spacing: 0.03em; text-transform: none;
}
details.rail-group > summary { cursor: pointer; list-style: none; margin-bottom: 0; }
details.rail-group > summary::-webkit-details-marker { display: none; }
details.rail-group > summary::after { content: " +"; font-family: var(--sans); }
details.rail-group[open] > summary::after { content: " −"; }
details.rail-group[open] > summary { margin-bottom: var(--s3); }

/* Segmented control: one row, one choice, no wasted vertical space. */
/* Five measures do not fit one row of a 254px rail, so they wrap into two. Each option
   keeps a floor wide enough for its label rather than being squeezed to fit. */
.seg { display: flex; flex-wrap: wrap; background: var(--stone-deep); border-radius: var(--r-sm); padding: 3px; gap: 2px; }
.seg-opt { flex: 1 1 68px; position: relative; }
.seg-opt input { position: absolute; opacity: 0; inset: 0; cursor: pointer; }
.seg-opt span {
  display: block; text-align: center; padding: 6px 4px; border-radius: 6px;
  font-size: var(--t-2xs); color: var(--ink-soft); cursor: pointer;
  transition: background var(--quick), color var(--quick);
}
.seg-opt:hover span { color: var(--ink); }
.seg-opt input:checked + span { background: var(--paper); color: var(--sky); font-weight: 620; box-shadow: var(--lift-1); }
.seg-opt input:focus-visible + span { outline: 2px solid var(--focus); outline-offset: 1px; }

.rail-select {
  width: 100%; padding: var(--s2) var(--s3); font: inherit; font-size: var(--t-xs);
  color: var(--ink); background: var(--paper); border: 1px solid var(--edge);
  border-radius: var(--r-sm); cursor: pointer;
}
.rail-select:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }

/* A band per row, each with the value where it starts. */
.legend-scale { display: flex; flex-direction: column; gap: 3px; margin-top: var(--s3); }
.legend-step { display: flex; align-items: center; gap: var(--s2); }
.legend-step i { width: 22px; height: 11px; border-radius: 3px; flex: none; }
.legend-step span {
  font-size: var(--t-2xs); color: var(--ink-faint);
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
}

/* ================================================ the money page ========= */

/* The ratio between an unfinished contract and a finished one is the finding on
   this page, so it is set at display size and given nothing to compete with. */
.ratio {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  font-size: clamp(2.6rem, 7vw, 3.6rem); line-height: 1; font-weight: 600;
  color: var(--t4); margin: var(--s2) 0 var(--s3); letter-spacing: -0.02em;
}

/* Concentration read against the thresholds it is judged by, because 562 means
   nothing to a reader who does not already know where 1,500 sits. */
.hhi { margin: var(--s4) 0 var(--s2); }
.hhi-track {
  position: relative; display: block; height: 14px; border-radius: 7px;
  background: var(--stone-deep); overflow: visible;
}
.hhi-fill {
  position: absolute; inset: 0 auto 0 0; display: block; border-radius: 7px;
  background: var(--sky); min-width: 6px;
}
.hhi-mark {
  position: absolute; top: -5px; bottom: -5px; width: 2px; border-radius: 1px;
  background: var(--ink-faint);
}
.hhi-ends {
  position: relative; display: flex; justify-content: space-between; margin-top: 7px;
  font-size: var(--t-2xs); color: var(--ink-faint);
}
/* The middle label sits under its own tick rather than being spaced evenly, because the
   whole point of the mark is where 1,500 falls on this scale. */
.hhi-ends .at-threshold { position: absolute; left: 60%; transform: translateX(-50%); }
.hhi-ends i { font-style: normal; }

/* A row that reports rather than links — same rhythm, no affordance. */
.panel-row.static { cursor: default; }

/* Bands of lateness read as a scale, so the count carries a proportional rule. */
.band { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: var(--s3) var(--s3);
  align-items: baseline; padding: 9px 0; border-top: 1px solid var(--edge-soft); }
.band-label { font-size: var(--t-xs); }
.band-figure { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--t-xs); color: var(--ink-soft); white-space: nowrap; }
.band-rule { grid-column: 1 / -1; height: 6px; border-radius: 3px; background: var(--stone-deep); }
.band-rule i { display: block; height: 100%; border-radius: 3px; background: var(--t4); }
.band.zero .band-rule i { background: var(--edge); }

/* On a phone the three labels collide, so the scale keeps its numbers and the words
   they were carrying are already in the sentence underneath. */
@media (max-width: 720px) {
  .hhi-ends { font-size: 12px; }
  .hhi-ends i { display: none; }
}

/* ================================================ the map page =========== */

/* The map has a page of its own, where the constraint is the window rather than the slot
   it used to sit in halfway down the home page. Height comes from the viewport minus the
   header and a little breathing room, floored so a short laptop window still gets a map
   worth panning. */
.wrap-wide { max-width: 1600px; }
.lede-tight { padding: var(--s6) 0 var(--s4); }

.mapstage-tall {
  max-width: 1600px;
  margin: 0 auto var(--s4);
  padding: 0 var(--s5);
  grid-template-columns: 270px minmax(0, 1fr) 350px;
}
.mapstage-tall #map,
.mapstage-tall .mapside,
.mapstage-tall .mappanel { height: clamp(520px, calc(100vh - 210px), 900px); max-height: none; }

/* The doorway that replaced the map on the home page. One link: the whole block is the
   target, so it works the same whether a reader goes for the shape or the words. */
.mapdoor {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: var(--s6); align-items: center;
  margin: var(--s6) 0 var(--s3); padding: var(--s6);
  background: var(--stone); border: 1px solid var(--edge-soft);
  border-radius: var(--r-lg); color: inherit; text-decoration: none;
  transition: border-color var(--calm), background var(--calm);
}
.mapdoor:hover, .mapdoor:focus-visible {
  border-color: var(--sky-line); background: var(--sky-wash); text-decoration: none;
}
.mapdoor h2 { margin-bottom: var(--s2); }
.mapdoor p { color: var(--ink-soft); max-width: 52ch; }
.mapdoor-meta {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--t-xs); color: var(--ink-faint); margin: var(--s3) 0 var(--s4);
}
.mapdoor .btn { pointer-events: none; }

.mapdoor-art { display: flex; justify-content: center; }
.delta-outline { width: 100%; max-width: 420px; height: auto; }
/* Flat, deliberately. Colouring it would make it a chart, and a chart nobody can read the
   legend of is worse than a shape. It says "this is the place"; the map says the rest. */
.delta-outline path {
  fill: var(--sky-wash); stroke: var(--sky-line); stroke-width: 1;
  stroke-linejoin: round; transition: fill var(--calm);
}
.mapdoor:hover .delta-outline path,
.mapdoor:focus-visible .delta-outline path { fill: var(--paper); }

@media (max-width: 1100px) {
  .mapstage-tall { grid-template-columns: minmax(0, 1fr); padding: 0 var(--s4); }
  .mapstage-tall #map { height: 60vh; min-height: 380px; }
  .mapstage-tall .mapside, .mapstage-tall .mappanel { height: auto; max-height: 400px; }
  .mapdoor { grid-template-columns: minmax(0, 1fr); gap: var(--s4); padding: var(--s5); }
  .mapdoor-art { order: -1; }
  .delta-outline { max-width: 300px; }
}

/* A panel row whose value is a sentence rather than a figure. The default row puts name
   and value on one line with the value unwrapped, which is right for "₦187.35bn" and
   clips "26.9% of the money, 3.1% of the people" against a 350px rail.

   Not called .stack, which is the stacked-bar chart and carries height: 16px and
   overflow: hidden. .panel-row.stack quietly inherited both and clipped every row to a
   sliver — a collision, not a cascade, and the kind that leaves no error behind. */
.panel-row.sentence {
  display: block;
}
.panel-row.sentence b {
  display: block; white-space: normal; font-weight: 500;
  color: var(--ink-soft); margin-top: 2px;
}

/* ====================================== the map, as the page ============= */

/* The map page is the map. Not a page with a map on it: no lede above it pushing it under
   the fold, no measured column width it has to sit inside. The window minus the header is
   the stage, and everything that used to be prose above it is in the two rails beside it,
   which is where a reader looks once the map is the thing they came for.

   What genuinely does not fit — sources, the table version, what nobody publishes — is one
   scroll down, out of the tool's way but still attached to it. */
body.is-map main { padding: 0; }
body.is-map .mapstage-tall {
  /* 100dvh, not vh: on a phone the browser chrome collapses as you scroll, and vh is
     measured against the collapsed height, so the map overflows by the height of the
     toolbar until you scroll — on the one page where scrolling is not what you want. */
  height: calc(100dvh - var(--header-h));
  max-width: none; margin: 0; padding: var(--s3);
  gap: var(--s3);
  grid-template-columns: 280px minmax(0, 1fr) 360px;
}
body.is-map .mapstage-tall #map,
body.is-map .mapstage-tall .mapside,
body.is-map .mapstage-tall .mappanel { height: 100%; max-height: none; }
body.is-map .mapstage-tall .mapshell { height: 100%; }

/* Everything under the map gets a rule and a breath, so the scroll reads as leaving the
   tool rather than as more of it. */
.map-below {
  border-top: 1px solid var(--edge-soft);
  padding-top: var(--s7);
  margin-top: var(--s3);
}

/* Places named in an LGA's own records. Two columns because the list runs long for the
   larger areas and a single column would push everything below it out of the panel. */
.placelist {
  list-style: none; margin: var(--s2) 0 var(--s4); padding: 0;
  columns: 2; column-gap: var(--s4); font-size: var(--t-2xs);
}
.placelist li {
  break-inside: avoid; display: flex; justify-content: space-between; gap: var(--s2);
  padding: 3px 0; color: var(--ink-soft); line-height: 1.35;
}
.placelist b {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums;
  color: var(--ink-faint); flex: none;
}

@media (max-width: 1100px) {
  body.is-map .mapstage-tall {
    height: auto; grid-template-columns: minmax(0, 1fr); padding: var(--s3) var(--s4);
  }
  body.is-map .mapstage-tall #map { height: 62dvh; min-height: 380px; }
  body.is-map .mapstage-tall .mapside,
  body.is-map .mapstage-tall .mappanel { height: auto; max-height: 420px; }
  .placelist { columns: 1; }
}

/* The areas list. Most of an LGA's places have no contract against them, so the ones that
   do are marked rather than the ones that do not — marking the majority would turn the
   list into a wall of ticks and hide the finding. */
.placelist.areas li { color: var(--ink-faint); }
.placelist.areas li.has { color: var(--ink); font-weight: 550; }
.placelist.areas li.has span::before {
  content: "●"; color: var(--series-running); font-size: 0.7em;
  margin-right: 5px; vertical-align: 1px;
}

/* ================================================ search suggestions ===== */

.has-suggest { position: relative; }

/* Above everything on the page and below the header, which stays sticky over it. Fixed
   rather than absolute would escape the header's stacking context and float over the nav. */
.suggest-box {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0;
  z-index: var(--z-dropdown);
  max-height: min(60vh, 480px); overflow-y: auto;
  background: var(--paper); border: 1px solid var(--edge);
  border-radius: var(--r-md); box-shadow: var(--lift-3);
  padding: var(--s2);
}
/* In the header the box would otherwise be as narrow as the input, which is 170px. */
.top .suggest-box { width: 380px; left: auto; right: 0; }

.suggest-group + .suggest-group { border-top: 1px solid var(--edge-soft); margin-top: 4px; padding-top: 4px; }
.suggest-group h6 {
  margin: 0; padding: var(--s2) var(--s2) 4px;
  font-size: var(--t-2xs); font-weight: 600; color: var(--ink-faint);
}

.suggest-item {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px var(--s3); align-items: baseline;
  padding: 7px var(--s2); border-radius: var(--r-sm);
  color: var(--ink); text-decoration: none;
}
/* One highlight class for pointer and keyboard alike, so the two cannot disagree about
   which row is current. */
.suggest-item.on, .suggest-item:hover { background: var(--sky-wash); text-decoration: none; }
.suggest-item:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.suggest-name { font-size: var(--t-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.suggest-name b { font-weight: 700; color: var(--sky); }
.suggest-meta {
  grid-column: 1; font-size: var(--t-2xs); color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.suggest-item .ev { grid-column: 2; grid-row: 1 / span 2; align-self: center; }

.suggest-empty { margin: 0; padding: var(--s3) var(--s2); font-size: var(--t-xs); color: var(--ink-soft); }
.suggest-empty span { color: var(--ink-faint); }

@media (max-width: 860px) {
  .top .suggest-box { width: min(340px, calc(100vw - var(--s5) * 2)); }
}

/* Every place inside an LGA, on its own page. Wider than the map panel's two columns, so
   it fills the measure and stays scannable — these lists run to eighty-odd names. */
.arealist {
  list-style: none; margin: var(--s4) 0 var(--s2); padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 2px var(--s4);
}
.arealist li {
  display: flex; align-items: baseline; gap: var(--s2);
  padding: 6px 0; border-top: 1px solid var(--edge-soft);
  font-size: var(--t-xs); color: var(--ink-soft);
}
.arealist li.has { color: var(--ink); }
.arealist li.has .an { font-weight: 600; }
.arealist .an { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.arealist .ak { font-size: var(--t-2xs); color: var(--ink-faint); margin-left: auto; flex: none; }
.arealist li.has b {
  font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-2xs); color: var(--series-running);
  flex: none; margin-left: var(--s2);
}
.arealist li.has .ak { display: none; }

/* ================================================ office holders ========= */

/* The lede used to be a column of text against half a screen of nothing. The chooser goes
   in the space, which is also where a reader looks for it: the heading asks about "your
   area" and the control that answers it should be beside the question. */
.lede-split { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: var(--s7); align-items: start; }
.picker {
  background: var(--stone); border: 1px solid var(--edge-soft);
  border-radius: var(--r-lg); padding: var(--s5);
}
.picker label { display: block; font-weight: 600; font-size: var(--t-sm); margin-bottom: var(--s3); }
.picker-row { display: flex; gap: var(--s2); }
.picker-row .rail-select { flex: 1; }
.picker .btn { flex: none; }
.picker .chart-note { margin-top: var(--s3); }

/* The representation ladder on the officials page.
   Its own names: .ladder / .rung / .rung-head / .rung-person already exist for the compact
   list on an LGA page, and redefining them here changed that page as a side effect. A
   collision like this leaves no error — the other page simply starts looking wrong. */
.repladder { list-style: none; margin: var(--s5) 0 0; padding: 0 0 0 var(--s5); position: relative; }
.repladder::before {
  content: ""; position: absolute; left: 7px; top: 10px; bottom: 10px;
  width: 2px; background: var(--sky-line); border-radius: 1px;
}
.repladder .step { position: relative; padding: 0 0 var(--s5); }
.repladder .step::before {
  content: ""; position: absolute; left: calc(var(--s5) * -1 + 2px); top: 6px;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--sky); border: 2px solid var(--paper);
}
/* Not `.empty`: that is an existing block-level empty-state with padding and
   text-align: center, and hanging a modifier on it centred every gap note. */
.repladder .step.step-vacant::before { background: var(--danger); }
.step-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3); }
.step-level { font-size: var(--t-2xs); color: var(--ink-faint); }
.step-zone { font-size: var(--t-lg); font-weight: 640; letter-spacing: -0.015em; }
.step-body { font-size: var(--t-2xs); color: var(--ink-faint); }
.step-person {
  display: block; margin-top: var(--s2); padding: var(--s3) var(--s4);
  background: var(--stone); border: 1px solid var(--edge-soft);
  border-radius: var(--r-md); color: inherit; text-decoration: none;
}
.step-person:hover { border-color: var(--sky-line); background: var(--sky-wash); text-decoration: none; }
.step-person b { display: block; font-size: var(--t-md); }
.step-person span { font-size: var(--t-xs); color: var(--ink-soft); }
.step-gap {
  margin: var(--s2) 0 0; padding: var(--s3) var(--s4);
  background: var(--danger-wash); border: 1px solid var(--edge-soft);
  border-radius: var(--r-md); font-size: var(--t-xs); color: var(--ink-soft);
}
.repladder .chart-note { margin-top: var(--s2); }

/* Seats, drawn as seats. Three senators is few enough that "one of three" should be
   countable rather than inferred from the length of a bar. */
.seatcount { font-size: var(--t-sm); color: var(--ink-soft); margin: var(--s2) 0; }
.seatcount b { font-family: var(--sans); font-variant-numeric: tabular-nums lining-nums; font-size: var(--t-xl); color: var(--ink); }
.seats { display: flex; flex-wrap: wrap; gap: 3px; margin-top: var(--s2); }
.seats i { width: 11px; height: 11px; border-radius: 2px; background: var(--danger-wash);
           border: 1px solid var(--edge); }
/* The filled seats overlay the empty ones, so the two rows always agree on size. */
.seats-named { margin-top: -14px; }
.seats-named i { background: var(--sky); border-color: var(--sky); }

@media (max-width: 1100px) {
  .lede-split { grid-template-columns: minmax(0, 1fr); gap: var(--s5); }
}

/* Five years of council revenue, drawn in the table. Colour carries the direction and the
   label carries the numbers — the line is a shape to scan a column by, not a chart to read
   a value off. */
.spark-cell { width: 88px; padding-top: 6px; padding-bottom: 4px; }
.spark { display: block; overflow: visible; }
.sparkrow.big { margin: var(--s3) 0 0; }
.sparkrow.big .spark { width: 100%; height: auto; }
/* A person's name, not a figure — no tabular-nums, unlike `.tally b` beside it. */
.chairname { font-size: var(--t-lg); font-weight: 620; margin: var(--s2) 0; }

.sparkyears {
  display: flex; justify-content: space-between;
  font-size: var(--t-2xs); color: var(--ink-faint); margin-top: 2px;
}

/* Seasonal water as a bar beside its figure. Scaled to the wettest area rather than to
   100: the largest share is under 16%, and against a full-width track every area would be
   a stub the eye cannot rank. */
.waterbar { width: 130px; padding-top: 10px; padding-bottom: 10px; }
.waterbar i {
  display: block; height: 8px; border-radius: 4px;
  background: var(--series-running); min-width: 2px;
}

/* ============================================ type roles ================= */

/* Five roles, so a page is built from a vocabulary rather than from ad-hoc sizes.
   One family throughout — Public Sans carries headings, labels, body and data, figures
   included. The mono stack is for code and hashes only; it never renders a chairman's
   name as a serial number, and it no longer renders anybody's money as one either. */

/* A label above or beside a value. Not an eyebrow above every section — those are
   banned here — but the small word that says what a number is. */
.label {
  font-size: var(--t-2xs); font-weight: 600; letter-spacing: 0.01em;
  color: var(--ink-faint);
}

/* Data. Tabular figures so a column of naira lines up on the decimal, lining figures so
   they sit on the baseline beside capitals, and a touch tighter than body text because
   digits are wider than letters. */
.figure {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  letter-spacing: -0.01em;
}


/* ================================================ the homepage =========== */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* --- what this is ------------------------------------------------------- */

/* One column, measured. The old lede put a heading against half a screen of nothing;
   this keeps the opening statement, the search and the four figures on one axis so the
   eye starts in one place and travels down. */
/* The measure belongs to the prose, not to the block. Capping the whole hero pulled the
   figures in with it: four of them in a 787px column wrapped to three and one, and left a
   dead half-screen to the right of everything. Sentences get a line length; the ledger
   gets the page. */
.hero {
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: var(--s6) var(--s8); align-items: start;
  padding: var(--s7) 0 var(--s6);
}
.hero h1 {
  font-size: var(--t-4xl); line-height: 1.08; letter-spacing: -0.028em;
  text-wrap: balance; margin: 0 0 var(--s4); max-width: 26ch;
}
.hero-lead {
  font-size: var(--t-lg); line-height: 1.55; color: var(--ink-soft);
  max-width: 62ch; text-wrap: pretty; margin: 0 0 var(--s5);
}

/* Four figures on one rule, not four cards. A card each would make them look like
   navigation; they are one statement about the state of the register, read across. */
/* Four across on one rule, spanning the full measure of the page. They are one statement
   read left to right — what is registered, how much of the state it accounts for, how much
   of it is late, and how much of it anybody has checked — and that reading breaks the
   moment the fourth wraps onto its own line. */
.ledger {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s5) var(--s6); margin: var(--s7) 0 0;
  padding-top: var(--s5); border-top: 1px solid var(--edge);
}
@media (max-width: 1100px) { .ledger { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .ledger { grid-template-columns: minmax(0, 1fr); } }
.ledger-item dt { margin-bottom: var(--s2); }
.ledger-item dd {
  margin: 0; font-size: var(--t-3xl); line-height: 1;
  letter-spacing: -0.03em; font-weight: 640; color: var(--ink);
}
.ledger-item dd.stop { color: var(--danger); }
.ledger-item p {
  margin: var(--s2) 0 0; font-size: var(--t-xs); line-height: 1.45;
  color: var(--ink-soft); max-width: 34ch;
}

/* --- section headers ---------------------------------------------------- */

/* Heading and its one action on the same line, so the way out of a section is where the
   section begins rather than hidden at the end of it. */
.band-head {
  display: flex; align-items: end; justify-content: space-between;
  gap: var(--s5); margin: var(--s8) 0 var(--s4);
  padding-bottom: var(--s3); border-bottom: 1px solid var(--edge-soft);
}
.band-head h2 { margin: 0 0 0.25rem; font-size: var(--t-2xl); letter-spacing: -0.02em; }
.band-head p { margin: 0; color: var(--ink-soft); font-size: var(--t-sm); max-width: 68ch; }
.band-head .btn { flex: none; }

/* --- the map ------------------------------------------------------------ */

/* Full width and on its own ground, because it is the thing the site is for. The tint is
   the state's own blue at the faintest end of the ramp — enough to lift the band off the
   page, not enough to compete with the choropleth inside it. */
.mapband {
  background: var(--sky-wash); border-block: 1px solid var(--sky-line);
  padding: var(--s6) 0 var(--s5); margin: var(--s8) 0 0;
}
/* The band supplies its own rhythm; the wraps inside it must not add the page's. */
.mapband > .wrap { padding-top: 0; padding-bottom: 0; }
.mapband .band-head { margin: 0 0 var(--s4); border-bottom-color: var(--sky-line); }
.mapband .mapstage-tall {
  height: clamp(460px, 62vh, 720px); max-width: 1600px;
  margin: 0 auto; padding: 0 var(--s5);
}
.mapband .mapstage-tall #map,
.mapband .mapstage-tall .mapside,
.mapband .mapstage-tall .mappanel { height: 100%; max-height: none; }
.mapband .mapshell { height: 100%; }
.mapband .mapfoot { margin-top: var(--s4); }

/* --- contracts ---------------------------------------------------------- */

.contracts {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
  gap: var(--s4);
}
/* The whole card is the target, via one stretched link rather than a click handler on a
   div — so it is reachable by keyboard and announces itself as a link. */
.contract {
  position: relative; display: flex; flex-direction: column;
  padding: var(--s4) var(--s4) var(--s3);
  background: var(--paper); border: 1px solid var(--edge-soft);
  border-radius: var(--r-lg);
  transition: border-color var(--quick), box-shadow var(--quick), transform var(--quick);
}
.contract:hover { border-color: var(--sky-line); box-shadow: var(--lift-2); }
.contract:focus-within { border-color: var(--sky); box-shadow: var(--lift-2); }
.contract-hit { position: absolute; inset: 0; border-radius: inherit; z-index: 1; }
.contract-hit:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.contract-top { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; margin-bottom: var(--s3); }
.contract-cat { margin-left: auto; font-size: var(--t-2xs); color: var(--ink-faint); }
.contract-title {
  margin: 0 0 var(--s3); font-size: var(--t-sm); font-weight: 620; line-height: 1.35;
  letter-spacing: -0.005em;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.contract-value {
  margin: 0 0 var(--s3); font-size: var(--t-xl); font-weight: 650; letter-spacing: -0.02em;
}
.contract-facts {
  display: grid; grid-template-columns: 5.75rem minmax(0, 1fr); gap: 3px var(--s3);
  margin: 0 0 var(--s3); font-size: var(--t-xs); align-items: baseline;
}
.contract-facts dt { color: var(--ink-faint); }
.contract-facts dd { margin: 0; color: var(--ink-soft); overflow-wrap: anywhere; }
.contract-facts em { font-style: normal; color: var(--ink-faint); }
.contract-ev { margin: auto 0 0; padding-top: var(--s3); border-top: 1px solid var(--edge-soft); display: flex; flex-wrap: wrap; gap: var(--s2); }

/* Status is a word first. The colour repeats what the word says rather than replacing
   it, so the card survives greyscale and colour blindness both. */
.state {
  font-size: var(--t-2xs); font-weight: 600; padding: 3px 8px;
  border-radius: 999px; border: 1px solid transparent; white-space: nowrap;
}
.state-done { color: var(--series-done); background: var(--t1-wash); border-color: var(--t1-wash); }
.state-running { color: var(--series-running); background: var(--t0-wash); border-color: var(--sky-line); }
.state-new { color: var(--t4); background: var(--t4-wash); border-color: var(--t4-wash); }
.state-unstated { color: var(--ink-faint); background: var(--stone-deep); }
.state-late { color: var(--danger); background: var(--danger-wash); border-color: var(--danger-wash); }

.contract.is-loading { min-height: 200px; }
.contract.is-loading .skeleton { height: 100%; width: 100%; }

/* --- the 25 areas ------------------------------------------------------- */

.areas {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: var(--s3);
}
.area {
  position: relative; padding: var(--s4);
  background: var(--paper); border: 1px solid var(--edge-soft); border-radius: var(--r-md);
  transition: border-color var(--quick), box-shadow var(--quick);
}
.area:hover { border-color: var(--sky-line); box-shadow: var(--lift-1); }
.area:focus-within { border-color: var(--sky); }
.area-hit { position: absolute; inset: 0; border-radius: inherit; }
.area-hit:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.area-name { margin: 0 0 var(--s2); font-size: var(--t-sm); font-weight: 640; }
.area-value { margin: 0; font-size: var(--t-lg); font-weight: 620; letter-spacing: -0.02em; }
.area-meta { margin: 2px 0 var(--s2); font-size: var(--t-2xs); color: var(--ink-faint); }
.area-share { margin: 0 0 var(--s3); font-size: var(--t-2xs); color: var(--ink-soft); line-height: 1.5; }
/* Above the stretched link, so it is a second, separate action on the same card. */
.area-links { margin: 0; position: relative; z-index: 1; }
.area-links button {
  font: inherit; font-size: var(--t-2xs); font-weight: 550;
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--sky); text-decoration: underline; text-underline-offset: 2px;
}
.area-links button:hover { color: var(--sky-deep); }
.area-links button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 2px; }

/* --- motion ------------------------------------------------------------- */

/* The default is visible. This class only adds the offset, and it is only ever added by
   script that has already confirmed an observer exists to take it away again — so a
   failed script, a background tab or a headless render all leave the page readable
   rather than blank. */
.will-reveal > * {
  opacity: 0; transform: translateY(10px);
  transition: opacity 460ms var(--ease), transform 460ms var(--ease);
}
.will-reveal.is-in > * { opacity: 1; transform: none; }
/* Staggered within one list, because the items belong to each other and arriving
   together reads as one thing appearing. Capped: past the eighth the wait is longer than
   the interest. */
.will-reveal.is-in > *:nth-child(2) { transition-delay: 40ms; }
.will-reveal.is-in > *:nth-child(3) { transition-delay: 80ms; }
.will-reveal.is-in > *:nth-child(4) { transition-delay: 120ms; }
.will-reveal.is-in > *:nth-child(5) { transition-delay: 160ms; }
.will-reveal.is-in > *:nth-child(6) { transition-delay: 200ms; }
.will-reveal.is-in > *:nth-child(n+7) { transition-delay: 240ms; }

@media (prefers-reduced-motion: reduce) {
  .will-reveal > *,
  .will-reveal.is-in > * { opacity: 1; transform: none; transition: none; }
}

/* --- responsive --------------------------------------------------------- */

@media (max-width: 860px) {
  .hero { padding-top: var(--s6); }
  .hero h1 { font-size: var(--t-3xl); }
  .hero-search { flex-direction: column; }
  .ledger { gap: var(--s5) var(--s4); }
  .hero h1 { max-width: none; }
  .band-head { flex-direction: column; align-items: start; gap: var(--s3); }
  .mapband .mapstage-tall { height: auto; padding: 0 var(--s4); }
  .mapband .mapstage-tall #map { height: 58vh; min-height: 360px; }
  .mapband .mapstage-tall .mapside,
  .mapband .mapstage-tall .mappanel { height: auto; max-height: 380px; }
  .contracts { grid-template-columns: 1fr; }
  .areas { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
}

/* The hero at width: the claim and the search on the left, what you can do on the right.
   Below 1100px the second column falls under the first, which is the reading order the
   markup already has. */
.hero-say { min-width: 0; }
.hero .ledger { grid-column: 1 / -1; }

/* Numbered because they are a sequence — find a place, follow its money, see what is
   missing from it — and the order is the argument. Not a scaffold above every section. */
.hero-do { list-style: none; margin: var(--s2) 0 0; padding: 0; counter-reset: step; }
.hero-do li {
  counter-increment: step; padding: var(--s3) 0 var(--s3) var(--s6);
  border-top: 1px solid var(--edge-soft); position: relative;
}
.hero-do li:first-child { border-top: 0; padding-top: 0; }
.hero-do li::before {
  content: counter(step); position: absolute; left: 0; top: var(--s3);
  font-size: var(--t-2xs); font-weight: 650; color: var(--sky);
  width: 1.55rem; height: 1.55rem; border-radius: 50%;
  background: var(--sky-wash); display: grid; place-items: center;
}
.hero-do li:first-child::before { top: 0; }
.hero-do a { font-size: var(--t-md); font-weight: 620; }
.hero-do p { margin: 3px 0 0; font-size: var(--t-xs); color: var(--ink-soft); line-height: 1.5; }

@media (max-width: 1100px) {
  .hero { grid-template-columns: minmax(0, 1fr); }
  .hero-do { margin-top: 0; }
}

/* ======================================================= tables on a phone == */
/* Every data table here is a `.tw` wrapper round a `table.d`, and on a 375px screen most
   were two or three times too wide — the areas table 838px, the list of sources 937px — so
   they scrolled sideways and lost the reader's place. Two treatments, because there are two
   kinds of table:

     records      projects, sources, offices: stacked into a card per row. tables.js copies
                  each column's heading onto its cells as data-label; nothing is stacked
                  until it has, so a table without labels is never turned into bare cards.
     comparisons  marked `compare` in the template — the 25 areas, the councils' finances:
                  kept as a table, because the point is reading down a column. The name
                  column is pinned, and a shadow at the right edge says there is more.

   At the end of the file on purpose: rules for .sat-pair and the cell classes below sit
   later in the sheet than the table section, and a phone rule placed earlier would lose to
   them on source order. */
.cell-note { font-size: var(--t-xs); color: var(--ink-faint); }
.cell-fine { font-size: var(--t-xs); }

@media (max-width: 640px) {
  table.d.stackable thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  table.d.stackable, table.d.stackable tbody, table.d.stackable tr,
  table.d.stackable td { display: block; }
  table.d.stackable tbody tr {
    padding: var(--s3) var(--s4); border-top: 1px solid var(--edge-soft);
  }
  table.d.stackable tbody tr:first-child { border-top: 0; }
  table.d.stackable tbody td {
    padding: 2px 0; border-top: 0; text-align: left; font-size: var(--t-sm);
  }
  /* The first cell names the record, so it is the card's title and needs no label. */
  table.d.stackable tbody td:first-child { font-weight: 600; padding-bottom: var(--s1); }
  /* Inline, before the value, rather than a grid of label and value: cells mix text, links
     and badges, and a grid would split each of those into a separate item. */
  table.d.stackable tbody td[data-label]:not(:first-child)::before {
    content: attr(data-label) ": ";
    color: var(--ink-faint); font-family: var(--sans); font-weight: 500;
    font-size: var(--t-xs);
  }
  table.d.stackable td.is-empty { display: none; }
  /* Short values share a line under the title rather than taking one each: "LGA: Burutu ·
     Days late: 412 · Value: ₦1.2m" is one glance, where a line apiece made every card in the
     list of late projects 180px tall and every source on /data/ 214px. Figures are marked
     `.n` in the templates; tables.js marks any other value under twenty characters. Long
     text keeps a line to itself. */
  table.d.stackable tbody td.n,
  table.d.stackable tbody td.is-short:not(:first-child) {
    display: inline-block; margin-right: var(--s4); vertical-align: baseline;
  }

  table.d.compare th:first-child, table.d.compare td:first-child {
    position: sticky; left: 0; z-index: 1; background: var(--paper);
    box-shadow: inset -1px 0 0 var(--edge-soft);
  }
  table.d.compare thead th:first-child { z-index: 2; background: var(--stone); }
  /* The shadow lives in the wrapper's background and a matching cover scrolls with the
     table: while there is more to the right, the shadow shows; at the end the cover sits
     on it. No script, and nothing to keep in step with the scroll position. */
  .tw:has(> table.d.compare) {
    background:
      linear-gradient(to left, var(--paper) 30%, transparent) right / 28px 100% no-repeat local,
      radial-gradient(farthest-side at 100% 50%, oklch(0 0 0 / 0.16), transparent)
        right / 12px 100% no-repeat scroll,
      var(--paper);
  }

  /* Before and after, side by side. Stacked full width they were 309px each — 94 of them
     made /late/ 72 screens long — and a before above an after is two pictures, where a
     before beside an after is a comparison. Equal width either way. */
  .sat-pair { grid-template-columns: 1fr 1fr; gap: var(--s2); }
}

/* ================================================ the councils pages ====== */
/* CouncilWatch: the overview of all 25, one council's page, and two side by side. Every
   class starts cw- so none of it can collide with the rest of the site, and every colour
   is a token, so both themes come for free. */
.cw-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3); margin: var(--s5) 0; }
.cw-tiles.four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 900px) { .cw-tiles.four { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.cw-tile { background: var(--sky-wash); border-radius: var(--r-lg); padding: var(--s4); display: grid; gap: 4px; align-content: start; }
.cw-tile b { font-size: var(--t-xl); font-weight: 700; font-variant-numeric: tabular-nums lining-nums; color: var(--ink); }
.cw-tile b.warn { color: var(--caution); }
.cw-tile span:not(.ev) { font-size: var(--t-xs); color: var(--ink-soft); line-height: 1.4; }
/* Two tiles to a row on a phone leave about 130px inside each, narrower than "Read from a
   document" on one line, which ran out past the tile's rounded edge. Inside a tile the
   badge may wrap; the radius is a pill on one line and a rounded box on two. */
.cw-tile .ev { justify-self: start; white-space: normal; line-height: 1.35; border-radius: var(--r-md); }

.cw-find { display: flex; gap: var(--s2); margin: var(--s4) 0; max-width: 32rem; }
.cw-find select, .cw-pick select { flex: 1; min-width: 0; padding: 10px 12px; font-size: 1rem;
  border: 1px solid var(--edge); border-radius: var(--r-md); background: var(--paper); color: var(--ink); }

/* The ranked chart. One series and one magnitude, so one hue; the value is printed at the
   end of each bar so nothing depends on reading a length. */
.cw-rank { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.cw-row { display: grid; grid-template-columns: minmax(6.5em, 9em) 1fr 3.5em; align-items: center;
  gap: var(--s2); padding: 5px 6px; border-radius: var(--r-sm); color: var(--ink); text-decoration: none; }
.cw-row:hover, .cw-row:focus-visible { background: var(--sky-wash); text-decoration: none; }
.cw-name { font-size: var(--t-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cw-track { height: 10px; background: var(--edge-soft); border-radius: 0 4px 4px 0; position: relative; }
.cw-fill { position: absolute; inset: 0 auto 0 0; background: var(--series-1); border-radius: 0 4px 4px 0; }
.cw-v { font-size: var(--t-sm); font-weight: 600; text-align: right; font-variant-numeric: tabular-nums lining-nums; }
.cw-table { margin: var(--s3) 0 var(--s5); }
.cw-table summary { cursor: pointer; font-weight: 600; color: var(--sky); font-size: var(--t-sm); }

.cw-sparks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2);
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); }
.cw-spark { display: grid; gap: 2px; padding: 8px; border: 1px solid var(--edge-soft); border-radius: var(--r-md);
  color: var(--ink); text-decoration: none; }
.cw-spark:hover { border-color: var(--sky-line); text-decoration: none; }
.cw-spark svg { width: 100%; height: 28px; }
.cw-ch { font-size: var(--t-2xs); color: var(--ink-soft); font-variant-numeric: tabular-nums lining-nums; }
.cw-ch.down { color: var(--caution); font-weight: 600; }

/* One council. */
.cw-back { font-weight: 600; font-size: var(--t-sm); }
.cw-who { display: grid; grid-template-columns: 96px 1fr; gap: var(--s4); align-items: center; margin: var(--s4) 0; }
.cw-who h1 { margin: 0; font-size: var(--t-2xl); }
.cw-portrait { width: 96px; height: 128px; object-fit: cover; border-radius: var(--r-md); background: var(--sky-wash); }
.cw-portrait.none { display: grid; place-items: center; font-size: var(--t-2xl); font-weight: 700;
  color: var(--sky); border: 1.5px dashed var(--sky-line); }
.cw-chair { margin: 4px 0 0; font-size: var(--t-md); font-weight: 600; }
.cw-chair.missing { color: var(--caution); }
.cw-chips { list-style: none; padding: 0; margin: 0 0 var(--s4); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.cw-chips .chip { font-size: var(--t-2xs); font-weight: 600; border-radius: 999px; padding: 3px 10px;
  background: var(--sky-wash); color: var(--sky); }
.cw-chips .chip.warn { background: var(--t6-wash); color: var(--caution); }
.cw-chart { max-width: 36rem; }
.yb { display: block; width: 100%; height: auto; overflow: visible; }
.yb .s1 { fill: var(--series-1); }
.yb .s2 { fill: var(--series-2); }
.yb .yb-base { stroke: var(--edge); }
.yb text { font-family: var(--sans); font-size: 12px; fill: var(--ink-soft); }
.yb .yb-val { fill: var(--ink); font-weight: 600; }
.cw-links { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s2); margin: var(--s5) 0; }
.cw-links a { border: 1px solid var(--edge); border-radius: var(--r-md); padding: var(--s3); color: var(--ink);
  font-size: var(--t-xs); text-decoration: none; }
.cw-links a b { display: block; color: var(--sky); font-size: var(--t-md); }
.cw-gaps { color: var(--ink-soft); font-size: var(--t-sm); }

/* Two side by side. Each select is outlined in its series colour, so the chart below
   needs no second key to say which bar is which. */
.cw-pick { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: var(--s2);
  align-items: center; margin: var(--s4) 0; }
.cw-pick select.s1 { border: 2px solid var(--series-1); }
.cw-pick select.s2 { border: 2px solid var(--series-2); }
.cw-pick .btn { grid-column: 1 / -1; justify-self: start; }
/* The names head their columns in ink. The series tokens are for marks and never for
   text, as their definition above says, so each column's colour is a rule under its
   name, the same key as the select above it. */
.cw-cmp th.s1 { color: var(--ink); box-shadow: inset 0 -3px 0 var(--series-1); }
.cw-cmp th.s2 { color: var(--ink); box-shadow: inset 0 -3px 0 var(--series-2); }
/* Table headings stay on one line everywhere else; here two long names ("Warri South West"
   beside "Oshimili South") pushed the second council out of sight on a phone. */
.cw-cmp thead th.n { white-space: normal; }
/* At 1280px the table ran the whole width of the page, about 1170px, and each label sat far
   from both its figures. Its box is held to the width of the chart below it. A phone is
   narrower than this already, so nothing changes there. */
.tw:has(> table.cw-cmp) { max-width: 36rem; }
/* The rows come in two groups — what the report prints, what we work out from it — each
   headed by its badge. A badge on every row made the label column too wide for two
   councils to sit side by side on a phone. The heading spans the row, so the pinned name
   column's edge would run down the table's right side instead of between the columns;
   :first-child matches the phone rule's weight, and this later rule wins on order. */
.cw-cmp tr.cw-tier th:first-child { padding-block: var(--s3) 0; box-shadow: none; }
.cw-cmp tbody tr.cw-tier:hover { background: none; }
.cw-legend { list-style: none; padding: 0; margin: 0 0 var(--s2); display: flex; gap: var(--s4);
  font-size: var(--t-xs); color: var(--ink-soft); }
.cw-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; }
.cw-legend i.s1 { background: var(--series-1); }
.cw-legend i.s2 { background: var(--series-2); }

#cw-tip { position: fixed; z-index: var(--z-toast); pointer-events: none; max-width: 16rem; padding: 6px 9px;
  border-radius: var(--r-sm); background: var(--ink); color: var(--paper); font-size: var(--t-xs); }
#cw-tip[hidden] { display: none; }

@media (max-width: 480px) {
  .cw-links { grid-template-columns: 1fr; }
  .cw-who { grid-template-columns: 76px 1fr; }
  .cw-portrait { width: 76px; height: 100px; }
}
