/* Design system: Stonehaven/Adura layout language, Public First brand.
   Beausite Classic type, navy ink, sage hairline rules, square corners. */

@font-face {
  font-family: "Beausite Classic";
  src: url("fonts/BeausiteClassic-Clear.otf") format("opentype");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Beausite Classic";
  src: url("fonts/BeausiteClassic-Medium.otf") format("opentype");
  font-weight: 500;
  font-display: swap;
}

:root {
  --paper: #ffffff;
  --paper-alt: #f7f9fb;
  --ink: #001d3b;
  --ink2: #2a3245;
  --muted: #6b7a95;
  --accent: #ff6411;
  --accent-soft: #ffe8db;
  /* 5% of the highlight colour — a wash, for panels that should read as
     highlighted without carrying a border. */
  --accent-wash: rgba(255, 100, 17, 0.05);
  --primary-blue: #001d3b;
  --smaller-blue: #7f97c5;
  --rule: #cfd7ed;
  --rail-w: 48px;
  /* The map PNGs' own background tones — stages match them so any letterboxing
     around a map reads as map field rather than a gap. in7 road maps render on
     #f0f0f0, the in5 catchment maps on #f8f9fa. */
  --map-field: #f0f0f0;
  --map-field-cool: #f8f9fa;
  /* Sampled from the dashboard catchment maps themselves, so the treemap beside
     a map is filled in that map's own colours: the pale wash the map lays over
     the driving catchment, and the darker tone where its transit layer sits on
     top of that wash - which is the "both" class, not transit.
     "Transit only" is deliberately grey: the map draws transit over driving, so
     an area reachable only by transit is not distinguishable on it at all, and
     a blue would imply a fill the reader could go and find. */
  --catch-driving: #dee4ef;
  --catch-both: #7d8aa6;
  --catch-transit: #9a9a9a;
  /* The overlap maps' two fills, sampled from the PNGs: teal where a place is
     within reach of one city, mauve where two or more overlap. The shared
     legend above that pair names both. */
  --catch-one-city: #a5c0b7;
  --catch-overlap: #89747b;
  /* The column gap .chart-stack--section lays every chart on. The map sections
     reference it so they sit on the same two-column rhythm as the rest of the
     page instead of each inventing its own width. */
  --page-col-gap: 56px;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: "Beausite Classic", ui-sans-serif, system-ui, sans-serif;
  font-weight: 400;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4 {
  font-weight: 500;
  letter-spacing: -0.01em;
}

/* ---------- Brand rail (fixed left, desktop only) ---------- */

.brand-rail {
  display: none;
}

@media (min-width: 1024px) {
  .brand-rail {
    position: fixed;
    left: 0;
    top: 0;
    height: 100vh;
    width: var(--rail-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    padding: 24px 0;
    border-right: 1px solid var(--rule);
    background: var(--paper);
    z-index: 45;
  }

  .brand-rail img {
    width: 20px;
    height: auto;
    display: block;
  }

  .brand-rail__text {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--muted);
    font-weight: 500;
    white-space: nowrap;
  }

  body {
    padding-left: var(--rail-w);
  }
}

.page {
  margin: 0 auto;
  padding: 0 24px 96px;
}

@media (min-width: 1024px) {
  .page {
    padding: 0 48px 96px;
  }
}

/* ---------- Topbar ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--rule);
  margin: 0 -24px;
  padding: 14px 24px;
}

@media (min-width: 1024px) {
  .topbar {
    margin: 0 -48px;
    padding: 16px 48px;
  }
}

.nav {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}

.topbar-report-btn {
  display: inline-block;
  font-family: inherit;
  font-weight: 500;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 6px 14px;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 0;
  text-decoration: none;
  cursor: pointer;
}

@media (min-width: 640px) {
  .nav {
    gap: 32px;
  }
}

.nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  padding: 4px 2px;
  white-space: nowrap;
  transition: color 0.15s ease;
}

.nav a:hover {
  color: var(--ink);
}

.nav a.is-current {
  color: var(--accent);
  font-weight: 700;
}

.nav-item {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

.nav-item__toggle {
  display: inline-flex;
  align-items: center;
  font-family: inherit;
  background: none;
  border: 0;
  padding: 2px;
  margin: 0;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s ease, transform 0.15s ease;
}

.nav-item__toggle:hover,
.nav-item__toggle.is-open {
  color: var(--accent);
}

.nav-item__toggle.is-open {
  transform: rotate(180deg);
}

.nav-item__menu {
  position: absolute;
  top: 100%;
  left: 0;
  margin: 8px 0 0;
  padding: 6px 0;
  list-style: none;
  background: var(--paper);
  border: 1px solid var(--rule);
  z-index: 40;
  min-width: 200px;
  max-height: 320px;
  overflow-y: auto;
}

.nav-item__menu button {
  display: block;
  width: 100%;
  text-align: left;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  padding: 7px 14px;
  border: 0;
  background: none;
  color: var(--ink);
  cursor: pointer;
}

.nav-item__menu button:hover {
  background: #eef1f5;
}

.nav-item__menu button.is-current {
  color: var(--accent);
  font-weight: 600;
}

/* ---------- Hero ---------- */

.site-header {
  padding: 40px 0 0;
}

@media (min-width: 1024px) {
  .site-header {
    padding-top: 56px;
  }
}

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  margin: 0 -24px;
  padding: 0 24px;
}

@media (min-width: 1024px) {
  .hero {
    grid-template-columns: minmax(0, 1fr) 460px;
    align-items: center;
    margin: 0 -48px;
    padding: 0 48px;
  }
}

.hero__copy {
  padding: 40px 0;
}

@media (min-width: 1024px) {
  .hero__copy {
    padding: 56px 48px 56px 0;
  }
}

.eyebrow {
  font-weight: 500;
  /* Sentence case: the wide-tracked all-caps treatment was doing the work of
     a label but reading as decoration. Tracking goes with the caps. */
  letter-spacing: 0.01em;
  font-size: 0.82rem;
  color: var(--muted);
  margin: 0 0 14px;
}

.hero .eyebrow {
  color: var(--accent);
}

.punch {
  color: var(--accent);
  font-weight: 700;
}

/* ---------- Closer ("The fix") ----------
   Same components as the rest of the page: a section title, a rule-topped
   card grid like the constraints, and the stat block from the hero. */

.prize-band {
  display: grid;
  grid-template-columns: 1fr;
  margin: 20px 0 0;
}

@media (min-width: 640px) {
  .prize-band {
    grid-template-columns: repeat(3, 1fr);
    gap: 0 32px;
  }
}

.stat--prize {
  min-height: 0;
  gap: 12px;
}

.stat--prize .stat__value {
  color: var(--accent);
}

.closer-prize-intro {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--ink);
  margin: 24px 0 0;
}

.closer-prize-intro a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: border-color 0.15s ease;
}

.closer-prize-intro a:hover {
  border-bottom-color: var(--accent);
}

.closer-national {
  max-width: 66ch;
  line-height: 1.55;
  color: var(--muted);
  margin: 32px 0 0;
}

.topbar-report-btn:hover,
.topbar-report-btn:focus-visible,
.closer-report-btn:hover,
.closer-report-btn:focus-visible {
  color: var(--paper);
  background: var(--ink);
}

.closer-report-btn {
  display: inline-block;
  margin-top: 24px;
  padding: 12px 22px;
  font-family: inherit;
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 0;
  text-decoration: none;
  cursor: pointer;
}

.hero h1 {
  font-size: clamp(2rem, 4vw, 3.4rem);
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 0 0 20px;
  max-width: 20ch;
}

.dek {
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--ink2);
  margin: 0 0 12px;
  max-width: 62ch;
}

.note {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0;
}

.hero__media {
  display: none;
  flex-direction: column;
  border-left: 1px solid var(--rule);
}

@media (min-width: 1024px) {
  .hero__media {
    display: flex;
  }
}

.hero__media-img {
  flex: 1;
  min-height: 410px;
  background-image: url("brand/oxcam-arc-hero.png");
  background-size: 100% auto;
  background-position: center 35%;
  background-repeat: no-repeat;
}

.hero__media-cap {
  margin: 0;
  padding: 14px 24px 20px;
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.5;
  color: var(--accent);
}

.hero__kpis {
  display: grid;
  grid-template-columns: 1fr;
  border-bottom: 1px solid var(--rule);
  margin: 0 -24px;
  padding: 0 24px;
}

@media (min-width: 640px) {
  .hero__kpis {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .hero__kpis {
    margin: 0 -48px;
    padding: 0 48px;
  }
}

.stat {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 20px;
  min-height: 7rem;
  padding: 20px 0;
  border-top: 1px solid var(--rule);
}

@media (min-width: 640px) {
  .stat {
    border-top: 0;
    padding: 20px 20px 20px 0;
  }

  .stat + .stat {
    padding-left: 20px;
  }
}

.stat__label {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.7rem;
  color: var(--muted);
}

.stat__value {
  font-weight: 500;
  font-size: 2.6rem;
  line-height: 1;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.stat__rank {
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--ink);
}

.stat__rank--lead {
  font-size: 1.7rem;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.stat__hint {
  font-size: 0.8rem;
  color: var(--muted);
}

/* ---------- Featured chart ---------- */

.overview-block {
  margin-bottom: 56px;
}

.overview-block h3 {
  font-size: 1.35rem;
  margin: 0 0 8px;
}

.overview-block > .section-dek {
  margin: 0 0 16px;
}

.overview-block .section-title {
  margin-bottom: 16px;
}

.featured-controls {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

.featured-controls__label {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.7rem;
  color: var(--muted);
}

.featured-controls select,
.featured-controls button {
  font-family: inherit;
  font-size: 0.85rem;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 8px 12px;
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
}

.featured-controls button {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
  white-space: nowrap;
}

.featured-controls button[hidden] {
  display: none;
}

.featured-chart {
  background: var(--paper);
  overflow: hidden;
}

.featured-chart img {
  width: 100%;
  display: block;
  background: #fff;
}

.featured-chart img[hidden] {
  display: none;
}

.featured-chart__caption {
  margin: 0 0 12px;
  padding: 2px 0 2px 14px;
  border-left: 1px solid var(--accent);
  font-size: 0.98rem;
  font-weight: 500;
  line-height: 1.6;
  color: var(--ink);
  max-width: 68ch;
}

.featured-chart__caption:empty {
  display: none;
}

.featured-native {
  padding: 24px 0;
  overflow-x: auto;
  background: var(--paper);
}

.featured-native[hidden] {
  display: none;
}

/* ---------- Interactive heatmap ---------- */

.heatmap-table {
  border-collapse: collapse;
  font-size: 0.82rem;
  min-width: 900px;
  width: 100%;
  table-layout: fixed;
}

.heatmap-table col.col-rank {
  width: 52px;
}

.heatmap-table col.col-cluster {
  width: 150px;
}

.heatmap-table th,
.heatmap-table td {
  padding: 5px 10px;
  text-align: center;
  overflow: hidden;
}

.heatmap-table thead th {
  cursor: pointer;
  user-select: none;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted);
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}

.heatmap-table thead th.is-sorted {
  color: var(--ink);
}

.heatmap-table tbody th {
  text-align: left;
  white-space: nowrap;
  font-weight: 400;
  padding-right: 16px;
}

.heatmap-table tbody th a {
  color: inherit;
  text-decoration: none;
}

.heatmap-table tbody th a:hover {
  text-decoration: underline;
}

.heatmap-table tbody tr:hover .scorebar {
  background: #dde4ef;
}

.scorebar {
  position: relative;
  height: 27px;
  min-width: 80px;
  background: #eef1f5;
  overflow: hidden;
}

.scorebar__fill {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  display: block;
}

.scorebar__label {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.68rem;
  font-weight: 500;
  line-height: 1;
  pointer-events: none;
}

.scorebar__label--in {
  left: 6px;
  color: #ffffff;
}

.heatmap-table tbody th a {
  font-weight: 500;
}

.scorebar__label--out {
  color: var(--muted);
}

.heatmap-table tr.is-oxcam th {
  color: var(--accent);
  font-weight: 500;
}

.heatmap-table .rank-cell {
  color: var(--muted);
  font-size: 0.78rem;
  text-align: left;
  white-space: nowrap;
  width: 1%;
}

.heatmap-table .rank-head {
  cursor: default;
}

.sort-arrow {
  margin-left: 4px;
  color: var(--ink);
}

/* ---------- Dumbbell / pill scorecard ---------- */

.dumbbell-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}

.dumbbell-picker {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.85rem;
}

.dumbbell-picker label {
  color: var(--muted);
}

.dumbbell-picker select {
  font-family: inherit;
  font-size: 0.85rem;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 6px 10px;
  background: var(--paper);
  color: var(--ink);
}

.view-toggle {
  display: inline-flex;
  border: 1px solid var(--rule);
}

.view-toggle button {
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 500;
  padding: 6px 14px;
  border: 0;
  background: var(--paper);
  color: var(--muted);
  cursor: pointer;
}

.view-toggle button.is-active {
  background: var(--ink);
  color: #ffffff;
}

.pill-row {
  padding: 14px 0;
}

.pill-row + .pill-row {
  border-top: 1px solid var(--rule);
}

.pill-row__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.pill-row__theme {
  font-weight: 500;
  font-size: 0.95rem;
}

.pill-summary {
  font-size: 0.82rem;
  color: var(--muted);
}

.pill-summary strong {
  color: var(--ink);
  font-weight: 500;
  font-size: 0.95rem;
}

.pill-summary__of {
  color: var(--muted);
}

.pill-track {
  position: relative;
  height: 32px;
  border-radius: 16px;
  background: #eef1f5;
  width: 100%;
}

.pill-track__rail {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 40px;
  right: 40px;
}

.pill-tick {
  position: absolute;
  top: 50%;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #ffffff;
  transform: translate(-50%, -50%);
}

.pill-end {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--muted);
}

.pill-end--left { left: 12px; }
.pill-end--right { right: 12px; }

.pill-dot {
  position: absolute;
  top: 50%;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--smaller-blue);
  cursor: default;
}

.pill-dot--primary {
  background: var(--primary-blue);
}

.pill-dot--smaller {
  background: var(--smaller-blue);
}

.pill-dot--subject {
  background: var(--accent);
}

.pill-dot--dim {
  opacity: 0.22;
}

.pill-dot--focus {
  z-index: 2;
}

.pill-median {
  position: absolute;
  top: -6px;
  bottom: -6px;
  width: 3px;
  border-radius: 2px;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 29, 59, 0.75) 0,
    rgba(0, 29, 59, 0.75) 5px,
    transparent 5px,
    transparent 9px
  );
  transform: translateX(-50%);
}

.dumbbell-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 16px;
  font-size: 0.78rem;
  color: var(--muted);
}

.dumbbell-legend span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.dumbbell-swatch {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
}

.legend-dot--highlight {
  background: var(--accent);
}

.legend-dot--primary {
  background: var(--primary-blue);
}

.legend-dot--smaller {
  background: var(--smaller-blue);
}

.dumbbell-median-swatch {
  background: rgba(0, 29, 59, 0.35);
  border-radius: 0;
  width: 2px;
  height: 12px;
}

/* ---------- Catchment explorer ---------- */

/* Was capped at 680px and centred, which left it indented 301px while its own
   heading sat at the content edge: the one centred object on a left-aligned
   page. It now spans the content width like every other block, with the map and
   its readout side by side on the page's two-column rhythm. */
.catchment-explorer {
  background: var(--paper);
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

/* A fixed stage the map is contained within, rather than a full-width image
   whose height follows whichever city and threshold happen to be selected —
   those range from 0.62 to 1.48 in aspect, so the box used to jump on every
   change. The background matches the maps' own field so the letterboxing
   either side of a portrait map is invisible. */
/* A stage the map is contained within, sized by aspect rather than a fixed
   pixel height: the column width varies from ~370px to 640px across the
   breakpoints, and a fixed height left the stage far wider than tall at the
   middle sizes, stranding portrait maps in side letterboxing. Square-ish suits
   the set, whose aspects run 0.62 to 1.48, and the cap stops it towering. */
#catchment-map {
  width: 100%;
  max-width: 640px;
  height: auto;
  aspect-ratio: 1 / 1;
  max-height: 500px;
  display: block;
  object-fit: contain;
  object-position: left center;
  background: transparent;
}

@media (min-width: 1024px) {
  .catchment-explorer {
    /* Readout left, map right, on the page's two columns. Stretched, not
       start-aligned, so the treemap can take whatever height the map column
       ends up at and the two columns close on the same line. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0 var(--page-col-gap);
  }

  .catchment-infographic {
    padding: 0;
    display: flex;
    flex-direction: column;
  }

  /* Fills the height left under the figure rather than holding a fixed aspect,
     which left the readout 74px short of the map beside it. */
  .catchment-infographic .treemap {
    flex: 1;
    min-height: 0;
    aspect-ratio: auto;
    max-height: none;
  }
}

.catchment-infographic {
  padding: 28px 0 0;
}

/* The controls sit directly above the map they redraw. */
.catchment-mapcol .featured-controls {
  margin-bottom: 14px;
}

.catchment-stat {
  margin-bottom: 18px;
}

/* The figure leads and its description follows it, in sentence case: as an
   all-caps label above the number it read as furniture, and the number is the
   thing worth seeing first. */
.catchment-stat__note {
  margin: 6px 0 0;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--muted);
}

/* ---------- Catchment treemap ---------- */

/* Area-true rather than a stacked bar, so each class gets a box with room for
   its own label instead of a legend sitting apart from the colours. Cells are
   positioned in percentages by catchmentTreemapLayout; the aspect gives the
   smallest class enough height to hold a line of text. */
.treemap {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  max-height: 460px;
  overflow: hidden;
}

.treemap__cell {
  position: absolute;
  /* Hairline gutters between cells, drawn in the page ground so the split
     reads without a border box on every cell. */
  box-shadow: inset 0 0 0 1px var(--paper);
  display: flex;
  align-items: flex-start;
  overflow: hidden;
}

.treemap__label {
  display: block;
  padding: 10px 12px;
  color: #fff;
  line-height: 1.35;
}

/* A cell too small for any label holds nothing at all: with padding, an empty
   label still stood 20px tall and overflowed a short cell. */
.treemap__label:empty {
  display: none;
}

/* Two smaller insets, so a shorter cell keeps its name instead of falling
   back to a bare percentage. The left inset holds at 12px on both, so every
   name in the chart still starts on the same line as the others. */
.treemap__label--compact {
  padding: 5px 12px;
}

.treemap__label--tight {
  padding: 2px 6px;
}

.treemap__name {
  display: block;
  font-size: 0.88rem;
  font-weight: 500;
}

.treemap__figure {
  display: block;
  margin-top: 2px;
  font-size: 0.8rem;
  opacity: 0.85;
}

.treemap__cell--driving { background: var(--catch-driving); }
.treemap__cell--both    { background: var(--catch-both); }
.treemap__cell--transit { background: var(--catch-transit); }

/* Every fill is now light enough that ink beats white on all three, by measured
   contrast against each: 13.3:1 on the driving wash, 4.9:1 on the "both" tone
   and 6.0:1 on the transit grey. White would reach only 3.5:1 on the darkest of
   them, so it is overridden away entirely rather than per class. */
.treemap__cell .treemap__label {
  color: var(--ink);
}

/* Only carries the classes whose cell was too small to name itself, so it is
   empty and hidden for most selections. */
.catchment-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 12px;
  font-size: 0.82rem;
  color: var(--muted);
}

.catchment-legend[hidden] {
  display: none;
}

.catchment-legend span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.catchment-swatch {
  width: 10px;
  height: 10px;
  display: inline-block;
}

.catchment-swatch--driving {
  background: var(--catch-driving);
}

.catchment-swatch--both {
  background: var(--catch-both);
}

.catchment-swatch--transit {
  background: var(--catch-transit);
}

/* ---------- Chart sections ---------- */

section {
  padding: 56px 0;
  border-bottom: 1px solid var(--rule);
  scroll-margin-top: 76px;
}

/* The footer draws the page's closing rule - don't double it with the
   last section's border. */
main > section:last-of-type {
  border-bottom: 0;
  padding-bottom: 40px;
}

/* Theme pages: folder blocks sit directly in <main>, so give them the same
   dividing rules sections get on the landing page. */
main > .overview-block,
[id$="-blocks"] > .overview-block + .overview-block {
  border-top: 1px solid var(--rule);
  padding-top: 48px;
}

/* First block sits under the hero's own rule - no divider of its own,
   but the same breathing room. */
main > .overview-block:first-child,
[id$="-blocks"] > .overview-block:first-child {
  border-top: 0;
  padding-top: 48px;
}

.section-title {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 20px;
  margin-bottom: 28px;
  flex-wrap: wrap;
}

.section-title h2 {
  font-size: 1.7rem;
  letter-spacing: -0.01em;
  margin: 0 0 6px;
}

.section-dek {
  color: var(--muted);
  margin: 0;
  max-width: 62ch;
  line-height: 1.55;
}

.chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 20px;
}

.chart-card {
  overflow: hidden;
  background: var(--paper);
  cursor: zoom-in;
  transition: border-color 0.15s ease;
}

.chart-card:hover {
  border-color: var(--ink);
}

/* The frame sits on the map, not the cell. On the cell it enclosed the whole
   grid column, so the letterboxing showed as white space between the line and
   the map; on the image it hugs the imagery exactly. It holds its colour on
   hover too, since darkening it read as a line appearing above the map, and
   cursor: zoom-in is the affordance. */
#overlap-maps-grid .chart-card,
#overlap-maps-grid .chart-card:hover {
  border: 0;
}

#overlap-maps-grid .chart-card img,
#overlap-maps-grid .chart-card:hover img {
  border: 1px solid var(--rule);
}

/* One is landscape (1.27) and one portrait (0.76), so at equal widths they
   rendered 505px and 839px tall and the grid stretched both cards to the
   taller. A fixed stage sizes them together. Two maps land exactly on the
   page's two columns, so they share its edges rather than a 1000px cap. */
@media (min-width: 1024px) {
  #overlap-maps-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--page-col-gap);
  }
}

/* Same stage treatment as the catchment map, so both scale with their column
   and cap at the same height. Square-ish also balances this pair: one is
   landscape at 1.27 and one portrait at 0.76, so a stage biased either way
   strands one of them. */
/* The two cards carry different amounts of text above their maps - only the
   Randstad card states a finding - so the maps are flushed to the bottom of
   their cards instead of following the text. The grid stretches both cards to
   the same height and the two maps are height-matched below, so aligning their
   bottoms aligns their tops. Same device as #development-pattern-grid. */
#overlap-maps-grid .chart-card {
  display: flex;
  flex-direction: column;
}

#overlap-maps-grid .chart-card img {
  margin-top: auto;
}

/* Matched on height rather than width, which is the only way two maps of
   different aspect can both fill their frame: the landscape one (2015x1587,
   1.270) takes the column, and the portrait one (1540x2015, 0.764) takes
   0.764/1.270 = 60.19% of it, which lands them the same height at any column
   width. No stage, so nothing letterboxes. */
#overlap-maps-grid .chart-card img {
  height: auto;
  aspect-ratio: auto;
  background: transparent;
}

#overlap-maps-grid .chart-card img {
  width: 100%;
}

/* Only worth matching while the two sit side by side. Stacked, matching heights
   just shrinks the portrait map to 60% of an already narrow column. */
@media (min-width: 640px) {
  #overlap-maps-grid .chart-card:nth-child(2) img {
    width: 60.19%;
  }
}

/* Keep captions and infographics painted above the scaled map imagery */
.chart-card__caption,
.catchment-infographic {
  position: relative;
  background: var(--paper);
}

.chart-card img {
  width: 100%;
  display: block;
  background: #fff;
}

.chart-card__caption {
  margin: 4px 0 14px;
  padding: 2px 0 2px 14px;
  border-left: 1px solid var(--accent);
  font-size: 0.95rem;
  font-weight: 500;
  line-height: 1.6;
  color: var(--ink);
}

/* A note under a map's place label reads what that one map shows. The finding
   is stated once, in the accent-ruled lead above the grid, so these carry no
   rule and sit in the muted register rather than competing with it. */
#overlap-maps-grid .chart-card__caption,
#development-pattern-grid .chart-card__caption {
  margin: 4px 0 12px;
  border-left: 0;
  padding-left: 0;
  font-size: 0.88rem;
  font-weight: 400;
  color: var(--ink2);
}

/* Sits in the first of the page's two columns, the same 613px every other
   native chart on this page gets, rather than its own 720px cap. */
/* The maps say how growth is shaped, the chart says how fast it is, and each
   column states its own finding first, so the two findings share a line and
   the chart no longer sits a screen below the maps it qualifies. The panel
   takes the wider share: three maps need it, the chart reads fine narrower. */
.development-row {
  display: grid;
  gap: 20px;
}

@media (min-width: 1024px) {
  .development-row {
    /* 1.85:1 is set by the panel's own minimum: three maps at their 240px
       floor plus two 14px gaps and the panel's 16px padding needs 792px, and
       1.85fr of 1225px is 795px. The chart takes the remaining 430px. */
    grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
    gap: var(--page-col-gap);
    /* Deliberately not align-items: start. The chart card is shorter than the
       map panel, and stretching it lets the chart centre against the maps
       (see #development-rate-card below) rather than leaving a ragged gap
       under the right-hand column. */
  }
}

/* Both columns lead with a finding, and the two share a top edge: the lead
   opens the left column above the map panel, the chart's finding opens the
   right above the chart. Neither carries an offset, so they line up by
   sitting at the top of their own column. */
.development-row__col > .section-lead {
  /* The column is the measure here, so the 613px cap meant for a full-width
     block would leave the lead short of the panel it introduces. */
  max-width: none;
}

#development-rate-card {
  display: flex;
  flex-direction: column;
  padding: 0;
}

/* .chart-card__caption carries a 4px top margin for captions that follow a
   place label. This one opens its column, so it drops the margin to start on
   the same line as the lead opposite it. */
#development-rate-card > .chart-card__caption {
  margin-top: 0;
}

/* This chart sits in the row's narrower column, so the shared bar metrics,
   tuned for a 613px card, leave it small inside its own box. Two scoped
   corrections, neither of which touches any other chart on the site.

   1. The 158px label column is sized for cluster names that run long
      ("Shenzhen-HK-Guangzhou"). Here every label is a single short word, the
      widest being "OxCam Arc" and "Amsterdam" at 68px, so 90px of every row
      was empty. 76px clears the widest and hands the bars the rest. */
#development-rate-card .hbar-row {
  grid-template-columns: 76px minmax(0, 1fr);
  /* 2. Ten rows at the shared 26px stood 339px tall in a 489px box, floating
        in 150px of slack because the card stretches to the map panel opposite.
        36px rows fill it at a bar proportion that still reads as a bar. */
  padding: 4px 0;
}

#development-rate-card .hbar-row__track {
  height: 28px;
}

#development-rate-card .chart-card__native {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 14px 14px 10px;
}

/* The three development-pattern maps are exports of regions with very
   different geographic aspect ratios (OxCam wide, Guangzhou tall and narrow),
   so at equal card widths they render at unequal heights and the grid stretches
   every card to the tallest. Paint the cards in the maps' own background tone
   and centre each map in the space left over, so the slack reads as map field
   instead of a ragged white gap under the shorter maps. */
#development-pattern-grid .chart-card {
  display: flex;
  flex-direction: column;
  background: transparent;
}

/* The three regions render 402x320, 402x448 and 402x602 at equal widths, so as
   a small-multiple row their tops and bottoms did not line up. One shared
   stage aligns them. Its aspect is the narrowest map's own (Guangzhou,
   1381x2068), which is the one ratio that squares the row without making any
   of the three smaller than it already was. */
#development-pattern-grid .chart-card img {
  height: auto;
  aspect-ratio: 1381 / 2068;
  object-fit: contain;
  /* Pushed to the foot of the card rather than centred in it: the captions run
     to different line counts, which offset the centred stages by 13px. Equal
     stages flushed to one edge line up on both. */
  margin-top: auto;
  margin-bottom: 0;
  background: transparent;
}

/* Captions are opaque elsewhere to sit above the bled map imagery; here the
   card is already the map's own tone, so let it through. */
#development-pattern-grid .chart-card__caption {
  background: transparent;
}

/* A finding is a bigger step down from the place label than a legend note is
   (which carries its own 4px), so it takes its own space rather than sitting
   flush against the title. */
.chart-card__title + .featured-chart__caption {
  margin-top: 9px;
}

/* The place a map shows, above its note. Unscoped, so the overlap pair and the
   development panel label their maps the same way. */
.chart-card__title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
}

/* Section lead for the map blocks. Same accent-ruled caption the picker
   sections lead with, but a direct child of the block, so a section whose
   visual is a map grid or an interactive explorer still reads finding-first. */
.section-lead {
  margin: 0 0 20px;
  /* One page column, the same 613px the case-study panel and every picker
     caption sit in, rather than the 68ch these inherit. On a full-width map
     block 68ch runs 104px past the left column and the findings stop lining
     up with the four picker sections above. */
  max-width: 613px;
}

/* One panel around the three maps, so they can share a single key inside it. */
.map-panel {
  /* Horizontal rules only: one at the top, and the key's own pair below. The
     side borders closed the grey field into a box; dropping them lets it read
     as a band on the page. The foot rule is this border-bottom, sitting under
     the key's own padding. */
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--map-field);
  padding: 16px 16px 0;
}

.map-panel__maps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
}

@media (min-width: 1024px) {
  .map-panel__maps {
    /* Always the three regions. auto-fit would drop to two columns at the
       narrower width the panel now takes to share its line with the chart,
       which breaks the small-multiple row the shared key describes. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* The key belongs to all three maps, so it sits once at the foot of the panel
   rather than being repeated in every card. */
.map-key {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  margin: 0 -16px;
  padding: 12px 16px 14px;
  border-top: 1px solid var(--rule);
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink2);
}

.map-key__item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.map-key__swatch {
  width: 22px;
  height: 12px;
  flex: none;
  border: 1px solid rgba(0, 0, 0, 0.18);
}

/* Matches the fills the map export uses for the two classes. */
.map-key__swatch--built { background: #b4b4b4; }
.map-key__swatch--new   { background: #d21e1e; }

/* ---------- Map ---------- */

.legend {
  display: flex;
  gap: 18px;
  font-size: 0.82rem;
  color: var(--muted);
  flex-wrap: wrap;
}

.legend__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  display: inline-block;
}

.dot--subject { background: var(--accent); }
.dot--primary { background: var(--primary-blue); }
.dot--smaller { background: var(--smaller-blue); }
/* The overlap maps' two fills, for the key above that pair. */
.dot--one-city { background: var(--catch-one-city); }
.dot--overlap { background: var(--catch-overlap); }

.map-layout {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 0;
  align-items: stretch;
}

#worldmap {
  position: relative;
  background: var(--paper);
  display: flex;
  align-items: center;
  overflow: hidden;
}

.worldmap-svg {
  width: 100%;
  height: auto;
  display: block;
}

.worldmap-land {
  fill: #e8edf6;
  stroke: var(--rule);
  stroke-width: 0.5;
}

.worldmap-marker {
  cursor: pointer;
  opacity: 0.9;
  transition: r 0.1s ease;
}

.worldmap-marker:hover,
.worldmap-marker:focus {
  opacity: 1;
  outline: none;
}

.cluster-panel {
  background: var(--paper);
  padding: 24px;
  min-height: 480px;
}

/* ---------- Cluster detail page ---------- */

.cluster-hero,
.page-hero {
  padding: 40px 24px 48px;
  margin: 0 -24px;
  border-bottom: 1px solid var(--rule);
}

@media (min-width: 1024px) {
  .cluster-hero,
  .page-hero {
    padding: 40px 48px 48px;
    margin: 0 -48px;
  }
}

.page-hero h1 {
  font-size: clamp(2rem, 3.6vw, 3rem);
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 4px 0 0;
}

.page-hero .dek {
  margin-top: 16px;
}

.cluster-switcher {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.cluster-switcher label {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.7rem;
  color: var(--muted);
}

.cluster-switcher select {
  font-family: inherit;
  font-size: 0.9rem;
  padding: 5px 10px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
}

.chart-stack {
  display: flex;
  flex-direction: column;
  gap: 28px;
  max-width: 820px;
}

.metric-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 14px;
}

.metric-head__pos {
  font-size: 1rem;
  font-weight: 500;
  color: var(--muted);
}

.metric-head__name {
  font-size: 0.98rem;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.4;
}

.metric-head__meta {
  margin-top: 2px;
  font-size: 0.8rem;
  color: var(--muted);
}

.cluster-hero h1 {
  font-size: clamp(2rem, 3.6vw, 3rem);
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 4px 0 0;
}

.cluster-panel__placeholder {
  color: var(--muted);
  font-size: 0.9rem;
}

.cluster-panel h3 {
  margin: 0 0 4px;
  font-size: 1.3rem;
}

.cluster-panel__role {
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.7rem;
  color: var(--muted);
  margin: 0 0 18px;
}

.cluster-panel__link {
  display: inline-block;
  margin-top: 14px;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--accent);
  text-decoration: none;
}

.cluster-panel__link:hover {
  text-decoration: underline;
}

.score-row {
  margin-bottom: 12px;
}

.score-row__labels {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  margin-bottom: 4px;
}

.score-row__labels strong {
  font-weight: 500;
}

.score-row__of {
  color: var(--muted);
  font-weight: 400;
}

.score-row__track {
  height: 24px;
  background: #eef1f5;
  overflow: hidden;
}

.score-row__fill {
  height: 100%;
}

/* ---------- Footer ---------- */

.site-footer {
  border-top: 1px solid var(--rule);
  margin: 0 -24px;
  padding: 24px 24px 0;
  color: var(--muted);
  font-size: 0.78rem;
}

@media (min-width: 1024px) {
  .site-footer {
    margin: 0 -48px;
    padding: 24px 48px 0;
  }
}

.site-footer p {
  margin: 0;
}

/* ---------- Lightbox ---------- */

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 29, 59, 0.9);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 40px;
  z-index: 50;
}

.lightbox[hidden] {
  display: none;
}

.lightbox__img {
  max-width: 92vw;
  max-height: 78vh;
  background: #fff;
}

.lightbox__caption {
  color: #fff;
  font-size: 0.9rem;
}

.lightbox__close {
  position: absolute;
  top: 24px;
  right: 32px;
  background: none;
  border: none;
  color: #fff;
  font-size: 2rem;
  cursor: pointer;
  line-height: 1;
}

/* ---------- Responsive ---------- */

@media (max-width: 800px) {
  .map-layout {
    grid-template-columns: 1fr;
  }
  .cluster-panel {
    min-height: auto;
  }
}

/* ---------- Shared tooltip ---------- */

#ui-tooltip {
  position: fixed;
  z-index: 60;
  background: var(--ink);
  color: #ffffff;
  padding: 8px 10px;
  font-size: 0.75rem;
  line-height: 1.4;
  pointer-events: none;
  max-width: 240px;
}

#ui-tooltip strong {
  display: block;
  font-weight: 500;
}

#ui-tooltip span {
  display: block;
  color: #c5cede;
}

#ui-tooltip[hidden] {
  display: none;
}

#ui-tooltip.ui-tooltip--light {
  background: #eef1f5;
  color: var(--ink2);
}

#ui-tooltip.ui-tooltip--light strong {
  font-weight: 400;
  color: var(--ink2);
}

/* ---------- Overview narrative beats (from main, restyled) ---------- */

.overview-block--beat {
  padding-top: 40px;
  margin-bottom: 40px;
  border-top: 1px solid var(--rule);
}

.overview-block--beat:first-of-type {
  padding-top: 0;
  border-top: none;
}

.overview-block--beat .section-dek {
  max-width: 72ch;
  font-size: 1rem;
  line-height: 1.55;
}

.evidence-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
  margin-top: 20px;
}

.evidence-card {
  margin: 0;
  background: var(--paper);
}

.evidence-card img {
  width: 100%;
  display: block;
  background: #fff;
}

.evidence-card figcaption {
  margin-bottom: 14px;
  padding: 2px 0 2px 14px;
  border-left: 1px solid var(--accent);
  font-size: 0.95rem;
  font-weight: 500;
  line-height: 1.6;
  color: var(--ink);
}

/* ---------- Case study callout (constraint pages) ---------- */

.case-study {
  /* Spacing and the page's rules belong to .case-study-band now; the panel
     just fills it. `section` gives every section a bottom border, which the
     band's own closing rule would otherwise double at 613px. */
  /* Centred, not on the page's 96px left grid. Bracketed by two full-bleed
     rules, an off-centre panel read as stranded; centred inside a symmetric
     frame it reads as a deliberate interlude in the argument. Below 1024px
     the panel already fills the column, so auto resolves to nothing. */
  margin: 0 auto;
  border: 0;
  /* One page column wide, the same 613px the charts and maps sit in, which is
     also a sound prose measure once the padding comes off. */
  max-width: 613px;
  padding: 22px 26px 26px;
  /* Unbordered: the wash alone marks it out. */
  background: var(--accent-wash);
}

/* Bracket the panel into the page with the same two rules every other section
   is separated by, drawn at the 48px bleed the sub-page hero and the footer
   use, so they run the full width of the page rather than stopping at the
   613px panel. Free-floating, the panel read as loose. */
/* The case study names itself with an h3 in the heading position, in the same
   picker-head every other section on these pages uses. That was the piece
   missing: it was the one section with no heading, which is why it still read
   as a card dropped into the page. The heading sits inside the band's rules and
   on the page's left grid, so the top rule brackets the section as a whole
   rather than cutting between its heading and its card. */
.case-study-section {
  margin-top: 48px;
}

.case-study-band {
  margin: 0 -24px;
  /* No vertical padding at all: the heading sits directly under the opening
     rule and the closing rule lands on the card's own bottom edge, so neither
     line has inserted space behind it. Side padding only, to hold the content
     on the page's column. */
  padding: 0 24px;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

/* 1180px, not 1024px: the label column is half of whatever the card's 613px
   leaves, and the standfirst inside it is capped at 235px. 1180px is where the
   column first reaches that measure; at 1024px it would be ~157px and both the
   heading and the standfirst would shred. Below this the heading stacks above
   the card at full width instead. */
@media (min-width: 1180px) {
  .case-study-band {
    margin: 0 -48px;
    padding: 0 48px;
    /* The heading sits beside the card, not above it, so the card can start
       hard against the opening rule while the heading text still gets air
       below it. */
    display: grid;
    /* Three tracks, the outer two splitting the remainder evenly: the label
       starts at the band's padding edge, which is the page's left margin, so
       it lines up with the h1, the dek and every block above it, and the card
       stays centred on the page like the rest of the page's content. The
       label's own text is capped below, so the space between it and the card
       is a margin rather than a column that has to be filled. */
    grid-template-columns: minmax(0, 1fr) 613px minmax(0, 1fr);
    align-items: start;
  }

  .case-study-section .picker-head {
    grid-column: 1;
    /* The gap is the text's alone. The card opposite stays flush. */
    padding-top: 22px;
  }

  /* The heading breaks at every width in this column, not only where the
     column happens to be narrow enough to force it: "International" above
     "comparison", set as a two-line marginal label. 8em holds the longer word
     alone but not both, so the break lands between them without a <br> in the
     markup. Below the breakpoint the heading spans the page and stays on one
     line. */
  .case-study-section .picker-head h3 {
    max-width: 8em;
  }

  /* The standfirst takes a measure of its own for the same reason, rather than
     running to whatever the column allows: ~28 characters a line, which sets
     all five of them over two lines and holds the label column to one shape
     across widths. Stacked, it keeps the 62ch measure above. */
  .case-study-section .picker-head .case-study__why {
    max-width: 14em;
  }

  .case-study-band > .case-study {
    grid-column: 2;
  }
}

/* The band's closing rule already separates it from what follows, so the next
   block does not draw a second one below it. */
main > .case-study-section + .overview-block {
  border-top: 0;
}

/* Photo band across the top of the panel. Full-bleed over the panel's own
   padding, so the wash frames the prose and the picture reads as the panel's
   lid rather than a figure inside it. 3:1 matches the 1226x409 sources in
   images/case-studies/, each already cropped to its own focal point (Boston
   and Singapore sit low, to keep their waterfronts), so no object-position is
   needed here and the same rule serves all five. */
.case-study__photo {
  display: block;
  width: calc(100% + 52px);
  aspect-ratio: 3 / 1;
  margin: -22px -26px 16px;
  object-fit: cover;
}

/* The place, on the card rather than in the heading: the card carries its own
   identification, so it still reads on its own if it is screenshotted or
   pulled out of the page. The pin is the only glyph the panel uses. */
.case-study .case-study__place {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Sits under the finding it belongs to, so the title's own 10px carries the
     gap above and this only has to hold the body copy off below. */
  margin: 0 0 14px;
  /* One step below the 1.25rem finding beneath it, not level with it: the
     place identifies the comparator, the title carries the argument, and
     matching them leaves the reader no way to tell which is the headline. */
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--ink);
}

/* Why this cluster is in the report at all, in one line. It sits under the
   section heading as its standfirst, not on the card, so the label column
   carries text at a real measure and says what the comparison is for before
   the card is read. Same 0.85rem muted register the landing page's constraint
   cards use for the same job. The 54ch cap only bites in the stacked layout;
   in the side column the track is narrower than that already. */
.case-study-section .case-study__why {
  margin: 0;
  /* Same register as the hero's .dek: standfirst under a heading is the job
     this now does, so it takes that style's size, colour, leading and measure
     rather than the 0.85rem caption it wore inside the card. */
  max-width: 62ch;
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--ink2);
}

/* Sized in em, not by the width/height attributes on the markup, so the pin
   tracks the place name at whatever size that lands on. */
.case-study__pin {
  flex: none;
  width: 1em;
  height: 1em;
  color: var(--accent);
}

.case-study__title {
  margin: 0 0 10px;
  font-size: 1.25rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.case-study p {
  max-width: 68ch;
  margin: 0 0 14px;
  font-size: 0.95rem;
  line-height: 1.65;
  color: var(--ink2);
}

.case-study p:last-child {
  margin-bottom: 0;
}

/* ---------- Explore / constraint cards (from main, restyled) ---------- */

/* Five constraints. The content is a 5 x 4 matrix (constraint, evidence, case
   study, fix), so it is laid out as one: each constraint is a row, and the two
   labels that used to repeat inside all five cards are stated once as column
   headers instead. Stacked cards return below 1024px, where the per-row
   kickers do the labelling again. */
.explore-grid {
  --explore-cols: 46px minmax(140px, 0.85fr) minmax(0, 1.25fr) minmax(0, 1.15fr) minmax(0, 1.05fr);
  display: grid;
  gap: 0;
}

.explore-head {
  display: none;
}

.explore-card {
  display: grid;
  gap: 4px 28px;
  padding: 18px 0;
  border-top: 1px solid var(--rule);
  text-decoration: none;
  color: var(--ink);
}

.explore-card:last-child {
  border-bottom: 1px solid var(--rule);
}

/* A row marker rather than a label: bigger, quieter, no caps treatment. */
.explore-card__num {
  font-weight: 500;
  font-size: 1.1rem;
  color: var(--smaller-blue);
  font-variant-numeric: tabular-nums;
}

.explore-card__ident {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}

.explore-card__label {
  font-weight: 500;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
  line-height: 1.3;
}

/* Was a fixed 150px, which forced uneven gaps once the text lengths differed.
   Wider rows give it a readable measure instead, and --ink2 lifts the figures
   out of the wash of muted grey the whole card used to sit in. */
.explore-card__hint {
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--ink2);
}

.explore-card__block {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.explore-card__kicker {
  font-weight: 500;
  letter-spacing: 0.01em;
  font-size: 0.72rem;
  color: var(--muted);
}

.explore-card__solved {
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--muted);
}

.explore-card__solved strong {
  color: var(--ink);
  font-weight: 500;
}

/* Same size and measure as the case study text beside it, carried at the
   weight that column gives its <strong>. */
.explore-card__ask {
  font-size: 0.85rem;
  line-height: 1.55;
  font-weight: 500;
  color: var(--ink);
}

.explore-card__cta {
  font-weight: 500;
  font-size: 0.78rem;
  color: var(--muted);
  transition: color 0.15s ease;
}

/* Matrix layout: one row per constraint, headers once at the top. */
@media (min-width: 1024px) {
  .explore-head {
    display: grid;
    grid-template-columns: var(--explore-cols);
    gap: 0 28px;
    padding-bottom: 8px;
    font-weight: 500;
    font-size: 0.72rem;
    color: var(--muted);
  }

  .explore-card {
    grid-template-columns: var(--explore-cols);
    align-items: start;
  }

  /* The kickers are the mobile labelling; the header row replaces them here,
     but they stay in the DOM for screen readers. */
  .explore-card__kicker {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

.explore-card:hover .explore-card__cta {
  color: var(--accent);
}

/* Innovation isn't one of the five constraints (it's OxCam's strength, not
   its gap), so its link lives here in "The prize" instead of in the
   constraints table below. Styled to match .explore-card's typography so it
   reads as a sibling of those cards, just standalone rather than a numbered
   row in that table. */
.beat-link-card {
  display: block;
  max-width: 420px;
  margin-top: 16px;
  padding: 16px 20px;
  border: 1px solid var(--rule);
  border-radius: 4px;
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.15s ease;
}

.beat-link-card:hover {
  border-color: var(--accent);
}

.beat-link-card:hover .beat-link-card__cta {
  color: var(--accent);
}

.beat-link-card__ident {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}

.beat-link-card__label {
  font-weight: 500;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
  line-height: 1.3;
}

.beat-link-card__cta {
  font-weight: 500;
  font-size: 0.78rem;
  color: var(--muted);
  transition: color 0.15s ease;
}

.beat-link-card__hint {
  display: block;
  margin-top: 8px;
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--ink2);
}

/* ---------- Native horizontal bar charts ---------- */

.featured-chart__native {
  padding: 20px 0 4px;
}

.featured-chart__native .hbar {
  max-width: 760px;
}

.featured-chart__native[hidden] {
  display: none;
}

.hbar__title {
  font-weight: 500;
  font-size: 0.95rem;
  margin-bottom: 16px;
}

.hbar-row {
  display: grid;
  grid-template-columns: 158px minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  padding: 3px 0;
}

.hbar-row__label {
  font-size: 0.78rem;
  color: var(--ink2);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hbar-row--subject .hbar-row__label {
  color: var(--accent);
  font-weight: 500;
}

.hbar-row__track {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 20px;
}

.hbar-row__fill {
  display: block;
  height: 100%;
}

.hbar-row__stack {
  display: flex;
  height: 100%;
  flex-shrink: 0;
}

.hbar-row__stack .hbar-row__fill {
  position: static;
}

.hbar-row__value {
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
}

.hbar-row__stack {
  display: flex;
  height: 100%;
  flex-shrink: 0;
}

.hbar-row__stack .hbar-row__fill {
  position: static;
}

.hbar-row--subject .hbar-row__value {
  color: var(--accent);
  font-weight: 500;
}

/* Dual-metric bar rows: bars on the primary scale, diamond markers on a
   second scale of their own (e.g. % of GDP against absolute spend). */
.hbar-row--dual .hbar-row__rail {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
}

.hbar-row__value--onrail {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  padding-left: 8px;
}

.hbar-row__marker {
  position: absolute;
  top: 50%;
  width: 9px;
  height: 9px;
  transform: translate(-50%, -50%) rotate(45deg);
}

.hbar-row__marklabel {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  padding-left: 11px;
  font-size: 0.72rem;
  white-space: nowrap;
}

.dumbbell-swatch--diamond {
  width: 9px;
  height: 9px;
  border-radius: 0;
  transform: rotate(45deg);
}

.hbar__source {
  margin-top: 14px;
  font-size: 0.72rem;
  color: var(--muted);
}

/* ---------- Native SVG charts (scatter / line) ---------- */

.nchart-svg {
  width: 100%;
  height: auto;
  display: block;
  font-family: inherit;
}

.nchart-grid {
  stroke: #eef1f5;
  stroke-width: 1;
}

.nchart-axis {
  stroke: var(--rule);
  stroke-width: 1;
}

.nchart-tick {
  font-size: 12px;
  fill: var(--muted);
}

.nchart-axislabel {
  font-size: 13px;
  font-weight: 500;
  fill: var(--muted);
}

.nchart-divider {
  stroke: var(--rule);
  stroke-width: 1;
  stroke-dasharray: 5 4;
}

.nchart-quadlabel {
  font-size: 12px;
  font-style: italic;
  fill: var(--muted);
}

.nchart-ptlabel {
  font-size: 12px;
  fill: var(--ink2);
}

.nchart-ptlabel--subject {
  fill: var(--accent);
  font-weight: 500;
}

.nchart-pt {
  cursor: default;
}

.chart-card--native {
  cursor: default;
}

.hbar__subtitle {
  display: block;
  font-weight: 400;
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 2px;
}

.gbar-group {
  margin-bottom: 14px;
}

.gbar-group__label {
  font-size: 0.82rem;
  font-weight: 500;
  margin-bottom: 4px;
}

.gbar-line {
  position: relative;
  height: 20px;
  margin: 3px 0;
}

.gbar-line__fill {
  position: absolute;
  top: 0;
  bottom: 0;
  display: block;
}

.gbar-line__value {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------- Presence matrix ---------- */

.matrix-scroll {
  overflow-x: auto;
}

.matrix-table {
  border-collapse: collapse;
  width: 100%;
  min-width: 820px;
  table-layout: fixed;
}

.matrix-table col.matrix-col--label {
  width: 96px;
}

.matrix-table th {
  font-size: 0.74rem;
  font-weight: 500;
  color: var(--muted);
}

/* Column headers run diagonally rather than wrapping, so names like
   "Shenzhen-HK-GZ" stay on one line instead of breaking mid-word. */
.matrix-table thead th {
  height: 92px;
  padding: 0 2px 8px;
  text-align: left;
  vertical-align: bottom;
  white-space: nowrap;
}

.matrix-th-label {
  display: inline-block;
  transform: rotate(-45deg);
  transform-origin: left bottom;
  white-space: nowrap;
}

.matrix-table thead th.is-subject {
  color: var(--accent);
}

.matrix-table tbody th {
  text-align: right;
  padding-right: 12px;
  color: var(--ink2);
  font-size: 0.8rem;
}

.matrix-table td {
  padding: 2px;
  text-align: center;
}

.matrix-cell {
  display: block;
  height: 24px;
  line-height: 24px;
  font-size: 0.72rem;
  color: #ffffff;
}

.matrix-cell--on {
  background: #3b7c74;
}

.matrix-cell--onhl {
  background: var(--accent);
}

.matrix-cell--off {
  background: #eef1f5;
}

/* The presence-matrix chart has 14 comparator columns with long names
   (e.g. "Shenzhen–HK–GZ") that wrap badly once squeezed into one column
   of the two-up .chart-stack--section grid or the 760px cap shared by
   every other native chart. Let it span the full row and drop that cap. */
.featured-chart:has(.matrix-table) {
  grid-column: 1 / -1;
}

.featured-chart__native:has(.matrix-table) .hbar {
  max-width: none;
}

/* ---------- Paired-dot chart ---------- */

.pair-row {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  align-items: center;
  gap: 14px;
  padding: 12px 0;
}

.pair-row__label {
  font-size: 0.82rem;
  color: var(--ink2);
  text-align: right;
}

.pair-row__track {
  height: 20px;
}

.pair-row__rail {
  position: relative;
  display: block;
  height: 100%;
  margin-left: 48px;
  margin-right: 44px;
}

.pair-row__join {
  position: absolute;
  top: 50%;
  height: 2px;
  transform: translateY(-50%);
  background: var(--rule);
}

.pair-row__dot {
  position: absolute;
  top: 50%;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

.pair-row__dot--a { background: var(--accent); z-index: 2; }
.pair-row__dot--b { background: var(--primary-blue); }

.pair-row__val {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}

.pair-row__val--a {
  transform: translate(calc(-100% - 14px), -50%);
  color: var(--accent);
  font-weight: 500;
}

.pair-row__val--b {
  padding-left: 14px;
}

.map-hint {
  position: absolute;
  left: 4px;
  bottom: 4px;
  margin: 0;
  max-width: 180px;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--muted);
  z-index: 2;
  pointer-events: none;
}

/* ---------- Legend explainer (overview map) ---------- */

.section-title + .legend-explain__intro {
  margin-top: -10px;
}

.legend-explain__intro {
  margin: 0 0 18px;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--ink2);
  max-width: 78ch;
}

.map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  margin: 0 0 16px;
  font-size: 0.8rem;
  color: var(--muted);
}

.map-legend__item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: help;
}

.h3-note {
  font-weight: 400;
  font-style: italic;
  font-size: 0.85rem;
  color: var(--muted);
  letter-spacing: 0;
}

/* ---------- Title-as-switcher (cluster profile) ---------- */

.title-switch {
  position: relative;
}

.title-switch__btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-family: inherit;
  font-weight: 500;
  font-size: clamp(2rem, 3.6vw, 3rem);
  line-height: 1.05;
  letter-spacing: -0.015em;
  color: var(--ink);
  background: none;
  border: 0;
  padding: 0;
  margin: 4px 0 0;
  cursor: pointer;
}

.title-switch__chevron {
  color: var(--muted);
  transition: transform 0.15s ease;
}

.title-switch__btn.is-open .title-switch__chevron {
  transform: rotate(180deg);
}

.title-switch__btn:hover .title-switch__chevron {
  color: var(--accent);
}

.cluster-switch-hint {
  margin-left: 12px;
  font-weight: 700;
  font-style: normal;
  font-size: 0.85rem;
  letter-spacing: 0;
  color: var(--accent);
}

.title-switch__menu {
  position: absolute;
  top: 100%;
  left: 0;
  margin: 8px 0 0;
  padding: 6px 0;
  list-style: none;
  background: var(--paper);
  border: 1px solid var(--rule);
  z-index: 30;
  min-width: 240px;
  max-height: 340px;
  overflow-y: auto;
}

.title-switch__menu button {
  display: block;
  width: 100%;
  text-align: left;
  font-family: inherit;
  font-size: 0.9rem;
  padding: 8px 16px;
  border: 0;
  background: none;
  color: var(--ink);
  cursor: pointer;
}

.title-switch__menu button:hover {
  background: #eef1f5;
}

.title-switch__menu button.is-current {
  color: var(--accent);
  font-weight: 500;
}

/* ---------- In-page contents ---------- */

.page-index {
  display: flex;
  align-items: stretch;
  gap: 8px 0;
  flex-wrap: wrap;
  margin-top: 24px;
}

.page-index a {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 20px;
  border-left: 1px solid var(--rule);
}

.page-index a:first-child {
  padding-left: 0;
  border-left: 0;
}



.page-index a {
  font-size: 0.85rem;
  color: var(--ink2);
  text-decoration: none;
}

.page-index a:hover {
  color: var(--accent);
}

.overview-block[id],
section[id] {
  scroll-margin-top: 76px;
}

.chart-stack--section {
  max-width: none;
  gap: 44px;
}

/* Two-up on desktop, single column on smaller screens */
@media (min-width: 1024px) {
  .chart-stack--section {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 44px var(--page-col-gap);
    align-items: start;
  }
}

/* ---------- Two-up picker sections (all five theme pages) ---------- */

.blocks-grid {
  padding-top: 48px;
}

.blocks-grid > .overview-block,
[id$="-blocks"].blocks-grid > .overview-block + .overview-block,
[id$="-blocks"].blocks-grid > .overview-block:first-child {
  border-top: 0;
  padding-top: 0;
}

@media (min-width: 1024px) {
  .blocks-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 56px;
    align-items: start;
  }
}

.picker-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px 16px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}

.picker-head h3 {
  margin: 0;
}

/* The case-study section is not an .overview-block (it carries a full-bleed
   band instead of the block's own rules), so it misses that rule's heading
   size. Its heading is a peer of every other section's, so it takes the same
   size - and only the size, leaving the margin to .picker-head above. */
.case-study-section .picker-head h3 {
  font-size: 1.35rem;
}

/* Stacked (below the grid breakpoint) the heading still introduces the card
   directly, so it keeps no gap of its own; the card follows immediately.
   The standfirst sits under the heading rather than beside it, so this one
   picker-head runs as a column instead of the shared row. */
.case-study-section .picker-head {
  margin-bottom: 0;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.overview-block--picker select {
  max-width: 100%;
}

/* ---------- Dashboard page ---------- */

.dash-kpis {
  border-top: 1px solid var(--rule);
  margin-top: 0;
}

@media (min-width: 640px) {
  .dash-kpis {
    grid-template-columns: repeat(6, 1fr);
  }
}

.dash-controls {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 20px 0 4px;
}

.dash-metric {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr) 52px 70px;
  align-items: center;
  gap: 14px;
  padding: 7px 0;
}

@media (max-width: 800px) {
  .dash-metric {
    grid-template-columns: minmax(0, 1fr) 40px 70px;
  }
  .dash-metric__label {
    grid-column: 1 / -1;
  }
}

.dash-metric__label {
  font-size: 0.82rem;
  line-height: 1.35;
  color: var(--ink2);
}

.dash-metric__strip {
  display: block;
  height: 22px;
  background: #eef1f5;
  border-radius: 11px;
}

.dash-metric__rail {
  position: relative;
  display: block;
  height: 100%;
  margin: 0 14px;
}

.dash-dot {
  position: absolute;
  top: 50%;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  border: 0;
  padding: 0;
  cursor: pointer;
  opacity: 0.35;
}

.dash-dot--focus {
  opacity: 1;
  z-index: 2;
}

.dash-metric__value {
  font-size: 0.85rem;
  font-weight: 500;
  text-align: right;
}

.dash-metric__rank {
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}

/* Cluster profile: more compact theme scorecard */

#cluster-dumbbell .pill-row {
  padding: 8px 0;
}

#cluster-dumbbell .pill-row__head {
  margin-bottom: 6px;
}

#cluster-dumbbell .pill-track {
  height: 24px;
  border-radius: 12px;
}

#cluster-dumbbell .pill-dot {
  width: 16px;
  height: 16px;
}

/* Overview rank chart: slightly more compact */

#scorecard-dumbbell .pill-row {
  padding: 12px 0;
}

#scorecard-dumbbell .pill-track {
  height: 28px;
  border-radius: 14px;
}

#scorecard-dumbbell .pill-dot {
  width: 18px;
  height: 18px;
  opacity: 0.9;
}

#scorecard-dumbbell .pill-dot--dim {
  opacity: 0.22;
}

#scorecard-dumbbell .pill-dot--focus {
  opacity: 1;
}

/* ---------- Column chart ---------- */

.colchart {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  height: 240px;
  padding-top: 20px;
}

.colchart__col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  min-width: 0;
}

.colchart__value {
  font-size: 0.72rem;
  color: var(--muted);
  margin-bottom: 4px;
}

.colchart__value--subject {
  color: var(--accent);
  font-weight: 500;
}

.colchart__bar {
  display: block;
  width: 70%;
  max-width: 44px;
}

.colchart__label {
  margin-top: 8px;
  font-size: 0.68rem;
  text-align: center;
  line-height: 1.25;
  white-space: nowrap;
}

.hbar__titlenote {
  font-weight: 400;
  font-size: 0.8rem;
  color: var(--muted);
  margin-left: 8px;
  white-space: nowrap;
}

/* Viewed cluster emphasised on profile-page charts (orange stays OxCam's):
   the rest of the field fades lightly. */
.hbar--emphasis .hbar-row:not(.hbar-row--em) {
  opacity: 0.55;
}

.hbar-row--em .hbar-row__label,
.hbar-row--em .hbar-row__value {
  color: var(--ink);
  font-weight: 500;
}
