/* @ds-tokens:start */
/* GENERATED by platform/design/build.mjs — edit tokens.json, not this block */
:root {
  /* color */
  --ds-color-border: #e2e8f0;
  --ds-color-border-accent-muted: #cfe2f3;
  --ds-color-border-button: rgba(0, 34, 68, 0.2);
  --ds-color-border-card: #e3e7ee;
  --ds-color-border-control: #cbd5e1;
  --ds-color-border-divider: #eeeeee;
  --ds-color-border-line: #e6eaf0;
  --ds-color-border-strong: #cbd5e0;
  --ds-color-border-subtle: #e0e0e0;
  --ds-color-danger: #991b1b;
  --ds-color-hero-scrim-1: rgba(0, 17, 34, 0.86);
  --ds-color-hero-scrim-2: rgba(0, 17, 34, 0.77);
  --ds-color-hero-scrim-3: rgba(0, 17, 34, 0.48);
  --ds-color-hero-scrim-4: rgba(0, 17, 34, 0.30);
  --ds-color-hero-scrim-5: rgba(0, 17, 34, 0.26);
  --ds-color-legacy-page-accent: #1d4ed8;
  --ds-color-legacy-page-ink: #1f2a3a;
  --ds-color-legacy-page-line: #e2e6ee;
  --ds-color-legacy-page-muted: #5b6678;
  --ds-color-legacy-page-navy: #0b1f3a;
  --ds-color-logo-glow: rgba(120, 150, 200, 0.28);
  --ds-color-navy: #002244;
  --ds-color-navy-black: #000f1e;
  --ds-color-navy-deep: #001a33;
  --ds-color-page-bg: #eef1f6;
  --ds-color-shadow-card: rgba(0, 34, 68, 0.08);
  --ds-color-shadow-faint: rgba(16, 42, 67, 0.04);
  --ds-color-shadow-focus: rgba(0, 34, 68, 0.12);
  --ds-color-shadow-hero-text: rgba(0, 0, 0, 0.4);
  --ds-color-shadow-medium: rgba(0, 0, 0, 0.2);
  --ds-color-shadow-soft: rgba(0, 0, 0, 0.08);
  --ds-color-shadow-strong: rgba(0, 0, 0, 0.3);
  --ds-color-surface: #ffffff;
  --ds-color-surface-accent-tint: #eef4fb;
  --ds-color-surface-alt: #f1f5f9;
  --ds-color-surface-border: rgba(0, 34, 68, 0.12);
  --ds-color-surface-error: #fee2e2;
  --ds-color-surface-hero-scrim-deep: rgba(0, 15, 30, 0.9);
  --ds-color-surface-hero-scrim-light: rgba(0, 15, 30, 0.15);
  --ds-color-surface-hero-scrim-mid: rgba(0, 15, 30, 0.35);
  --ds-color-surface-hero-scrim-strong: rgba(0, 15, 30, 0.55);
  --ds-color-surface-modal-scrim: rgba(0, 0, 0, 0.5);
  --ds-color-surface-note: #f4f7fc;
  --ds-color-surface-on-navy: rgba(255, 255, 255, 0.15);
  --ds-color-surface-on-navy-active: rgba(255, 255, 255, 0.25);
  --ds-color-surface-on-navy-badge: rgba(255, 255, 255, 0.16);
  --ds-color-surface-on-navy-hover: rgba(255, 255, 255, 0.1);
  --ds-color-surface-on-navy-icon: rgba(255, 255, 255, 0.18);
  --ds-color-surface-placeholder: #fafafa;
  --ds-color-surface-subtle: #f8fafc;
  --ds-color-surface-success: #d1fae5;
  --ds-color-text-accent-link: #0a6bb0;
  --ds-color-text-badge-accent: #0369a1;
  --ds-color-text-body: #475569;
  --ds-color-text-faint: #94a3b8;
  --ds-color-text-grey: #717171;
  --ds-color-text-grey-dark: #666666;
  --ds-color-text-heading: #1a2433;
  --ds-color-text-ink: #1a1a1a;
  --ds-color-text-muted: #627288;
  --ds-color-text-muted-cool: #5a6573;
  --ds-color-text-on-navy: rgba(255, 255, 255, 0.85);
  --ds-color-text-on-navy-body: rgba(255, 255, 255, 0.82);
  --ds-color-text-on-navy-dim: rgba(255, 255, 255, 0.4);
  --ds-color-text-on-navy-faint: rgba(255, 255, 255, 0.45);
  --ds-color-text-on-navy-hover: rgba(255, 255, 255, 0.8);
  --ds-color-text-on-navy-muted: rgba(255, 255, 255, 0.7);
  --ds-color-text-on-navy-soft: rgba(255, 255, 255, 0.75);
  --ds-color-text-on-navy-strong: rgba(255, 255, 255, 0.9);
  --ds-color-text-secondary: #333333;
  --ds-color-text-strong: #334155;
  --ds-color-text-success: #065f46;
  --ds-color-text-tertiary: #555555;
  --ds-color-viz-area: rgba(42, 120, 214, 0.1);
  --ds-color-wb-blue: #003087;
  --ds-color-wb-cyan: #009fda;
  --ds-color-wb-cyan-text: #006d94;
  --ds-color-white: #ffffff;
  /* font */
  --ds-font-display: 'Inter', sans-serif;
  --ds-font-family-1: Arial, Helvetica, sans-serif;
  --ds-font-sans: 'Source Sans 3', 'Helvetica Neue', Arial, sans-serif;
  --ds-font-sans-2: 'Source Sans 3', sans-serif;
  /* text */
  --ds-text-10: 10px;
  --ds-text-11: 11px;
  --ds-text-12: 12px;
  --ds-text-13: 13px;
  --ds-text-14: 14px;
  --ds-text-15: 15px;
  --ds-text-16: 16px;
  --ds-text-17: 17px;
  --ds-text-18: 18px;
  --ds-text-19: 19px;
  --ds-text-20: 20px;
  --ds-text-22: 22px;
  --ds-text-23: 23px;
  --ds-text-24: 24px;
  --ds-text-26: 26px;
  --ds-text-28: 28px;
  --ds-text-29: 29px;
  --ds-text-30: 30px;
  --ds-text-32: 32px;
  --ds-text-34: 34px;
  --ds-text-_82rem: .82rem;
  --ds-text-1_05rem: 1.05rem;
  --ds-text-1_7rem: 1.7rem;
  --ds-text-11_5: 11.5px;
  --ds-text-12_5: 12.5px;
  --ds-text-13_5: 13.5px;
  --ds-text-14_5: 14.5px;
  --ds-text-15_5: 15.5px;
  /* radius */
  --ds-radius-2: 2px;
  --ds-radius-3: 3px;
  --ds-radius-4: 4px;
  --ds-radius-5: 5px;
  --ds-radius-6: 6px;
  --ds-radius-8: 8px;
  --ds-radius-9: 9px;
  --ds-radius-10: 10px;
  --ds-radius-12: 12px;
  --ds-radius-14: 14px;
  --ds-radius-20: 20px;
  --ds-radius-999: 999px;
  /* size */
  --ds-size-container: 1300px;
  --ds-size-gutter: 24px;
  --ds-size-prose-measure: 720px;
  /* shadow */
  --ds-shadow-button-hover: 0 6px 16px rgba(0,0,0,0.2);
  --ds-shadow-card: 0 1px 3px rgba(0,0,0,0.05);
  --ds-shadow-card-expanded: 0 8px 24px rgba(0,34,68,0.12);
  --ds-shadow-card-hover: 0 4px 16px rgba(0,34,68,0.08);
  --ds-shadow-card-lift: 0 1px 2px rgba(0, 34, 68, 0.06), 0 4px 12px rgba(0, 34, 68, 0.05);
  --ds-shadow-control-hover: 0 2px 4px rgba(0,0,0,0.05);
  --ds-shadow-cover: 0 2px 8px rgba(0,0,0,0.15);
  --ds-shadow-dropdown: 0 4px 12px rgba(0,0,0,0.15);
  --ds-shadow-header: 0 2px 8px rgba(0,34,68,.18);
  --ds-shadow-hero-eyebrow: 0 1px 2px rgba(0,0,0,.45);
  --ds-shadow-hero-title: 0 1px 4px rgba(0,0,0,.5);
  --ds-shadow-modal: 0 20px 50px rgba(0,0,0,0.3);
  --ds-shadow-panel: 0 4px 12px rgba(0,0,0,0.08);
  /* viz */
  --ds-viz-area: rgba(43, 108, 176, 0.1);
  --ds-viz-cat-1: #2b6cb0;
  --ds-viz-cat-2: #38a169;
  --ds-viz-cat-3: #c05621;
  --ds-viz-cat-4: #6b46c1;
  --ds-viz-cat-5: #975a16;
  --ds-viz-cat-6: #805ad5;
  --ds-viz-cat-7: #b83280;
  --ds-viz-cat-8: #3182ce;
  --ds-viz-grid: rgba(0, 0, 0, 0.08);
  --ds-viz-grid-svg: #e6ebf1;
  --ds-viz-line: #2b6cb0;
  --ds-viz-neg: #e34948;
  --ds-viz-pos: #008300;
  --ds-viz-sector-agriculture: #3f6212;
  --ds-viz-sector-context: #e11d48;
  --ds-viz-sector-debt: #92400e;
  --ds-viz-sector-education: #ea580c;
  --ds-viz-sector-education-title: #c2410c;
  --ds-viz-sector-external: #1d4ed8;
  --ds-viz-sector-fiscal: #4338ca;
  --ds-viz-sector-governance: #0891b2;
  --ds-viz-sector-governance-title: #0e7490;
  --ds-viz-sector-health: #dc2626;
  --ds-viz-sector-labour: #db2777;
  --ds-viz-sector-labour-title: #9d174d;
  --ds-viz-sector-monetary: #15803d;
  --ds-viz-sector-poverty: #86198f;
  --ds-viz-sector-prices: #b45309;
  --ds-viz-sector-real: #0d9488;
  --ds-viz-sector-real-title: #0f766e;
  --ds-viz-sector-socio: #5b21b6;
}
/* @ds-tokens:end */
/* @ds-header:start */
/* GENERATED by platform/design/build.mjs — edit tokens.json, not this block */
/*
 * The site header — one definition.
 *
 * It was defined in 12 sources with 17 of its 18 shared selectors drifting;
 * `.language-toggle` and `.lang-btn` alone had five distinct rule bodies across six files.
 * This is the canonical version, injected into every source by build.mjs between
 * @ds-header markers, exactly as the token block is.
 *
 * Where copies disagreed the majority variant wins, EXCEPT where a rule was simply wrong:
 *
 *   - Nav links and language buttons were 21px and 22px tall, below the 24px WCAG 2.5.8
 *     minimum. They now carry a min-height that gives a 32px / 28px hit area. Nothing
 *     moves: the text stays vertically centred in a 48px bar, only the target grows.
 *   - Several copies set `color: white` literally where a token exists. Tokens throughout,
 *     so a change to the navy or the muted white reaches the header too.
 *
 * Responsive behaviour stays in each source's own media queries, which come later in the
 * file and so still override this base. Only the desktop definition is shared, because
 * that is the part that was drifting.
 */

.header {
  background: var(--ds-color-navy);
  padding: 0;
  position: sticky;
  top: 0;
  z-index: 100;
  box-shadow: var(--ds-shadow-header);
}

/* The header and the page content read from ONE container token so they cannot drift apart.
   They were 1200 and 1152, which put the logo 24px left of the content it sits above. */
.header-content {
  /* border-box explicitly, not inherited from a page reset. legal.html has no global
     `* { box-sizing: border-box }`, so its header measured 1348 against everyone else's
     1300 — the gutter was being ADDED to the cap instead of sitting inside it, and the
     logo landed 24px left of every other page. A shared component cannot assume a reset
     that only most pages happen to declare. */
  box-sizing: border-box;
  max-width: var(--ds-size-container);
  margin: 0 auto;
  padding: 0 var(--ds-size-gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 48px;
}

.header-left { display: flex; align-items: center; gap: 8px; }
.header-right { display: flex; align-items: center; gap: 24px; }

.header-logo {
  font-family: var(--ds-font-display);
  font-size: var(--ds-text-17);
  font-weight: 800;
  color: var(--ds-color-white);
  letter-spacing: -0.3px;
  text-decoration: none;
  transition: opacity 0.15s;
  /* 24px minimum target (WCAG 2.5.8); the mark and text stay where they were. */
  display: inline-flex;
  align-items: center;
  min-height: 32px;
}
.header-logo:hover { opacity: 0.85; }

/* The other two ways a page puts a link in the bar. sector.html marks its breadcrumbs
   .crumb and 404.html wraps its title link in .site-title; neither used .header-logo, so
   both stayed 15px tall. Sized here rather than asking every page to adopt one class,
   because the class is what drifted in the first place. */
.crumb,
.site-title a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

.header-mark {
  width: 17.33px;
  height: 20px;
  flex: 0 0 auto;
  display: block;
  margin-right: 4px;
  filter: drop-shadow(0 0 2.5px var(--ds-color-logo-glow));
}

.header-nav { display: flex; gap: 22px; }

.header-nav a {
  font-family: var(--ds-font-display);
  color: var(--ds-color-text-on-navy-muted);
  text-decoration: none;
  font-size: var(--ds-text-13);
  font-weight: 600;
  transition: color 0.15s;
  /* Was 21px tall. A 32px hit area inside the 48px bar, text unmoved. */
  display: inline-flex;
  align-items: center;
  min-height: 32px;
}
.header-nav a:hover { color: var(--ds-color-text-on-navy); }
.header-nav a.active { color: var(--ds-color-white); }

.hd-sep {
  opacity: 0.3;
  font-weight: 300;
  margin: 0 4px;
  color: var(--ds-color-white);
}

.hd-section {
  font-family: var(--ds-font-display);
  font-weight: 400;
  font-size: var(--ds-text-13);
  color: var(--ds-color-text-on-navy-muted);
  letter-spacing: 0.01em;
}

.language-toggle {
  display: flex;
  gap: 2px;
  background: var(--ds-color-surface-on-navy);
  padding: 2px;
  border-radius: var(--ds-radius-5);
  border: none;
}

.lang-btn {
  padding: 4px 10px;
  border: none;
  background: transparent;
  color: var(--ds-color-text-on-navy-muted);
  font-size: var(--ds-text-12);
  font-weight: 700;
  cursor: pointer;
  border-radius: var(--ds-radius-3);
  transition: all 0.15s;
  font-family: var(--ds-font-display);
  /* Was 22px tall. */
  min-height: 28px;
}
.lang-btn:hover { background: var(--ds-color-surface-on-navy-hover); color: var(--ds-color-text-on-navy); }
.lang-btn.active { background: var(--ds-color-surface-on-navy-active); color: var(--ds-color-white); }

.tagline {
  font-size: var(--ds-text-12);
  color: var(--ds-color-text-on-navy-faint);
  font-weight: 400;
  margin: 0;
  display: none;
}
.tagline a {
  color: var(--ds-color-text-on-navy-faint);
  text-decoration: none;
  font-weight: 400;
  transition: all 0.15s;
}
.tagline a:hover { color: var(--ds-color-text-on-navy-hover); text-decoration: underline; }

@media (max-width: 768px) {
  /* The bar had no mobile rules in the component, so at 390px its contents measured about
     487px against 358px of room and the language toggle ran off the right edge. Four pages
     scrolled sideways; the others only LOOKED fine because they carry `overflow-x: hidden`.
     
     These rules are not new. They are the pattern the home page and the data portal
     already used in their own CSS — 44px bar, 16px gutter, nav hidden — lifted into the
     component and deleted from both pages, so all 33 targets now behave the same way
     instead of three ways. Adopting the site's existing decision keeps this a
     consolidation rather than a redesign.
     
     Hiding the nav does mean a phone reader navigates by the logo. That was already true
     on the home page and every data page; it is now true consistently. Worth revisiting as
     its own change, with something to replace it. */
  .header-content {
    padding: 0 16px;
    height: 44px;
  }
  .header-logo { font-size: var(--ds-text-15); }
  .header-nav { display: none; }
}
/* @ds-header:end */
/* @ds-hero:start */
/* GENERATED by platform/design/build.mjs — edit tokens.json, not this block */
/*
 * The hero band — one definition.
 *
 * There were four implementations of the same thing: .hero (home), .data-hero (data),
 * .doc-hero (documents and sector pages) and .collection-hero (series, budget), spread
 * over 72 selectors. Structurally they were identical — a relative band on a navy-black
 * ground, an absolutely-positioned cover image, a gradient overlay, and a text block
 * capped at 1152px. They differed only in band height, padding and headline size.
 *
 * The four legacy names are kept as ALIASES rather than renamed, so no markup changes on
 * 35 pages and 17 generated ones. One rule set, four ways in. Sizing is the part that
 * legitimately varies, and it varies by name: the home page runs taller, series and the
 * legal pages run tighter.
 *
 * Anything genuinely page-specific — the home search box, the data badges, the collection
 * stats and icons — stays in its own source. This component owns the band, not its
 * contents.
 */

/* --- the band ------------------------------------------------------------------- */
/* Structure only. The GROUND is deliberately not set here: .hero and .page-hero mean
   different things on different pages — a dark photographic band on the home and connect
   pages, but a LIGHT band with navy text on the news page. Imposing a dark ground on the
   name made that page's own text invisible at 1.21:1. */
.hero,
.page-hero,
.data-hero,
.doc-hero,
.collection-hero {
  position: relative;
  overflow: hidden;
  /* The band's height comes from min-height, and in normal block flow ALL of the slack
     falls below the text: 44px under the documents headline on a phone, 137px under the
     series one on a desktop. Centring shares it, so the text reads as sitting in the band
     rather than clinging to its top edge. The image and the overlay are absolutely
     positioned and stay out of this; `<picture>` is a zero-height item in the column. */
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* A hero that holds a search dropdown must NOT clip it. The homepage suggestions sit at
   top: calc(100% + 8px) — below the band — so `overflow: hidden` swallowed them whole and
   the results looked as though they had gone behind the page. Dropping the clip is safe:
   the image and the overlay are both `position: absolute; inset: 0` with object-fit:cover,
   so neither can spill, and the band was not clipping anything else.
   This lives in the component because the injected block is generated: the same fix made
   by hand in index.html was erased by the very next `node build.mjs`. */
.hero:has(.sugg),
.page-hero:has(.sugg) {
  overflow: visible;
}

/* A hero that carries a photograph gets the dark ground and light text, because that is
   what the overlay is for. One that does not keeps the page's own colours. */
.hero:has(img),
.hero:has(picture),
.page-hero:has(img),
.page-hero:has(picture),
.data-hero,
.doc-hero,
.collection-hero {
  background: var(--ds-color-navy-black);
  min-height: 206px;
}

/* --- the image and its overlay ---------------------------------------------------- */
.hero-img,
.data-hero-img,
.doc-hero-img,
.collection-hero-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero-overlay,
.hero-ov,
.data-hero-overlay,
.doc-hero-overlay,
.collection-hero-overlay {
  position: absolute;
  inset: 0;
  /* Dark at the top and bottom, lighter through the middle, so a white headline holds its
     contrast wherever the photograph happens to be bright. */
  background: linear-gradient(
    180deg,
    var(--ds-color-surface-hero-scrim-mid) 0%,
    var(--ds-color-surface-hero-scrim-light) 30%,
    var(--ds-color-surface-hero-scrim-light) 45%,
    var(--ds-color-surface-hero-scrim-strong) 70%,
    var(--ds-color-surface-hero-scrim-deep) 100%
  );
}

/* --- the text block --------------------------------------------------------------- */
.hero-text,
.hero-in,
.data-hero-text,
.doc-hero-text,
.collection-hero-text,
/* series and budget name their inner wrapper -content, not -text. Leaving it out
   collapsed those bands to 48px, because the padding that gave them height lived in the
   page rule this component replaced. */
.collection-hero-content,
/* connect names its inner wrapper `.wrap` — the site-wide container class — so the band was
   aliased here but its wrapper never was, and it kept the exact bug the `width: 100%` below
   exists to fix: 578px wide at x=431 instead of 1152px at x=144, which reads as a centred
   hero on an otherwise left-aligned page. Scoped to `.page-hero > .wrap` deliberately:
   `.wrap` is used all over the site for plain content, and matching it unscoped would put
   48px of hero padding on every one of them. */
.page-hero > .wrap {
  position: relative;
  z-index: 2;
  box-sizing: border-box;
  max-width: var(--ds-size-container);
  margin: 0 auto;
  /* Explicit, because the band is now a column flex container. `margin: 0 auto` on a flex
     item absorbs the free space instead of stretching, so without this the wrapper shrank
     to its content and the headline centred itself — the documents hero read as a centred
     title rather than a left-aligned one. */
  width: 100%;
  padding: 48px 24px 44px;
}

.hero:has(img) .hero-eyebrow,
.hero:has(picture) .hero-eyebrow,
.page-hero:has(img) .hero-eyebrow,
.data-hero-eyebrow,
.doc-hero-eyebrow,
.collection-hero-eyebrow {
  font-family: var(--ds-font-display);
  font-size: var(--ds-text-12);
  text-transform: uppercase;
  letter-spacing: 3px;
  font-weight: 700;
  color: var(--ds-color-text-on-navy-soft);
  margin-bottom: 12px;
  text-shadow: var(--ds-shadow-hero-eyebrow);
}

.hero:has(img) .hero-headline,
.hero:has(picture) .hero-headline,
.page-hero:has(img) h1,
.page-hero:has(picture) h1,
.data-hero h1,
.doc-hero-h1,
.collection-hero h1 {
  font-family: var(--ds-font-display);
  font-size: var(--ds-text-32);
  font-weight: 900;
  line-height: 1.15;
  letter-spacing: -0.5px;
  color: var(--ds-color-white);
  text-shadow: var(--ds-shadow-hero-title);
  margin: 0;
}

.hero:has(img) .hero-sub,
.hero:has(picture) .hero-sub,
.page-hero:has(img) .hero-sub,
.data-hero-sub,
.doc-hero-sub,
.collection-hero-desc {
  /* The family is set here on purpose. Without it the sub inherits whatever the page
     happens to define, which on the home page was Source Sans 3 and on privacy and terms
     was the bare system stack — the same band rendering in two different typefaces. */
  font-family: var(--ds-font-sans);
  font-size: var(--ds-text-16);
  color: var(--ds-color-text-on-navy);
  margin: 10px 0 0;
  max-width: 68ch;
  line-height: 1.5;
}

/* A search field in a hero is a tap target: both the home page's and the search page's
   rendered 22px, two short of the WCAG 2.5.8 minimum. min-height only ever grows them.
   Matched by exclusion rather than by [type=text]: the search page's input carries no
   type attribute at all, so an attribute selector missed it. */
.hero input:not([type="checkbox"]):not([type="radio"]),
.page-hero input:not([type="checkbox"]):not([type="radio"]) {
  min-height: 24px;
}

/* --- sizing, the part that legitimately differs ----------------------------------- */

/* Home: the tallest band, and the only one with a display headline. */
.hero .hero-text,
.hero .hero-in {
  padding: 44px 24px 42px;
}
.hero .hero-headline {
  font-size: var(--ds-text-34);
  letter-spacing: -1px;
  line-height: 1.13;
}

/* Series, budget and the legal pages carry more inside the band (a description, stats),
   so they keep the tighter PADDING. They no longer keep tighter type: a 26px headline on
   privacy and terms next to a 32px one everywhere else is the sort of drift a shared
   component exists to prevent, and the reason once given for it — that the legal pages
   have no photograph — stopped being true when they got the same topographic band as
   every other page. One headline size, everywhere. */
.collection-hero .collection-hero-text,
.collection-hero .collection-hero-content,
.doc-hero--sm .doc-hero-text {
  padding: 32px 24px 28px;
}

@media (max-width: 768px) {
  /* The band sizes to its text on a phone. A fixed 206px was a desktop measurement, and
     on a 390px screen it left 29 to 97px of empty topographic image BELOW the text, which
     reads as a layout mistake rather than as breathing room. `auto` lets each hero be as
     tall as it needs; the floor keeps the image legible as a band. The home page is
     unaffected — its search field already makes it the tallest hero. */
  .hero:has(img),
  .hero:has(picture),
  .page-hero:has(img),
  .page-hero:has(picture),
  .data-hero,
  .doc-hero,
  .collection-hero {
    min-height: 132px;
    /* All padding belongs to the content wrapper, not to the band. series and budget were
       padding BOTH: 24px each side, so their headline started at 40px while every other
       page started at 16px, and 32px top plus 28px bottom, which is exactly the 60px of
       empty image that sat under their text. */
    padding: 0;
  }

  /* Every selector the desktop sizing uses has to be repeated at ITS specificity, not at
     the single-class level. `.hero .hero-text`, `.collection-hero .collection-hero-text`
     and `.doc-hero--sm .doc-hero-text` are all two classes, so a one-class rule in here
     lost to them and five pages kept their DESKTOP padding on mobile. That is why the
     headline started at 16px on two pages, 24px on four and 48px on two. */
  .hero-text,
  .hero-in,
  .data-hero-text,
  .doc-hero-text,
  .collection-hero-text,
  .collection-hero-content,
  .hero .hero-text,
  .hero .hero-in,
  .collection-hero .collection-hero-text,
  .collection-hero .collection-hero-content,
  .doc-hero--sm .doc-hero-text,
  /* Two classes, like the rule above it, so it has to be repeated here for the same reason:
     a single-class rule would lose to the desktop `.page-hero > .wrap` and connect would keep
     its 48px desktop padding on a phone. */
  .page-hero > .wrap {
    /* 16px matches the mobile gutter the header uses below, so the eyebrow, the headline
       and the logo above them share one left edge. */
    padding: 22px 16px 20px;
  }
  .hero .hero-headline,
  .doc-hero-h1,
  .data-hero h1 {
    font-size: var(--ds-text-26);
  }
  .hero-eyebrow,
  .data-hero-eyebrow,
  .doc-hero-eyebrow {
    font-size: var(--ds-text-11);
    letter-spacing: 2px;
  }
}

/* --- the slim band ------------------------------------------------------------------
 *
 * Every hero except the homepage is a shallow strip: breadcrumb or eyebrow, H1, and a
 * count subtitle where the page has live counts. It replaces a ~250px band that returned
 * nothing but atmosphere.
 *
 * SELECTED BY :has(picture), not by a modifier class. The homepage is the only page whose
 * hero still contains a <picture>, so the distinction is a fact about the markup rather
 * than a class that has to be added to 19 files and kept in sync. Adding a photographic
 * hero back to any page restores the tall band automatically.
 *
 * The topographic art stays, as a 222 KB webp band, drawn as a BACKGROUND rather than an
 * <img>. The 5160x1200 / 8.7 MB PNG was fetched on every page, several with
 * fetchpriority="high", to be squeezed into a strip.
 *
 * Two rules the band cost real rework to learn:
 *  - Never resample one axis alone. A 2560x240 cut squeezed the horizontal to 49.6% against
 *    an untouched vertical: the mesh collapsed into vertical hatch and the fine grid moired.
 *    The shipped band is (0,105,5160,435) Lanczos'd to 3752x240, both axes together.
 *  - Brightness belongs to the scrim, not to a filter on the artwork. brightness(1.85)
 *    saturate(1.15) washed the near-black ground grey-blue and blew the teal to cyan.
 *
 * `cover`, not a fixed height: the band's aspect is narrower than a wide viewport's strip,
 * and `auto 120px` stops covering past ~1876px, showing navy at the edges.
 */
.hero:not(:has(picture)),
.page-hero:not(:has(picture)),
.data-hero,
.doc-hero,
.collection-hero {
  /* ONE height for every non-home band, so the chrome does not jump between pages.
     min-height, not height: a French or Kreyol title wraps more often than the English and
     must push the band open rather than be clipped. Everything below is tuned so the
     busiest band (documents: eyebrow + title + two count chips) still fits inside it, which
     is what makes the shared number hold instead of being a floor only the sparse pages
     meet. Measured before this: 92 to 164px across thirteen pages. */
  min-height: 132px;
  background-color: var(--ds-color-navy);
  background-image: url('/haiti-topo-band.webp');
  background-position: center center;
  background-size: cover;
  background-repeat: no-repeat;
}

/* The scrim is a PSEUDO-ELEMENT on the band, not a rule on the overlay <div>.
   Only some pages carry an overlay element: /news/ has none, so a rule written against
   `.hero-overlay` matched nothing there and the band shipped with no scrim at all, white
   text straight onto bright mesh. A pseudo-element cannot be missing.
   Heaviest under the words, clearing to the right so the terrain stays visible. The
   right-hand stop is the whole brightness control: 0 glares, .36 goes muted, .26 is it. */
.hero:not(:has(picture))::after,
.page-hero:not(:has(picture))::after,
.data-hero::after,
.doc-hero::after,
.collection-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    var(--ds-color-hero-scrim-1) 0%,
    var(--ds-color-hero-scrim-2) 34%,
    var(--ds-color-hero-scrim-3) 56%,
    var(--ds-color-hero-scrim-4) 76%,
    var(--ds-color-hero-scrim-5) 100%
  );
}

/* The legacy overlay div, where a page has one, would paint a SECOND scrim on top of the
   pseudo-element and darken those pages relative to the ones without it. */
.hero:not(:has(picture)) .hero-overlay,
.hero:not(:has(picture)) .hero-ov,
.page-hero:not(:has(picture)) .hero-overlay,
.data-hero .data-hero-overlay,
.doc-hero .doc-hero-overlay,
.collection-hero .collection-hero-overlay {
  background: none;
}

/* The text sits above the scrim. Both are positioned, so without this the scrim paints
   last and dims the very headline it exists to protect. */
.hero:not(:has(picture)) .hero-text,
.hero:not(:has(picture)) .hero-in,
.page-hero:not(:has(picture)) > .wrap,
.data-hero .data-hero-text,
.doc-hero .doc-hero-text,
.collection-hero .collection-hero-text,
.collection-hero .collection-hero-content {
  position: relative;
  z-index: 2;
}

/* One padding for every slim band. These selectors are two classes deep because the
   desktop sizing rules above are, and a one-class rule loses to them — the same
   specificity trap that once left five pages on their desktop padding at mobile width. */
.hero:not(:has(picture)) .hero-text,
.hero:not(:has(picture)) .hero-in,
.page-hero:not(:has(picture)) > .wrap,
.data-hero .data-hero-text,
.doc-hero .doc-hero-text,
.doc-hero--sm .doc-hero-text,
.collection-hero .collection-hero-text,
.collection-hero .collection-hero-content {
  padding: 18px var(--ds-size-gutter);
}

/* 92px is a MINIMUM, not a height. A French or Kreyol title wraps to two lines far more
   often than the English one, and the band must grow rather than clip. */
.hero:not(:has(picture)) .hero-headline,
.page-hero:not(:has(picture)) h1,
.data-hero h1,
.doc-hero .doc-hero-h1,
.collection-hero h1 {
  font-size: var(--ds-text-29);
  line-height: 1.15;
  letter-spacing: -0.4px;
}

/* The slim band is always dark, so it owns its text colour. Pages that used to render a
   LIGHT band left their headline colour unset and inherited the page's ink: correct then,
   invisible now. /connect/ shipped a black H1 on navy that way, and /news/ would have too.
   Setting it on the band means a page cannot forget. A page that genuinely needs a
   different colour still overrides at its own specificity. */
.hero:not(:has(picture)),
.page-hero:not(:has(picture)),
.data-hero,
.doc-hero,
.collection-hero {
  color: var(--ds-color-white);
}
.hero:not(:has(picture)) h1,
.page-hero:not(:has(picture)) h1,
.data-hero h1,
.doc-hero h1,
.collection-hero h1,
.hero:not(:has(picture)) .hero-headline,
.doc-hero .doc-hero-h1 {
  color: var(--ds-color-white);
}
.hero:not(:has(picture)) p,
.page-hero:not(:has(picture)) p,
.data-hero p,
.doc-hero p,
.collection-hero p {
  color: var(--ds-color-text-on-navy-body);
}

/* The internal rhythm is part of the shared height, so it lives here rather than on each
   page. These were 12px under the eyebrow and 18px above the chips, which alone put the
   documents band 18px above everything else. */
.hero:not(:has(picture)) .hero-eyebrow,
.page-hero:not(:has(picture)) .eyebrow,
.data-hero .data-hero-eyebrow,
.doc-hero .doc-hero-eyebrow,
.collection-hero .collection-hero-eyebrow {
  margin-bottom: 4px;
}
.doc-hero .doc-hero-badges,
.data-hero .data-hero-badges {
  margin-top: 8px;
}
.doc-hero .doc-hero-sub,
.page-hero:not(:has(picture)) p,
.hero:not(:has(picture)) .hero-sub {
  margin-top: 4px;
  margin-bottom: 0;
}

/* A phone does not need the 3752px band. The full band is 222 KB and a 390px screen shows
   about a tenth of its pixels, which is 167 KB of LCP cost on exactly the connection least
   able to afford it. The small band is 1024x240 -- 2x for a ~512px strip, so it still maps
   about 1:1 on a phone -- and 55 KB.
   The breakpoint is generous on purpose: below 768 no viewport is wide enough for the big
   band to show detail the small one lacks. */
@media (max-width: 767px) {
  .hero:not(:has(picture)),
  .page-hero:not(:has(picture)),
  .data-hero,
  .doc-hero,
  .collection-hero {
    background-image: url('/haiti-topo-band-sm.webp');
  }
}
/* @ds-hero:end */
/* @ds-footer:start */
/* GENERATED by platform/design/build.mjs — edit tokens.json, not this block */
/*
 * The site footer — one definition.
 *
 * It existed under three different class names (.site-footer, .footer on the 404 page,
 * .foot on search) with drifted bodies: `.site-footer a` alone had four distinct rule
 * bodies across four sources. All three names are aliased here rather than renamed, so no
 * markup changes.
 *
 * The footer is a FULL-BLEED NAVY BAND, bookending the navy header. This block used to
 * open "BACKGROUND IS DELIBERATELY NOT SET ... each page keeps its own", on the reasoning
 * that a footer sits on white here and page-grey there. That is no longer true of any
 * page: the ground is one wash and the footer is one band, so a footer that inherited its
 * page's background would now be the odd one out rather than the safe choice.
 *
 * FULL BLEED WITHOUT A WRAPPER ELEMENT. Most pages mark the footer up as a bare
 * `<footer class="site-footer">` with the content as direct children, so there is no inner
 * element to constrain and adding one would mean editing 28 files plus the worker. Instead
 * the band spans the viewport and its horizontal PADDING carries the content in to the
 * container edge: `max(gutter, (100% - container) / 2)`. Below container+2·gutter the max()
 * falls back to the plain gutter, so it degrades on narrow screens instead of going
 * negative. Content lines up with the header and the page body at every width.
 *
 * Footer links were 18px tall, under the 24px WCAG 2.5.8 minimum. They now carry a 24px
 * hit area: the text does not move, only the target grows.
 */

.site-footer,
.footer,
.foot {
  background: var(--ds-color-navy);
}

.site-footer,
.footer,
.foot .wrap {
  max-width: none;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 24px max(var(--ds-size-gutter), calc((100% - var(--ds-size-container)) / 2));
  font-size: var(--ds-text-12);
  color: var(--ds-color-text-on-navy-muted);
}

.footer-logo,
.foot .fl {
  font-family: var(--ds-font-display);
  font-weight: 700;
  color: var(--ds-color-white);
}

/* The bare element selector is deliberate. privacy, terms and sector.html each mark up
   a plain <footer> with no class, so the three class selectors missed them entirely and
   their links stayed 15px tall. Matching the element covers every footer, including the
   next one someone writes without a class. */
footer a,
.site-footer a,
.footer a,
.foot a {
  color: var(--ds-color-text-on-navy-soft);
  text-decoration: none;
  transition: color 0.15s;
  /* 24px target without moving the text (WCAG 2.5.8). */
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

footer a:hover,
.site-footer a:hover,
.footer a:hover,
.foot a:hover {
  color: var(--ds-color-white);
}

@media (max-width: 640px) {
  .site-footer,
  .footer,
  .foot .wrap {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
}

/* The footer keeps TWO flex children so space-between still means what it says: the mark on
   one side, everything clickable on the other. Adding each link as its own child would
   spread them across the full 1300px band. */
.footer-links {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}
/* @ds-footer:end */

/* The /answers/ and /sources/ pages.
 *
 * A linked sheet rather than an inline <style> block in the templates, deliberately. An
 * inline block would put a byte-identical copy in every generated page and need the MIRRORS
 * machinery in platform/design/sources.mjs, which exists because 17 copies of the sector
 * block make every sector-local value look shared and defeat the audit. One sheet has no
 * mirrors to keep in step.
 *
 * These two page types were in neither sources.mjs nor verify/pages.mjs until 2026-08-20, so
 * their raw-hex inline block was never token-checked and never rendered by the pixel gate.
 * Both are registered now. Adding a rule here without a listed page that loads it puts the
 * file back in that state.
 */

.answer-page {
  background: var(--ds-color-surface-alt);
  color: var(--ds-color-text-ink);
}

.answer-back {
  color: var(--ds-color-text-on-navy-soft);
  text-decoration: none;
  font-weight: 600;
}

/* Matches the site's .language-toggle container, because these are the same control: the
   answer layer serves real pages per language, so they are links rather than buttons. */
.language-links {
  display: flex;
  gap: 2px;
  background: var(--ds-color-surface-on-navy);
  border-radius: var(--ds-radius-5);
  padding: 2px;
}

/* The answer layer serves its own language variants as separate pages, so these are links
   rather than the site's client-side toggle. They still have to look like the buttons a
   reader sees everywhere else. */
.language-links .lang-btn {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

/* The answer itself. Sized as the largest text in the body so a reader who came from a
 * search result and reads exactly one sentence reads this one. */
.answer-lede {
  max-width: 880px;
  margin: 0;
  font-size: 1.35rem;
  line-height: 1.48;
  color: var(--ds-color-legacy-page-ink);
  font-weight: 700;
}

.answer-asof {
  margin-top: 12px;
  color: var(--ds-color-text-muted-cool);
}

.answer-section {
  margin-top: 34px;
}

.answer-context {
  max-width: 820px;
  color: var(--ds-color-text-secondary);
  line-height: 1.6;
}

/* The caveat sits between the answer and the chart and has to read as a qualification of the
 * number rather than as a warning about the site. A left rule and the page surface, not a
 * yellow alert box: on several pages this is ordinary methodology, and styling it as an error
 * teaches readers to skip it on the pages where it is load-bearing. */
.answer-caveat {
  max-width: 820px;
  margin: 22px 0 0;
  padding: 16px 18px;
  background: var(--ds-color-surface);
  border-left: 4px solid var(--ds-color-border-accent-muted);
  color: var(--ds-color-text-secondary);
  line-height: 1.6;
}

.answer-caveat h2 {
  margin: 0 0 8px;
  font-size: 1.05rem;
  color: var(--ds-color-text-heading);
}

.answer-caveat p {
  margin: 0;
}

/* The findings list. Each item is one sourced claim; the citation is part of the sentence
 * rather than a superscript, because a page whose whole purpose is to be quoted should carry
 * the attribution into whatever quotes it. */
.answer-findings {
  max-width: 820px;
  display: grid;
  gap: 14px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.answer-findings li {
  background: var(--ds-color-surface);
  border: 1px solid var(--ds-color-border);
  padding: 14px 16px;
  line-height: 1.6;
  color: var(--ds-color-text-secondary);
}

.answer-cite {
  display: block;
  margin-top: 6px;
  font-size: 0.92rem;
  color: var(--ds-color-text-muted-cool);
}

.answer-cite a {
  color: var(--ds-color-text-accent-link);
  font-weight: 600;
}

.answer-chart {
  min-height: 340px;
  border: 1px solid var(--ds-color-border);
  background: var(--ds-color-surface);
}

@media (max-width: 720px) {

}

/* The /sources/ coverage pages. Same sheet as /answers/ because they are the same page
 * family: one names the data that exists on a subject, the other answers a question from it,
 * and they were built with two copies of the same raw-hex block. */

.source-page {
  background: var(--ds-color-surface-subtle);
  color: var(--ds-color-text-heading);
}

.source-hero {
  background: var(--ds-color-legacy-page-navy);
  color: var(--ds-color-white);
  padding: 46px 24px 38px;
}

.source-hero-inner {
  max-width: 1120px;
  margin: 0 auto;
}

.source-back {
  color: var(--ds-color-text-on-navy-body);
  text-decoration: none;
  font-weight: 600;
}

.source-hero h1 {
  max-width: 920px;
  margin: 18px 0 0;
  font-size: clamp(2rem, 5vw, 3.7rem);
  line-height: 1.02;
  letter-spacing: 0;
}

.source-main {
  max-width: 1120px;
  margin: 0 auto;
  padding: 34px 24px 64px;
}

.source-standfirst {
  max-width: 880px;
  margin: 0 0 32px;
  font-size: 1.2rem;
  line-height: 1.55;
  color: var(--ds-color-text-strong);
}

.source-section {
  margin-top: 36px;
}

.source-section h2 {
  margin: 0 0 14px;
  font-size: 1.45rem;
}

/* The table scrolls inside its own box. A wide table that widens the page instead is unusable
 * on a phone, and these run to five columns. */
.source-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--ds-color-border);
  background: var(--ds-color-surface);
}

.source-table {
  width: 100%;
  border-collapse: collapse;
  min-width: 820px;
  font-size: .96rem;
}

.source-table th,
.source-table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--ds-color-border);
  text-align: left;
  vertical-align: top;
}

.source-table th {
  background: var(--ds-color-surface-accent-tint);
  color: var(--ds-color-text-strong);
  font-weight: 700;
}

.source-list {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.source-list li {
  background: var(--ds-color-surface);
  border: 1px solid var(--ds-color-border);
  padding: 14px 16px;
}

.source-list a {
  font-weight: 700;
  color: var(--ds-color-text-accent-link);
}

.source-meta {
  display: block;
  margin-top: 5px;
  color: var(--ds-color-text-muted-cool);
}

.source-gaps {
  margin: 0;
  padding-left: 20px;
  color: var(--ds-color-text-strong);
}

.source-gaps li {
  margin: 10px 0;
}

/* The one dark surface in these two page families.
 *
 * --ds-color-navy-black, not a new token for the #0f172a this block used to carry. Every CSS
 * source holds a copy of the token block, so adding one rewrites all 37 of them and forces a
 * ?v= bump on ~40 pages, several generated by builders whose regeneration would ship unrelated
 * content. A near-identical existing token keeps the change inside these two page families.
 *
 * --ds-color-text-on-navy-strong rather than a border token used as text: check-text-tokens
 * treats a border colour used as text as a failure, and it is right to. */
.source-code {
  background: var(--ds-color-navy-black);
  color: var(--ds-color-text-on-navy-strong);
  padding: 14px 16px;
  overflow-x: auto;
  font-size: .94rem;
}

@media (max-width: 720px) {
  .source-hero {
    padding: 34px 18px 30px;
  }

  .source-main {
    padding: 26px 18px 52px;
  }
}

/* Two wide columns, where the catalog runs four narrow ones. The catalog card carries a
   name and a number; an answer card carries a sentence, and a sentence in a 298px column
   wraps to five lines. Everything else about the card is .catalog-card. */
.answer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(560px, 1fr));
  gap: 20px;
}
