/* ── DESIGN TOKENS ──────────────────────────────────────── */
:root {
  /* Colours */
  --bg:          #F5F1EA;   /* warm off-white paper */
  --bg-deep:     #EFEADF;
  --ink:         #1A1A1A;   /* near-black, never pure #000 */
  --ink-soft:    #4F4944;
  /* --ink-quiet darkened from the original #948B7E. The lighter value gave
     only 2.98:1 contrast on --bg — a WCAG AA failure (4.5:1 needed for
     normal text). #736A5D is the same warm-grey hue, darkened to 4.72:1
     on --bg / 5.02:1 on --paper — AA-compliant while staying visibly the
     quietest tone in the ink scale. CLAUDE.md's accessibility rule
     ("accessibility is not optional") overrides its own colour token. */
  --ink-quiet:   #736A5D;
  --rule:        #E2DCCE;
  --paper:       #FBF8F2;
  --corridor:    #B33028;   /* oxblood — pipeline corridor ONLY */
  --agi:         #B47834;   /* warm amber — AGI infrastructure */
  --receptor:    #2C4F66;   /* charcoal-blue — sensitive receptors */

  /* Distance band tints of --corridor */
  --band-1: rgba(179, 48, 40, 0.22);
  --band-2: rgba(179, 48, 40, 0.11);
  --band-3: rgba(179, 48, 40, 0.05);

  /* Motion */
  --ease:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
}

/* ── RESET ──────────────────────────────────────────────── */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
}

/* ── REDUCED MOTION ─────────────────────────────────────────
   Honour the OS-level "reduce motion" setting. The site uses a lot of
   motion — slide-ins, the pulsing brand dot, the pin pulse, camera
   easing, hover transitions. For users who have asked their system to
   reduce motion (vestibular sensitivity, migraine triggers), collapse
   all CSS animation and transition to near-instant. Map camera moves
   are handled separately in JS (flyTo/fitBounds duration is set to 0
   when this media query matches). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── BASE ───────────────────────────────────────────────── */
html, body {
  background: var(--bg);
  color: var(--ink);
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  min-height: 100vh;
}

/* ── PAPER GRAIN TEXTURE ────────────────────────────────── */
/*
  SVG noise generated inline so there's no extra HTTP request.
  mix-blend-mode: multiply means it darkens slightly against the warm
  off-white, giving the paper feel. pointer-events: none so it doesn't
  block clicks. z-index: 1000 keeps it above everything.
*/
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.1 0 0 0 0 0.08 0 0 0 0 0.05 0 0 0 0.04 0'/></filter><rect width='200' height='200' filter='url(%23n)'/></svg>");
  opacity: 0.5;
  mix-blend-mode: multiply;
  z-index: 1000;
}

/* ── TYPOGRAPHY HELPERS ─────────────────────────────────── */
.serif { font-family: 'Fraunces', Georgia, serif; }
.mono  { font-family: 'Geist Mono', monospace; }

/* ── HEADER ─────────────────────────────────────────────── */
/*
  Glass-fade: the gradient fades to transparent at the bottom so content
  scrolling underneath it looks natural. Not a solid bar.
*/
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding: 18px 22px;
  /* safe-area-inset-top handles iPhone notch/Dynamic Island */
  padding-top: max(18px, env(safe-area-inset-top));
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: linear-gradient(to bottom, var(--bg) 50%, rgba(245, 241, 234, 0) 100%);
}

/* ── BRAND MARK ─────────────────────────────────────────── */
.brand {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 600;
  font-size: 17px;
  letter-spacing: -0.015em;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
}

/* The pulsing dot — the campaign's only "energy" gesture */
.brand .dot {
  width: 7px;
  height: 7px;
  background: var(--corridor);
  border-radius: 50%;
  position: relative;
  flex-shrink: 0;
}

.brand .dot::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  background: var(--corridor);
  opacity: 0.25;
  animation: brandPulse 2.4s ease-out infinite;
}

@keyframes brandPulse {
  0%, 100% { transform: scale(1);   opacity: 0.25; }
  50%       { transform: scale(1.8); opacity: 0; }
}

/* ── HEADER RIGHT GROUP ─────────────────────────────────── */
.header-right {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* ── INFO BUTTON ─────────────────────────────────────────── */
.info-btn {
  font-family: 'Geist', system-ui, sans-serif;
  font-style: italic;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
  background: none;
  border: 1.5px solid var(--rule);
  border-radius: 50%;
  width: 22px;
  height: 22px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: color 150ms var(--ease), border-color 150ms var(--ease);
  line-height: 1;
  padding: 0;
  position: relative;
}
/* Extend touch target to 44×44 px without changing the visual circle */
.info-btn::after {
  content: '';
  position: absolute;
  inset: -11px;
}
.info-btn:hover,
.info-btn:focus-visible {
  color: var(--ink);
  border-color: var(--ink-soft);
  outline: none;
}

/* ── INFO MODAL ──────────────────────────────────────────── */
.info-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  /* 'safe center' falls back to start-alignment when the panel is taller
     than the viewport (small phones with the full info copy) — without
     this, the panel's top is clipped above the viewport and the user
     cannot scroll up to it. Combined with overflow-y: auto so the modal
     itself scrolls when the panel exceeds viewport height. */
  align-items: safe center;
  justify-content: center;
  padding: 24px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.info-modal--open { display: flex; }

.info-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(26, 26, 26, 0.4);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.info-modal__panel {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 28px 28px 24px;
  max-width: 440px;
  width: 100%;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.16);
  /* Belt-and-suspenders fallback for browsers without 'align-items: safe
     center' support (Safari ≤ 15). When the panel itself caps its height
     and scrolls internally, the older centring logic on the container
     cannot push the panel top above the viewport. */
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
}

.info-modal__close {
  position: absolute;
  top: 14px;
  right: 16px;
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 20px;
  line-height: 1;
  color: var(--ink-quiet);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  transition: color 150ms var(--ease);
}
.info-modal__close:hover { color: var(--ink); }

.info-modal__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 14px;
  padding-right: 28px;
}

.info-modal__body p {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-soft);
  margin-bottom: 12px;
}
.info-modal__body p:last-child { margin-bottom: 0; }

.info-modal__source {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.6;
  margin-top: 14px !important;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}

/* ── NAV LINK ───────────────────────────────────────────── */
.nav-link {
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-decoration: none;
}

/* ── MAP CONTAINER ──────────────────────────────────────────────────────────
   100dvh (dynamic viewport height) collapses the address bar on mobile so the
   map fills the true visible area. Falls back to 100vh on older browsers.
*/
#map {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
}

/* Override MapLibre's default font so controls use our sans stack */
.maplibregl-ctrl {
  font-family: 'Geist', system-ui, sans-serif !important;
}

/* Push the scale bar clear of any future bottom sheet */
.maplibregl-ctrl-bottom-left {
  bottom: 12px;
  left: 12px;
}

.maplibregl-ctrl-bottom-right {
  bottom: 12px;
}

/* Navigation control (top-right): push below the fixed header so it
   doesn't sit behind the header gradient or overlap the brand. */
.maplibregl-ctrl-top-right {
  top: 68px;
  right: 12px;
}

/* ── MAP VIEW CONTROL ───────────────────────────────────────────────────────
   Compact segmented control fixed top-centre, just below the header.
   Replaces the large floating map-disclaimer as the primary mode UI.
   Hidden when a result card is open (body.result-open).
*/
.map-view-control {
  position: fixed;
  top: 60px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 25;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  pointer-events: auto;
}

.map-view-control__tabs {
  display: flex;
  background: rgba(251, 248, 242, 0.93);
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 3px;
  gap: 2px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 2px 12px rgba(26, 26, 26, 0.10);
  white-space: nowrap;
}

.map-view-control__btn {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 5px 12px;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-quiet);
  cursor: pointer;
  transition: background 150ms var(--ease), color 150ms var(--ease);
  white-space: nowrap;
  line-height: 1;
}

.map-view-control__btn--active {
  background: var(--ink);
  color: var(--bg);
}

/* Long/short label spans — desktop shows long, mobile shows short.
   Full labels overflow on 360 px devices; short labels fit all mobile widths.
   Mobile override (.btn-label-long { display:none }) is in the ≤639 px block. */
.btn-label-short { display: none; }

.map-view-control__btn:hover:not(.map-view-control__btn--active) {
  background: rgba(26, 26, 26, 0.06);
  color: var(--ink-soft);
}


.map-view-control__status {
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  letter-spacing: 0.03em;
  color: var(--ink-quiet);
  text-align: center;
  line-height: 1.4;
  pointer-events: none;
  max-width: 320px;
  text-shadow: 0 1px 3px rgba(245, 241, 234, 0.9), 0 0 8px rgba(245, 241, 234, 0.8);
}

/* Hide mode control when result sheet is open */
.result-open .map-view-control { display: none; }

/* ── MAP DISCLAIMER OVERLAY ─────────────────────────────────────────────────
   Kept in DOM for JS compatibility (search.js show/hide, main.js more-details,
   map.js collapseDisclaimerMore). All visual mode UI moved to map-view-control.
   Hidden entirely — do not show this element.
*/
.map-disclaimer {
  display: none !important;
}

/* map-disclaimer sub-elements — kept so JS class toggles don't throw errors */
.map-disclaimer__label,
.map-disclaimer__body,
.map-disclaimer__source,
.map-disclaimer__context,
.map-disclaimer__more { display: none; }

/* map-modes__btn — used only inside result card (rendered by search.js modeButtonsHTML) */
.map-modes {
  display: flex;
  justify-content: center;
  gap: 4px;
  margin: 6px 0 4px;
  pointer-events: auto;
}

.map-modes__btn {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 4px 10px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: transparent;
  color: var(--ink-quiet);
  cursor: pointer;
  pointer-events: auto;
  transition: background 150ms var(--ease), color 150ms var(--ease), border-color 150ms var(--ease);
}

.map-modes__btn--active,
.map-modes__btn[aria-pressed="true"] {
  /* Match against aria-pressed too, not just the JS-toggled --active class.
     The result-card buttons are generated by showResult() AFTER setMode()
     has already done its --active loop, so they would otherwise never get
     the class. aria-pressed is set correctly at render time. */
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
}

/* Hiding #search-hint, .search-intro, and privacy link when result is open frees map height. */
.result-open #search-hint         { display: none; }
.result-open .search-intro        { display: none; }
.result-open .search-privacy-link { display: none; }

/* ── SCREEN-READER ONLY ─────────────────────────────────────────────────────*/
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

/* ── SEARCH BAR ─────────────────────────────────────────────────────────────
   Fixed strip above the bottom edge. Sits in front of the map (z 20) but
   below the header (z 30). The progress bar uses position: absolute so it
   doesn't shift the bar height.
*/
.search-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  padding: 12px 16px 20px;
  padding-bottom: max(20px, env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--bg) 70%, rgba(245, 241, 234, 0) 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

/* ── SEARCH BAR COLLAPSED STATE ─────────────────────────────────────────────
   On mobile, JS adds .search-bar--collapsed when the user interacts with the
   map (drag, mode switch, source-site fly). The pill replaces the full panel.
   Desktop never receives this class (JS guard: innerWidth >= 640 → skip).
*/
.search-bar--collapsed {
  padding-top: 8px;
  /* Extra bottom padding lifts the pill clear of the MapLibre attribution strip.
     The gradient floor covers the attribution zone while the pill floats above. */
  padding-bottom: max(32px, calc(env(safe-area-inset-bottom) + 12px));
  background: linear-gradient(to top, var(--bg) 0%, rgba(245, 241, 234, 0) 68%);
}
.search-bar--collapsed .search-intro     { display: none !important; }
.search-bar--collapsed form              { display: none !important; }
.search-bar--collapsed #search-hint     { display: none !important; }
.search-bar--collapsed #search-progress { display: none !important; }
.search-bar--collapsed .search-bar__collapsed-btn { display: flex; }

/* Collapsed "Check postcode" pill — hidden by default, shown in collapsed state */
.search-bar__collapsed-btn {
  display: none;
  align-items: center;
  justify-content: center;
  padding: 10px 22px;
  background: rgba(251, 248, 242, 0.94);
  border: 1px solid var(--rule);
  border-radius: 24px;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 2px 14px rgba(26, 26, 26, 0.10);
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
  transition: background 150ms var(--ease), border-color 150ms var(--ease), color 150ms var(--ease);
}
.search-bar__collapsed-btn:hover {
  background: rgba(251, 248, 242, 0.99);
  border-color: var(--ink-quiet);
  color: var(--ink);
}

.search-bar form {
  width: 100%;
  max-width: 480px;
  display: flex;
  gap: 0;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--paper);
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(26, 26, 26, 0.08);
  transition: box-shadow 200ms var(--ease), border-color 200ms var(--ease);
}

.search-bar form:focus-within {
  border-color: var(--ink-soft);
  box-shadow: 0 2px 16px rgba(26, 26, 26, 0.14);
}

#postcode-input {
  flex: 1;
  border: none;
  background: transparent;
  font-family: 'Geist Mono', monospace;
  font-size: 15px;
  letter-spacing: 0.06em;
  color: var(--ink);
  padding: 13px 16px;
  outline: none;
  text-transform: uppercase;
  /* Override Chrome Android autofill blue background via inset box-shadow —
     the only reliable cross-browser technique. Ensures the paper background
     survives both autofill and focus states on mobile Chrome. */
  -webkit-box-shadow: 0 0 0 1000px var(--paper) inset;
  box-shadow: 0 0 0 1000px var(--paper) inset;
  -webkit-text-fill-color: var(--ink);
  caret-color: var(--ink);
}

/* Chrome autofill pseudo-class — explicit override for all autofill states */
#postcode-input:-webkit-autofill,
#postcode-input:-webkit-autofill:hover,
#postcode-input:-webkit-autofill:focus,
#postcode-input:-webkit-autofill:active {
  -webkit-box-shadow: 0 0 0 1000px var(--paper) inset !important;
  box-shadow: 0 0 0 1000px var(--paper) inset !important;
  -webkit-text-fill-color: var(--ink) !important;
  caret-color: var(--ink) !important;
}

#postcode-input::placeholder {
  color: var(--ink-quiet);
  text-transform: none;
  letter-spacing: 0.02em;
  font-size: 14px;
}

.search-clear {
  flex-shrink: 0;
  border: none;
  background: transparent;
  color: var(--ink-quiet);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  padding: 0 6px;
  /* Visual breathing room between the clear × and the oxblood search
     button — the two glyphs would otherwise sit almost flush. */
  margin-right: 6px;
  display: none;
  align-items: center;
  justify-content: center;
  transition: color 150ms var(--ease);
}
.search-clear:hover { color: var(--ink); }
#postcode-input:not(:placeholder-shown) + .search-clear { display: flex; }

#search-submit {
  flex-shrink: 0;
  align-self: stretch;
  border: none;
  background: var(--corridor);
  color: #fff;
  width: 46px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 150ms var(--ease);
}

#search-submit:hover  { background: #9a2820; }
#search-submit:active { background: #7e2019; }
#search-submit:disabled { background: var(--ink-quiet); cursor: not-allowed; }

#search-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  -webkit-appearance: none;
  appearance: none;
  border: none;
  background: var(--rule);
}

#search-progress::-webkit-progress-bar   { background: var(--rule); }
#search-progress::-webkit-progress-value { background: var(--corridor); }
#search-progress::-moz-progress-bar      { background: var(--corridor); }

.search-hint {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.04em;
  text-align: center;
  max-width: 480px;
}

/* ── SEARCH INTRO ────────────────────────────────────────────────────────────
   Visible before first search. Explains geography and purpose.
   Hidden when result is open (body.result-open) to free map height.
   Frosted card so text stays legible over any basemap tile — water, urban, parks.
   Same warm-paper glass treatment as the mode tabs and KEY toggle.
*/
.search-intro {
  width: 100%;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 14px 10px;
  background: rgba(251, 248, 242, 0.94);
  border: 1px solid var(--rule);
  border-radius: 10px;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 2px 14px rgba(26, 26, 26, 0.07);
}

.search-intro__title {
  font-family: 'Fraunces', serif;
  font-size: 14px;
  font-weight: 600;
  font-variation-settings: "opsz" 18;
  color: var(--ink);
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.search-intro__hook {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.6;
}

.search-intro__caveat {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.5;
}

.search-intro__burden {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 11px;
  font-style: italic;
  color: var(--ink-soft);
  line-height: 1.55;
  border-top: 1px solid var(--rule);
  padding-top: 6px;
  margin-top: 2px;
}

/* About-body wrapper: always visible on desktop */
.search-intro__about-body {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 22px; /* room for the absolutely-positioned close button */
}

/* Chips row: hidden on desktop, shown on mobile */
.search-intro__chips {
  display: none;
  flex-direction: row;
  gap: 8px;
  flex-wrap: wrap;
}

.search-intro__chip {
  display: inline-flex;
  align-items: center;
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: rgba(251, 248, 242, 0.80);
  border: 1px solid var(--rule);
  border-radius: 20px;
  padding: 5px 12px;
  cursor: pointer;
  transition: color 150ms var(--ease), border-color 150ms var(--ease), background 150ms var(--ease);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.search-intro__chip:hover,
.search-intro__chip--active {
  color: var(--ink);
  border-color: var(--ink-quiet);
  background: rgba(251, 248, 242, 0.96);
}

.search-intro__chip--co2 {
  border-color: color-mix(in srgb, var(--corridor) 30%, var(--rule));
  color: color-mix(in srgb, var(--corridor) 60%, var(--ink-soft));
}

.search-intro__chip--co2:hover {
  border-color: color-mix(in srgb, var(--corridor) 55%, var(--rule));
  color: var(--corridor);
}

/* ── ABOUT-BODY HEADING ──────────────────────────────────────────────────── */
.search-intro__about-heading {
  font-family: 'Fraunces', serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 2px;
  line-height: 1.2;
}

/* ── ABOUT-BODY CLOSE BUTTON ─────────────────────────────────────────────── */
.search-intro__about-close {
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--ink-quiet);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 0 8px 16px;
  z-index: 1;
  line-height: 1;
  transition: color 150ms var(--ease);
}
.search-intro__about-close:hover,
.search-intro__about-close:focus-visible {
  color: var(--ink);
}

/* About-body expanded state on mobile */
.search-intro__about-body--open {
  display: flex;
}

/* About-body hidden after desktop close — overrides base display:flex */
.search-intro__about-body--closed {
  display: none !important;
}
/* Chips row re-appears on desktop so user can reopen the about panel */
@media (min-width: 640px) {
  .search-intro__about-body--closed ~ .search-intro__chips {
    display: flex;
    margin-top: 0;
  }
  /* CO₂ chip is redundant on desktop (dedicated button exists elsewhere) */
  .search-intro__about-body--closed ~ .search-intro__chips .search-intro__chip--co2 {
    display: none;
  }
}

/* ── BOTTOM SHEET ───────────────────────────────────────────────────────────
   Slides up from below the search bar. Uses transform so it doesn't cause
   layout shifts. hidden attribute keeps it out of the tab order when closed.
*/
.sheet {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 19;
  /* Bottom padding clears the search bar above the sheet bottom edge.
     The search bar sits in front of the sheet (z-20 > z-19) so the sheet
     only needs enough padding to scroll content above the form. */
  padding: 28px 20px 120px;
  padding-bottom: max(120px, calc(env(safe-area-inset-bottom) + 110px));
  background: var(--paper);
  border-top: 1px solid var(--rule);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -4px 24px rgba(26, 26, 26, 0.10);

  /* Cap at 54 % of the small viewport. 54 % ensures the unexpanded result
     card (including the CO₂ CTA button) sits fully above the ~101 px search
     bar without requiring any scroll. Measured: content=422 px, sheet at
     54 svh=456 px, sheet starts at y=388, CO₂ btn at y≈730, sb at y=743.
     svh excludes browser chrome; falls back to vh. */
  max-height: 54vh;
  max-height: 54svh;
  overflow-y: auto;
  transform: translateY(100%);
  transition: transform 450ms var(--ease);
}

.sheet[hidden] { display: block; visibility: hidden; }

.sheet--visible {
  transform: translateY(0);
  visibility: visible;
}

.sheet__close {
  position: absolute;
  top: 16px;
  right: 20px;
  font-size: 20px;
  line-height: 1;
  color: var(--ink-quiet);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 6px;
  z-index: 1;
  transition: color 150ms var(--ease);
}
.sheet__close:hover { color: var(--ink); }

.sheet__inner {
  max-width: 480px;
  margin: 0 auto;
}

/* ── RESULT TYPOGRAPHY ──────────────────────────────────────────────────────*/
.result-postcode {
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin-bottom: 6px;
}

.result-distance {
  font-family: 'Fraunces', Georgia, serif;
  font-size: clamp(38px, 10vw, 52px);  /* mobile; desktop overrides to 48px in ≥640px block */
  font-weight: 600;
  line-height: 1;
  color: var(--ink);
  letter-spacing: -0.02em;
  font-variation-settings: "opsz" 144;
}

.result-distance-secondary {
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.05em;
  color: var(--ink-quiet);
  margin-top: 3px;
}

.result-postcode-note {
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  color: var(--ink-quiet);
  letter-spacing: 0.03em;
  line-height: 1.5;
  margin-top: 6px;
}

.result-label {
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-top: 4px;
}

.result-status {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 22px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--ink);
  margin-bottom: 8px;
}

.result-status--inside {
  color: var(--corridor);
}

.result-detail {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.6;
  margin-bottom: 10px;
}

.result-disclaimer {
  margin-top: 14px;
  font-family: 'Geist Mono', monospace;
  font-size: 9.5px;
  color: var(--ink-quiet);
  letter-spacing: 0.03em;
  line-height: 1.7;
  border-top: 1px solid var(--rule);
  padding-top: 10px;
}

.result-error {
  font-size: 14px;
  color: var(--ink-soft);
  padding: 8px 0;
}

.result-modes {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}

/* ── EDITORIAL MAP CALLOUTS ─────────────────────────────────────────────────
   Pudding-style annotations: small cards anchored at chosen lng/lat via
   MapLibre HTML Markers. Each has a mono uppercase title and a short
   serif-feeling caption. Pointer-events disabled so they never block map
   pan/drag; informational only, no click affordance. Hidden during postcode
   results so the measurement line stays unobstructed.
*/
.map-callout {
  background: rgba(251, 248, 242, 0.96);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 6px 10px;
  box-shadow: 0 2px 10px rgba(26, 26, 26, 0.10);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  pointer-events: none;
  user-select: none;
  /* Wider, shorter aspect — fitting between the two AGI polygons at coastal
     zoom without occluding either one. Tall narrow callouts pushed past the
     polygon edges; wider boxes lay out in 3-4 lines and stay in the gap. */
  max-width: 280px;
  min-width: 180px;
  text-align: left;
}

@media (min-width: 640px) {
  .map-callout {
    max-width: 320px;
  }
}

.map-callout__title {
  font-family: 'Geist Mono', monospace;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.3;
}

.map-callout__caption {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 10.5px;
  color: var(--ink-soft);
  line-height: 1.4;
  margin-top: 3px;
}

/* ── RESULT NARRATIVE RECORD ────────────────────────────────────────────────
   "What's been said about this proposal" — the editorial heart of the result
   card. Four curated, sourced claims from the verified source matrix, with
   superscript footnote markers that open the Sources & Method modal. Voice:
   newsroom dispatch — named source, active verb, direct quote where the
   precise wording matters.
*/
.result-record {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
}

.result-record__heading {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin: 0 0 10px;
}

.result-record__intro {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin: 0 0 12px;
}

.result-record__claims {
  list-style: none;
  padding: 0;
  margin: 0 0 12px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.result-record__claim {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-soft);
  padding-left: 10px;
  border-left: 2px solid var(--rule);
}

.result-record__source {
  font-weight: 600;
  color: var(--ink);
}

.result-record q {
  quotes: '\201C' '\201D';
  font-style: normal;
}

.result-record__cite {
  font-size: 9px;
  vertical-align: super;
  line-height: 0;
  margin-left: 1px;
}

.result-record__cite a {
  color: var(--corridor);
  text-decoration: none;
  font-family: 'Geist Mono', monospace;
  font-weight: 500;
  padding: 1px 3px;
  border-radius: 2px;
  transition: background 150ms var(--ease);
}

.result-record__cite a:hover,
.result-record__cite a:focus {
  background: rgba(179, 48, 40, 0.10);
  outline: none;
}

/* Editorial instrument hook — small "→ See on …" link that lives at the
   end of a consultee claim and triggers a map action. Stylistically
   subtle so the prose still reads as prose; the hook is a scannable
   aside, not a button. */
.result-record__hook {
  display: inline-block;
  margin-top: 4px;
  font-family: 'Geist Mono', monospace;
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 150ms var(--ease), text-decoration-color 150ms var(--ease);
}
.result-record__hook:hover,
.result-record__hook:focus {
  color: var(--corridor);
  text-decoration-color: var(--corridor);
  outline: none;
}

.result-record__methodology {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--ink-quiet);
  margin: 0;
  line-height: 1.55;
}

.result-record__methodology a {
  color: var(--ink-soft);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 2px;
  transition: text-decoration-color 150ms var(--ease), color 150ms var(--ease);
}

.result-record__methodology a:hover,
.result-record__methodology a:focus {
  color: var(--ink);
  text-decoration-color: var(--ink-quiet);
  outline: none;
}

/* ── RESULT WHY + ACTIONS ───────────────────────────────────────────────────
*/

.result-why {
  margin-top: 10px;
  border-top: 1px solid var(--rule);
  padding-top: 10px;
}

.result-why__toggle {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 6px;
  user-select: none;
}

.result-why__toggle::-webkit-details-marker { display: none; }

.result-why__toggle::after {
  content: '▸';
  font-size: 9px;
  transition: transform 200ms var(--ease);
}

.result-why[open] .result-why__toggle::after {
  transform: rotate(90deg);
}

.result-why__body {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.result-why__body p {
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.6;
}

.result-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
}

/* All result CTAs share one ghost-button treatment so the action row reads
   as a single tier of equivalent calls. Previously the CO₂ button was styled
   as a full-width link, which made the row look like three different kinds
   of thing (button / button / heading). Unified now. */
.result-share-btn,
.result-agi-cta,
.result-co2-cta,
.result-goes-cta {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: none;
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 5px 10px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 150ms var(--ease), color 150ms var(--ease), border-color 150ms var(--ease);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  text-align: left;
}

.result-share-btn:hover,
.result-agi-cta:hover,
.result-co2-cta:hover,
.result-goes-cta:hover {
  background: var(--bg-deep);
  color: var(--ink);
  border-color: var(--ink-quiet);
}

/* ── POSTCODE PIN ───────────────────────────────────────────────────────────
   Solid oxblood dot with a repeating ring pulse. Ring uses a border (not a
   filled blob) so it does not look like a hazard marker.
*/
.result-pin {
  width: 16px;
  height: 16px;
  background: var(--corridor);
  border: 2.5px solid #fff;
  border-radius: 50%;
  box-shadow: 0 2px 8px rgba(179, 48, 40, 0.38);
  position: relative;
}

.result-pin::after {
  content: '';
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 1.5px solid rgba(179, 48, 40, 0.55);
  background: transparent;
  animation: pinPulse 2.6s ease-out infinite;
}

@keyframes pinPulse {
  0%   { transform: scale(0.55); opacity: 0.9; }
  70%  { transform: scale(1.55); opacity: 0; }
  100% { transform: scale(1.55); opacity: 0; }
}

/* ── MEASUREMENT TRACER ─────────────────────────────────────────────────────
   Moving head that travels from the postcode marker to the nearest boundary
   point during the connector animation. Warm gold — reads as "measuring
   instrument", not pipeline route or hazard. Positioned via MapLibre Marker
   so it stays geo-registered without any SVG-overlay complexity.
*/
.measurement-tracer {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #C4933A;
  box-shadow:
    0 0 0 3px rgba(196, 147, 58, 0.22),
    0 0 10px rgba(196, 147, 58, 0.60),
    0 0 20px rgba(196, 147, 58, 0.25);
  pointer-events: none;
}

/* ── MAP LEGEND ─────────────────────────────────────────────────────────────
   Fixed top-left, below the header glass-fade. Shows two layer types.
   pointer-events: none so it never blocks map clicks or drags.
*/
.map-legend {
  position: fixed;
  top: 68px;
  left: 16px;
  /* z-index 25 keeps legend above .search-bar (z 20), which spans full width
     bottom:0 and would otherwise capture pointer events on the bottom legend
     items even though its visible content is centred away from the left. */
  z-index: 25;
  /* pointer-events left as auto (default) so the KEY toggle button is always
     reachable. Items are set to none below so they don't block map drags. */

  background: rgba(251, 248, 242, 0.90);
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 10px 16px 10px;

  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-soft);
  letter-spacing: 0.03em;
  line-height: 1.5;
}

.map-legend__item {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  margin-bottom: 6px;
  transition: opacity 400ms var(--ease);
}

.map-legend__swatch {
  flex-shrink: 0;
  display: inline-block;
  width: 22px;
  height: 10px;
  border-radius: 2px;
  margin-top: 3px;
}

.map-legend__label {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.map-legend__label-name {
  font-size: 10px;
  font-weight: 500;
  color: var(--ink-soft);
  line-height: 1.3;
}

.map-legend__label-sub {
  font-size: 9px;
  color: var(--ink-quiet);
  line-height: 1.4;
  letter-spacing: 0.02em;
}

/* Visual break between context item (corridor) and distance items (wirral, agi) */
.map-legend__item--wirral {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--rule);
}

.map-legend__swatch--wirral {
  background: rgba(179, 48, 40, 0.10);
  border: 2px solid rgba(179, 48, 40, 0.92);
}

.map-legend__swatch--agi {
  background: rgba(180, 120, 52, 0.30);
  border: 2px solid rgba(180, 120, 52, 0.92);
}

.map-legend__swatch--corridor {
  background: transparent;
  border: 1.5px dashed rgba(179, 48, 40, 0.45);
}

.map-legend__swatch--pin {
  background: #B33028;
  border-radius: 50%;
  width: 10px;
  height: 10px;
  margin-left: 6px;
}

.map-legend__note {
  margin-top: 5px;
  font-size: 9px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  border-top: 1px solid var(--rule);
  padding-top: 5px;
}

/* ── Overlay toggles ─────────────────────────────────────────────────────── */
.map-legend__divider {
  height: 1px;
  background: var(--rule);
  margin: 6px 0 4px;
}

.map-legend__section-label {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin: 0 0 4px;
}

.map-overlay-toggle {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 3px 0;
  cursor: pointer;
  user-select: none;
}

.map-overlay-toggle--disabled {
  opacity: 0.45;
  cursor: default;
}

.map-overlay-toggle__input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

/* Track — the pill-shaped toggle */
.map-overlay-toggle__track {
  flex-shrink: 0;
  display: block;
  width: 28px;
  height: 16px;
  background: var(--rule);
  border-radius: 8px;
  margin-top: 1px;
  position: relative;
  transition: background 200ms ease;
}

.map-overlay-toggle__track::after {
  content: '';
  position: absolute;
  left: 2px;
  top: 2px;
  width: 12px;
  height: 12px;
  background: var(--paper);
  border-radius: 50%;
  transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
  box-shadow: 0 1px 2px rgba(0,0,0,0.18);
}

.map-overlay-toggle__input:checked + .map-overlay-toggle__track {
  background: #0A8090;
}

/* Per-overlay active colours */
#overlay-habitats:checked + .map-overlay-toggle__track {
  background: #3D6B3A;
}
#overlay-farmland:checked + .map-overlay-toggle__track {
  background: #C4A35A;
}
#overlay-transport:checked + .map-overlay-toggle__track {
  background: #8B6B3A;
}
#overlay-receptors:checked + .map-overlay-toggle__track {
  background: #2C4F66;
}

.map-overlay-toggle__input:checked + .map-overlay-toggle__track::after {
  transform: translateX(12px);
}

.map-overlay-toggle__label {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.map-overlay-toggle__name {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
}

.map-overlay-toggle__sub {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.4;
}

.map-legend__swatch--source {
  background: #4F4944;
  border-radius: 50%;
  width: 10px;
  height: 10px;
  margin-left: 6px;
  opacity: 0.82;
}

/* Source item: visible in Full Project mode only */
.map-legend__item--source {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--rule);
}
/* Toggle button — hidden on desktop, activated on mobile */
.map-legend__toggle {
  display: none;
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  pointer-events: auto;
  align-items: center;
  gap: 5px;
  position: relative;
}
.map-legend__toggle::after {
  content: '▾';
  font-size: 9px;
  transition: transform 200ms var(--ease);
}
.map-legend--open .map-legend__toggle::after {
  transform: rotate(180deg);
}

/* Items wrapper — always visible on desktop, toggled on mobile.
   pointer-events: none so the text/swatches never block map interaction.
   Overlay toggle labels opt back in via pointer-events: auto below. */
.map-legend__items { display: block; pointer-events: none; }

/* Overlay toggles must receive clicks — re-enable pointer events. */
.map-overlay-toggle { pointer-events: auto; }

/* Contextual reset — hidden by default via the [hidden] attribute. Shown
   when ≥1 overlay is on, so the user can clear all overlays in one tap
   without flipping six toggles individually. */
.map-legend__reset {
  margin-top: 6px;
  align-self: flex-start;
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  background: none;
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 4px 8px;
  cursor: pointer;
  pointer-events: auto;
  transition: color 150ms var(--ease), border-color 150ms var(--ease);
}
.map-legend__reset:hover,
.map-legend__reset:focus {
  color: var(--ink);
  border-color: var(--ink-quiet);
  outline: none;
}

/* Mobile-only quick-action pill sibling of the legend toggle. Hidden by
   default on every viewport; on mobile, becomes visible once at least one
   overlay is on, sitting beside the "Key" button so the user can clear
   the overlays without expanding the legend. Desktop already shows the
   in-legend reset button so the pill is suppressed there. */
.map-legend__clear-pill {
  display: none;
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 4px 10px;
  cursor: pointer;
  pointer-events: auto;
  transition: color 150ms var(--ease), border-color 150ms var(--ease), background 150ms var(--ease);
}
.map-legend__clear-pill:hover,
.map-legend__clear-pill:focus {
  color: var(--ink);
  border-color: var(--ink-quiet);
  background: var(--bg-deep);
  outline: none;
}
.map-legend__clear-pill-count { letter-spacing: 0; }
@media (max-width: 639.98px) {
  .map-legend__clear-pill:not([hidden]) {
    display: inline-block;
    vertical-align: middle;
    margin-left: 6px;
  }
}

/* ── LEGEND: mode-gating ────────────────────────────────────────────────────
   Hide items that are not relevant to the current mode. The active layer(s)
   for each mode are kept at full opacity; secondary/context items are dimmed.

   Full:          corridor + wirral + cheshire + agi + source-sites + pin
   Wirral:        wirral + agi + pin
   Coastal (agi): agi + pin
   Cheshire West: wirral (context) + cheshire + pin
*/

/* Corridor — context-only scoping boundary; show in Full mode only */
.map-legend:not([data-mode="full"]) .map-legend__item--corridor { display: none; }

/* Eastern sections (1–6) — Full mode only */
.map-legend:not([data-mode="full"]) .map-legend__item--eastern { display: none; }

/* Remove orphan border-top on Wirral item when corridor + eastern are hidden above it */
.map-legend:not([data-mode="full"]) .map-legend__item--wirral {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* Cheshire West sections — show in Full and Cheshire West only */
.map-legend[data-mode="local"] .map-legend__item--cheshire,
.map-legend[data-mode="agi"]   .map-legend__item--cheshire { display: none; }

/* AGI — hide in Cheshire West (barely visible at that zoom) */
.map-legend[data-mode="cheshire"] .map-legend__item--agi { display: none; }

/* Wirral — hide in Coastal (AGI is the sole focus there) */
.map-legend[data-mode="agi"] .map-legend__item--wirral { display: none; }

/* Source sites — Full mode only (context markers) */
.map-legend:not([data-mode="full"]) .map-legend__item--source { display: none; }

/* Mode-aware legend dimming.
   Each legend row's opacity reflects how prominently the matching map layer
   actually appears in the current mode (cross-ref MODES[modeKey].layers in
   js/map.js). Items not on the map at all get 0.35 (clearly inactive).
   Items present as context get 0.5 (visible but secondary). Items that are
   the mode's primary subject keep full opacity. Corridor (300m scoping
   outline) and Your-postcode-centre are relevant in every mode so they are
   never dimmed.

   FULL — all section sets are on the map alongside the corridor. Sections
   are dimmed slightly so the corridor outline reads as the headline. */
.map-legend[data-mode="full"] .map-legend__item--wirral,
.map-legend[data-mode="full"] .map-legend__item--cheshire,
.map-legend[data-mode="full"] .map-legend__item--agi      { opacity: 0.5; }

/* LOCAL (Wirral) — wirral + agi are the focus. Eastern, Cheshire, source
   sites are not rendered at all → clearly inactive in the key. */
.map-legend[data-mode="local"] .map-legend__item--eastern,
.map-legend[data-mode="local"] .map-legend__item--cheshire,
.map-legend[data-mode="local"] .map-legend__item--source  { opacity: 0.35; }

/* AGI (Coastal) — agi is the focus. Wirral kept as faint context (it is
   drawn very dim on the map). Eastern, Cheshire, source not rendered. */
.map-legend[data-mode="agi"]   .map-legend__item--wirral  { opacity: 0.5;  }
.map-legend[data-mode="agi"]   .map-legend__item--eastern,
.map-legend[data-mode="agi"]   .map-legend__item--cheshire,
.map-legend[data-mode="agi"]   .map-legend__item--source  { opacity: 0.35; }

/* CHESHIRE WEST — cheshire is the focus. Wirral and agi kept as faint
   context (both rendered dim on the map). Eastern, source not rendered. */
.map-legend[data-mode="cheshire"] .map-legend__item--wirral,
.map-legend[data-mode="cheshire"] .map-legend__item--agi      { opacity: 0.5;  }
.map-legend[data-mode="cheshire"] .map-legend__item--eastern,
.map-legend[data-mode="cheshire"] .map-legend__item--source   { opacity: 0.35; }

/* ── CO₂ JOURNEY BUTTON (map-view-control pill) ─────────────────────────────
   Visible in all modes below the mode tabs (map-view-control).
   Disappears automatically when result-open hides the whole view control.
*/
.co2-journey-btn {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  background: rgba(251, 248, 242, 0.90);
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 4px 12px;
  cursor: pointer;
  white-space: nowrap;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 1px 6px rgba(26, 26, 26, 0.08);
  transition: color 150ms var(--ease), border-color 150ms var(--ease), background 150ms var(--ease);
}
.co2-journey-btn:hover {
  color: var(--ink-soft);
  border-color: var(--ink-quiet);
  background: rgba(251, 248, 242, 0.98);
}

/* ── CO₂ JOURNEY BUTTON (search-intro variant) ───────────────────────────────
   Plain outlined button in the pre-search intro area. Hides with .search-intro
   when a result is open (body.result-open .search-intro { display: none }).
*/
/* Row wrapper used when two CTA buttons (CO₂ journey + What happens next) sit
   side by side in the desktop About panel. flex-wrap so the row collapses to
   two stacked buttons at narrow widths rather than overflowing. */
.search-intro__cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}

.search-intro__co2-btn {
  display: inline-flex;
  align-items: center;
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 5px 10px;
  cursor: pointer;
  transition: color 150ms var(--ease), border-color 150ms var(--ease);
}
.search-intro__co2-btn:hover {
  color: var(--ink);
  border-color: var(--ink-quiet);
}

/* ── CO₂ JOURNEY MODAL ───────────────────────────────────────────────────────*/
.co2-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.co2-modal--open { display: flex; }

.co2-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(26, 26, 26, 0.4);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.co2-modal__panel {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 28px 28px 24px;
  max-width: 480px;
  width: 100%;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.16);
  max-height: calc(100dvh - 48px);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}

.co2-modal__close {
  position: absolute;
  top: 14px;
  right: 16px;
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 20px;
  line-height: 1;
  color: var(--ink-quiet);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  transition: color 150ms var(--ease);
}
.co2-modal__close:hover { color: var(--ink); }

.co2-modal__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 16px;
  padding-right: 28px;
}

.co2-modal__section {
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin-top: 20px;
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}

.co2-modal__intro {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.65;
  margin-bottom: 0;
}

.co2-modal__caveat {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.55;
  margin-top: 12px;
}
.co2-modal__caveat--marker {
  margin-top: 8px;
}

.co2-modal__source {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.5;
  margin-top: 4px;
}

.co2-modal__prose {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.65;
}

.co2-modal__full-cta-row {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: flex-start;
}

/* Primary CTA — filled corridor (oxblood) so it reads as the prominent
   action in the modal. Was previously a small ghost button that vanished
   into the surrounding chip-style elements; user flagged it as needing
   more visibility. */
.co2-modal__full-cta {
  font-family: 'Geist Mono', monospace;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--corridor);
  border: 1px solid var(--corridor);
  border-radius: 6px;
  padding: 10px 18px;
  cursor: pointer;
  white-space: normal;
  text-align: left;
  line-height: 1.35;
  box-shadow: 0 2px 8px rgba(179, 48, 40, 0.20);
  transition:
    background    150ms var(--ease),
    border-color  150ms var(--ease),
    box-shadow    150ms var(--ease),
    transform     150ms var(--ease);
}
.co2-modal__full-cta:hover,
.co2-modal__full-cta:focus {
  background:    #9A2820;
  border-color:  #9A2820;
  box-shadow: 0 3px 12px rgba(179, 48, 40, 0.28);
  outline: none;
}
.co2-modal__full-cta:active {
  transform: translateY(1px);
  box-shadow: 0 1px 4px rgba(179, 48, 40, 0.18);
}

/* ── CO₂ STEPPER ─────────────────────────────────────────────────────────────*/
.co2-stepper {
  display: flex;
  flex-direction: column;
  margin: 20px 0 4px;
}

.co2-stepper__step {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.co2-stepper__node {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
}

.co2-stepper__num {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ink-soft);
  color: var(--paper);
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  font-weight: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  letter-spacing: 0;
  line-height: 1;
}

.co2-stepper__line {
  width: 1px;
  min-height: 12px;
  flex: 1;
  background: var(--rule);
  margin: 4px 0;
}

.co2-stepper__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: 14px;
}

.co2-stepper__step--last .co2-stepper__text {
  padding-bottom: 0;
}

.co2-stepper__label {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
}

.co2-stepper__sub {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.04em;
  line-height: 1.4;
}

/* ── CO₂ SITES LIST ──────────────────────────────────────────────────────────*/
.co2-sites-list {
  display: flex;
  flex-direction: column;
  margin-top: 12px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  overflow: hidden;
}

.co2-site {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--rule);
}
.co2-site:last-child { border-bottom: none; }

.co2-site__name {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
}

.co2-site__detail {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.5;
}

/* ── WATERWAY CROSSING POPUP ─────────────────────────────────────────────────*/
.waterway-popup {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.waterway-popup strong {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.waterway-popup__type {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ── TRANSPORT POPUP (MapLibre) ──────────────────────────────────────────────*/
.transport-popup {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.transport-popup strong {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.transport-popup__type {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ── RECEPTOR POPUP (MapLibre) ───────────────────────────────────────────────*/
.receptor-popup {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.receptor-popup strong {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.receptor-popup__type {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ── SOURCE SITE POPUP (MapLibre) ────────────────────────────────────────────*/
.src-popup-wrap .maplibregl-popup-content {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 12px 14px;
  box-shadow: 0 4px 20px rgba(26, 26, 26, 0.14);
  font-family: 'Geist', system-ui, sans-serif;
}

.src-popup__name {
  display: block;
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
  margin-bottom: 6px;
}

.src-popup__detail {
  font-size: 11px;
  color: var(--ink-soft);
  line-height: 1.5;
  margin-bottom: 6px;
}

.src-popup__caveat {
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.5;
  margin-bottom: 4px;
}

.src-popup__context {
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  line-height: 1.5;
}

/* ── RESPONSIVE: wider screens ──────────────────────────────────────────────*/
@media (min-width: 600px) {
  .search-bar {
    padding-left: 24px;
    padding-right: 24px;
  }

  .sheet {
    padding-left: 24px;
    padding-right: 24px;
  }

  .map-view-control {
    top: 64px;
  }

  .map-legend {
    top: 74px;
    left: 20px;
    max-width: 220px;
  }

}

/* ── RESPONSIVE: desktop compact result card ────────────────────────────────
   On wider screens the result sheet becomes a compact floating card centred
   above the search bar. Key properties:
   - overflow: hidden removes any internal scrollbar
   - max-height: none lets the card size to its content
   - width: min(680px, ...) gives room for the two-column layout
   - padding right: 52px leaves clearance for the × close button
   - bottom: 80px sits comfortably above the search bar (~78px without hint)

   The two-column result layout (result-card) puts the big distance number on
   the left and the explanatory text + mode buttons on the right.

   Hidden state: translateX(-50%) translateY(calc(100% + 120px)) pushes the
   card below the viewport so it slides in cleanly from off-screen.
*/
@media (min-width: 640px) {
  .sheet {
    left: 50%;
    right: auto;
    bottom: 86px;
    width: min(760px, calc(100% - 40px));
    border-radius: 12px;
    max-height: calc(100dvh - 120px);
    max-height: calc(100vh - 120px);
    overflow: hidden;
    overflow-y: auto;
    padding: 28px 52px 36px 28px;
    transform: translateX(-50%) translateY(calc(100% + 120px));
  }

  .sheet--visible {
    transform: translateX(-50%) translateY(0);
  }

  .sheet__inner {
    max-width: none;
  }

  /* Two-column layout: left = postcode + distance number; right = text + actions.
     min-content on the left means the column is exactly as wide as the widest child. */
  .result-card {
    display: grid;
    grid-template-columns: min-content 1fr;
    gap: 0 28px;
    align-items: start;
  }

  /* Reduce distance number from mobile clamp — keeps card height reasonable */
  .result-distance {
    font-size: 48px;
    white-space: nowrap;
  }

  /* Tighten vertical rhythm in context column on desktop */
  .result-status {
    font-size: 18px;
    margin-bottom: 6px;
  }

  .result-detail {
    margin-bottom: 6px;
  }

  .result-disclaimer {
    margin-top: 6px;
  }

  .result-postcode-note {
    margin-top: 4px;
  }

  .result-modes {
    margin-top: 8px;
    padding-top: 6px;
  }
}

/* ── DESKTOP RESULT CARD — WIDER SHEET + TWO-COLUMN CLAIMS LIST ──────────────
   On real desktop widths (≥ 1024 px) the result card was dominating ~50 % of
   the vertical space — almost all of it taken by the four stacked consultee
   claims. Two changes that together cut that in half:
   (1) widen the sheet from 760 px to ~1100 px so the card has horizontal room
       for two text columns at a comfortable reading width;
   (2) multi-column the .result-record__claims list itself so the four claims
       sit 2-and-2 side-by-side instead of stacking in a single tall column. */
@media (min-width: 1024px) {
  .sheet {
    width: min(1100px, calc(100% - 40px));
  }
  /* Three-child grid: metric (top-left), context (right, spans both rows),
     actions (bottom-left under metric). Fills the dead space under the
     hero number with the call-to-action buttons. */
  .result-card {
    /* Fixed-width left column (just enough for the widest CTA button
       "Where the CO₂ comes from") so the right column gets the rest. */
    grid-template-columns: 200px 1fr;
    grid-template-rows: auto 1fr;
    gap: 16px 32px;
    align-items: start;
  }
  .result-card__metric  { grid-column: 1; grid-row: 1; }
  .result-card__context { grid-column: 2; grid-row: 1 / span 2; }
  .result-actions       {
    grid-column: 1;
    grid-row: 2;
    /* Stack the CTA buttons vertically and stretch them to fill the
       narrow left column. Overrides the mobile flex-wrap row. */
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    margin-top: 8px;
    gap: 6px;
  }
  .result-actions > button {
    width: 100%;
    justify-content: flex-start;
  }
  .result-record__claims {
    /* Override the mobile flex-column layout */
    display: block;
    column-count: 2;
    column-gap: 28px;
  }
  .result-record__claim {
    /* CSS columns balance content automatically; break-inside: avoid keeps
       each claim and its border-left intact rather than splitting mid-claim
       across the column gap. Margin-bottom replaces the flex gap. */
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    margin-bottom: 10px;
  }
  .result-record__claim:last-child {
    margin-bottom: 0;
  }
}

/* ── MOBILE OVERLAY COMPACTION ──────────────────────────────────────────────
   Unified controls row: [ KEY ▾ ]  [ Full project | Local area | Coastal AGI ]
   KEY button anchored top-left, mode tabs start just to its right.
   Both sit at top: 64px so they form one visual row.
*/
@media (max-width: 639.98px) {
  /* ── HEADER: prevent brand wrapping at ~360 px viewports ───────────────────
     At 360 px the default 17px brand + 22px horizontal padding is too wide
     and "Peak Cluster Watch" wraps to two lines, pushing the header to ~87 px
     tall. The mode strip sits at top:64px (z-25) — completely inside the
     header (z-30), which then intercepts all taps on the mode buttons.
     Fix: reduce padding/font so the header stays ≤ 50 px single-line,
     with white-space: nowrap as a hard guard. */
  .header {
    padding: 14px 10px;
  }
  .brand {
    font-size: 14px;
    white-space: nowrap;
  }
  .header-right {
    gap: 5px;
  }
  /* ── NARROW-PHONE HEADER FIT ───────────────────────────────────────────────
     At the 375 px design target the brand + four header buttons overflowed the
     viewport by ~32 px, clipping the Share button. The header buttons use a
     wide 0.14em mono tracking and 11px type; trimming both (plus tighter
     padding/gap/brand above) reclaims the space without changing the editorial
     label aesthetic enough to notice. Applied to all mobile widths so the
     header fits the 375 px design target with margin to spare. */
  .header-right .sources-btn,
  .header-right .agi-btn,
  .header-right .share-btn {
    letter-spacing: 0.04em;
    font-size: 10.5px;
  }

  /* ── RESULT-OPEN: clean up search bar / sheet visual merge ──────────────────
     In result-open state the search bar (z-20) sits at bottom:0 on top of
     the sheet (z-19). They share the same warm-paper background, merging
     visually. A border-top on the form draws a clear dividing rule.
     Hiding the Privacy notice link saves ~55px of search-bar height, which
     ensures the mode tabs in the result card are fully above the search bar.
     Privacy notice remains visible on first load / pre-result state. */
  body.result-open .search-bar form {
    display: flex !important;  /* keep form visible; suppress collapsed-btn below */
    border-top: 1px solid var(--rule);
    margin-top: 10px;
  }
  body.result-open .search-bar a[href*="privacy"] {
    display: none;
  }
  /* When a result is open, never show the collapsed "CHECK YOUR POSTCODE" pill —
     the full form is always visible and the pill would overlay the result card. */
  body.result-open .search-bar__collapsed-btn {
    display: none !important;
  }

  /* Position the fixed scroll hint just above the search bar in result-open
     state. 100 px clears the ~101px result-open search bar height; the top of
     the hint is transparent so a pixel or two of overlap with the bar is fine. */
  body.result-open .sheet-scroll-hint {
    bottom: 100px;
  }

  /* Legend container: strip the shared background/border so its bounding box
     no longer paints a unified opaque panel behind the mode tabs.
     The toggle and items each carry their own backgrounds.
     Safe-area-aware top keeps the KEY button below the header on notched phones. */
  .map-legend {
    top: max(64px, calc(env(safe-area-inset-top, 0px) + 44px));
    padding: 0;
    background: none;
    border: none;
    box-shadow: none;
  }

  /* KEY toggle: same height and visual weight as the mode tabs container */
  .map-legend__toggle {
    display: flex;
    height: 27px;
    box-sizing: border-box;
    padding: 0 10px;
    background: rgba(251, 248, 242, 0.93);
    border: 1px solid var(--rule);
    border-radius: 8px;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: 0 2px 12px rgba(26, 26, 26, 0.10);
    color: var(--ink-soft);
  }
  /* Extend KEY touch target to ≥44px via ::before (::after is the ▾ arrow) */
  .map-legend__toggle::before {
    content: '';
    position: absolute;
    inset: -9px;
  }

  /* Items panel: independent background so it appears as a self-contained
     dropdown below the KEY toggle, not a merged block with the tabs above. */
  .map-legend__items {
    display: none;
    margin-top: 8px;
    background: rgba(251, 248, 242, 0.96);
    border: 1px solid var(--rule);
    border-radius: 8px;
    box-shadow: 0 2px 12px rgba(26, 26, 26, 0.10);
    padding: 8px 12px;
    max-width: 280px;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    /* Prevent dropdown from growing past the viewport bottom */
    max-height: calc(100dvh - 120px);
    max-height: calc(100vh - 120px);
    overflow-y: auto;
  }
  .map-legend--open .map-legend__items {
    display: block;
  }

  /* Mode status text: active tab already shows the current mode.
     Hiding avoids content fighting in the small top strip. */
  .map-view-control__status {
    display: none;
  }

  /* Short labels on mobile — "Ches. W." not "Cheshire West".
     Full labels overflow at 360 px; short labels fit all mobile widths. */
  .btn-label-long  { display: none; }
  .btn-label-short { display: inline; }

  /* Mode tabs: fixed width strip between KEY button and viewport right edge.
     Short labels fit without scrolling on 375–414 px devices.
     Safe-area-aware top keeps the strip below the header on notched/Dynamic-Island phones.
     overflow-x: auto kept as safety net for very small widths. */
  .map-view-control {
    top: max(64px, calc(env(safe-area-inset-top, 0px) + 44px));
    left: 76px;
    right: 16px;
    transform: none;
    align-items: flex-start;
  }
  .map-view-control__tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    width: 100%;
  }
  .map-view-control__tabs::-webkit-scrollbar { display: none; }
  .map-view-control__btn {
    padding: 5px 5px;
    flex-grow: 1;   /* distribute spare width equally across the full strip */
    flex-shrink: 0; /* still allow strip to scroll if labels ever overflow */
  }

  /* Navigation control: mobile users pinch-to-zoom — hide +/− buttons.
     Attribution (bottom-right) and scale (bottom-left) remain. */
  .maplibregl-ctrl-top-right { display: none; }

  /* When search panel is collapsed to pill, lift the MapLibre attribution and
     scale controls just above it. 54 px = pill-top clearance (48 px) + 6 px gap.
     Transition animates them up on collapse and back down on expand. */
  .maplibregl-ctrl-bottom-right,
  .maplibregl-ctrl-bottom-left {
    transition: margin-bottom 250ms var(--ease);
  }
  .panel-collapsed .maplibregl-ctrl-bottom-right,
  .panel-collapsed .maplibregl-ctrl-bottom-left {
    /* 100 px clears the collapsed pill (~34px) + its surrounding padding + safe-area
       on devices that have a home bar. Measured pill-top at y≈752 on a 844px
       viewport; 54 px was only lifting attribution to y≈744, still overlapping.
       100 px puts attribution bottom at y≈722 — 30 px clear of the pill top. */
    margin-bottom: 100px;
  }

  /* Hide "& method" suffix and nav placeholder to keep header compact */
  .sources-btn__suffix { display: none; }
  .nav-link { display: none; }
}

/* ── MAPLIBRE ATTRIBUTION + SCALE ────────────────────────────────────────────
   Override default opaque white with the site's warm-paper palette.
   Attribution must always be visible (OpenStreetMap licence).
   Compact ℹ button shows by default; text expands on tap/click.
*/
.maplibregl-ctrl-attrib {
  background: rgba(245, 241, 234, 0.72) !important;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border-radius: 4px !important;
}
.maplibregl-ctrl-attrib-inner,
.maplibregl-ctrl-attrib-inner a {
  font-size: 9px !important;
  color: var(--ink-quiet) !important;
  letter-spacing: 0.01em;
}
.maplibregl-ctrl-attrib-inner a:hover { color: var(--ink-soft) !important; }
.maplibregl-ctrl-attrib-button {
  background-color: transparent !important;
  opacity: 0.55;
  transition: opacity 150ms var(--ease);
}
.maplibregl-ctrl-attrib-button:hover { opacity: 1; }

.maplibregl-ctrl-scale {
  background-color: rgba(245, 241, 234, 0.72) !important;
  border-color: var(--ink-quiet) !important;
  color: var(--ink-quiet) !important;
  font-size: 9px !important;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* ── SOURCES BUTTON ──────────────────────────────────────────────────────────*/
.sources-btn {
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  white-space: nowrap;
  transition: color 150ms var(--ease);
}
.sources-btn:hover,
.sources-btn:focus-visible { color: var(--ink); outline: none; }

/* ── SOURCES MODAL ───────────────────────────────────────────────────────────*/
/* ── WHAT HAPPENS NEXT MODAL ────────────────────────────────────────────────
   Editorial closing of the page. Same shell as .sources-modal — backdrop,
   centred panel, close button, scrollable body. Content-specific styles
   (list, link row) defined inline at the bottom of this block.
*/
.next-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.next-modal--open { display: flex; }

.next-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(26, 26, 26, 0.4);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.next-modal__panel {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 8px;
  max-width: 580px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  padding: 28px 32px 32px;
  box-shadow: 0 8px 28px rgba(26, 26, 26, 0.18);
}

.next-modal__close {
  position: absolute;
  top: 14px;
  right: 18px;
  background: none;
  border: none;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-quiet);
  padding: 4px;
}
.next-modal__close:hover { color: var(--ink); }

.next-modal__title {
  font-family: 'Fraunces', serif;
  font-size: 22px;
  font-weight: 600;
  font-variation-settings: "opsz" 36;
  color: var(--ink);
  margin: 0 0 14px;
  line-height: 1.2;
}

.next-modal__body { display: flex; flex-direction: column; gap: 0; }

.next-modal__section {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin: 18px 0 6px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}
.next-modal__section:first-of-type { margin-top: 14px; }

.next-modal__prose {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.6;
  margin: 0 0 10px;
}
.next-modal__prose--quiet {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.03em;
  color: var(--ink-quiet);
}
.next-modal__prose q {
  quotes: '\201C' '\201D';
  font-style: italic;
}

/* Primary CTA row inside the "Where it goes" modal — same treatment as
   the CO₂ modal's "Show me on the map" button. Filled corridor, soft
   shadow, hover/active. */
.next-modal__cta-row {
  display: flex;
  justify-content: flex-start;
  margin: 6px 0 14px;
}
.next-modal__cta {
  font-family: 'Geist Mono', monospace;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--corridor);
  border: 1px solid var(--corridor);
  border-radius: 6px;
  padding: 10px 18px;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(179, 48, 40, 0.20);
  transition:
    background    150ms var(--ease),
    border-color  150ms var(--ease),
    box-shadow    150ms var(--ease),
    transform     150ms var(--ease);
}
.next-modal__cta:hover,
.next-modal__cta:focus {
  background:    #9A2820;
  border-color:  #9A2820;
  box-shadow: 0 3px 12px rgba(179, 48, 40, 0.28);
  outline: none;
}
.next-modal__cta:active {
  transform: translateY(1px);
  box-shadow: 0 1px 4px rgba(179, 48, 40, 0.18);
}

.next-modal__list {
  list-style: none;
  padding: 0;
  margin: 0 0 6px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.next-modal__list li {
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.55;
  padding-left: 12px;
  border-left: 2px solid var(--rule);
}

.next-modal__link-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: 4px 0 4px;
}

.next-modal__link {
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--corridor);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(179, 48, 40, 0.35);
  transition: text-decoration-color 150ms var(--ease);
}
.next-modal__link:hover,
.next-modal__link:focus {
  text-decoration-color: var(--corridor);
  outline: none;
}

/* ── SOURCES & METHOD MODAL ─────────────────────────────────────────────────*/
.sources-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.sources-modal--open { display: flex; }

.sources-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(26, 26, 26, 0.4);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.sources-modal__panel {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 28px 28px 24px;
  max-width: 540px;
  width: 100%;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.16);
  max-height: calc(100dvh - 48px);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}

.sources-modal__close {
  position: absolute;
  top: 14px;
  right: 16px;
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 20px;
  line-height: 1;
  color: var(--ink-quiet);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  transition: color 150ms var(--ease);
}
.sources-modal__close:hover { color: var(--ink); }

.sources-modal__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 20px;
  padding-right: 28px;
}

.sources-modal__section {
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin-top: 20px;
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}
.sources-modal__section:first-of-type { margin-top: 0; }

/* ── LAYER CARDS ─────────────────────────────────────────────────────────────*/
.sources-layer {
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
}
.sources-layer:last-of-type { border-bottom: none; padding-bottom: 0; }

.sources-layer__name {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 3px;
}

.sources-layer__source {
  font-family: 'Geist Mono', monospace;
  font-size: 10px;
  color: var(--ink-quiet);
  letter-spacing: 0.02em;
  margin-bottom: 7px;
}

.sources-layer__badge {
  display: inline-block;
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 10px;
  margin-bottom: 8px;
}
.sources-layer__badge--yes {
  background: rgba(44, 79, 102, 0.08);
  color: var(--receptor);
  border: 1px solid rgba(44, 79, 102, 0.18);
}
.sources-layer__badge--no {
  background: rgba(148, 139, 126, 0.08);
  color: var(--ink-quiet);
  border: 1px solid var(--rule);
}

.sources-layer__limit {
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.55;
}
.sources-layer__limit-label {
  display: block;
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin-bottom: 2px;
}

/* ── PROSE SECTIONS ──────────────────────────────────────────────────────────*/
.sources-modal__prose {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.65;
  margin-bottom: 0;
}

/* ── AGI BUTTON ──────────────────────────────────────────────────────────────*/
.agi-btn {
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  white-space: nowrap;
  transition: color 150ms var(--ease);
}
.agi-btn:hover,
.agi-btn:focus-visible { color: var(--ink); outline: none; }

@media (max-width: 639.98px) {
  .agi-btn__long { display: none; }
}
@media (min-width: 640px) {
  .agi-btn__short { display: none; }

  /* Desktop only: constrain legend height so its bottom items never run under
     the .search-bar (z 20, fixed bottom:0). The legend gets its own scroll on
     short viewports rather than hiding items. Mobile uses an inner items panel
     with its own scroll, so this would create a phantom outer scrollbox. */
  .map-legend {
    max-height: calc(100dvh - 84px);
    overflow-y: auto;
  }
}

/* ── SHARE BUTTON ────────────────────────────────────────────────────────────*/
.share-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: 'Geist Mono', monospace;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  white-space: nowrap;
  transition: color 150ms var(--ease);
}
.share-btn:hover,
.share-btn:focus-visible { color: var(--ink); outline: none; }
.share-btn__icon { flex-shrink: 0; }

/* ── AGI MODAL ───────────────────────────────────────────────────────────────*/
.agi-modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.agi-modal--open { display: flex; }

.agi-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(26, 26, 26, 0.4);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.agi-modal__panel {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 28px 28px 24px;
  max-width: 540px;
  width: 100%;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.16);
  max-height: calc(100dvh - 48px);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}

.agi-modal__close {
  position: absolute;
  top: 14px;
  right: 16px;
  font-family: 'Geist', system-ui, sans-serif;
  font-size: 20px;
  line-height: 1;
  color: var(--ink-quiet);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  transition: color 150ms var(--ease);
}
.agi-modal__close:hover { color: var(--ink); }

.agi-modal__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 20px;
  padding-right: 28px;
}

.agi-modal__section {
  font-family: 'Geist Mono', monospace;
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin-top: 20px;
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}
.agi-modal__section:first-of-type { margin-top: 0; }

.agi-modal__prose {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.65;
  margin-bottom: 0;
}
.agi-modal__prose--intro { margin-bottom: 6px; }

.agi-modal__list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.agi-modal__list li {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.55;
  padding: 5px 0 5px 14px;
  position: relative;
  border-bottom: 1px solid var(--rule);
}
.agi-modal__list li:last-child { border-bottom: none; padding-bottom: 0; }
.agi-modal__list--last li:last-child { padding-bottom: 0; }
.agi-modal__list li::before {
  content: '–';
  position: absolute;
  left: 0;
  color: var(--ink-quiet);
}

/* Primary CTA inside the AGI explainer modal — "See the two zones on the
   map". Same filled-corridor treatment as the CO₂ and Where-it-goes
   modals' map CTAs. */
.agi-modal__cta-row {
  display: flex;
  justify-content: flex-start;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
}
.agi-modal__cta {
  font-family: 'Geist Mono', monospace;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--paper);
  background: var(--corridor);
  border: 1px solid var(--corridor);
  border-radius: 6px;
  padding: 10px 18px;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(179, 48, 40, 0.20);
  transition:
    background    150ms var(--ease),
    border-color  150ms var(--ease),
    box-shadow    150ms var(--ease),
    transform     150ms var(--ease);
}
.agi-modal__cta:hover,
.agi-modal__cta:focus {
  background:    #9A2820;
  border-color:  #9A2820;
  box-shadow: 0 3px 12px rgba(179, 48, 40, 0.28);
  outline: none;
}
.agi-modal__cta:active {
  transform: translateY(1px);
  box-shadow: 0 1px 4px rgba(179, 48, 40, 0.18);
}

/* ── MOBILE SEARCH BAR COMPACT LAYOUT ────────────────────────────────────────
   At ≤639 px the search-bar bottom sheet collapses to ~130px:
   short title → postcode form → one-line hint → chips row.
   The "About this map" chip expands the about-body inline.
   Desktop (≥640px) is completely untouched.
*/
@media (max-width: 639.98px) {
  /* Hide the long title suffix — keep short "Check your postcode" only */
  .search-intro__title-long {
    display: none;
  }

  /* Tighten the intro card on mobile — reduce padding to keep the card compact */
  .search-intro {
    padding: 8px 12px 8px;
  }

  /* Hide the expanded about-body by default on mobile */
  .search-intro__about-body {
    display: none;
  }
  /* Reveal when toggled open — cap height so panel doesn't overwhelm the screen */
  .search-intro__about-body--open {
    display: flex;
    max-height: 48vh;
    overflow-y: auto;
    padding-top: 24px; /* room for the absolutely-positioned close button */
  }

  /* Show the chips row on mobile */
  .search-intro__chips {
    display: flex;
  }

  /* When the about-body is open, the "Check your postcode" title reads as a
     wrong heading for "About this map" content. Hide it — the chips row below
     already provides orientation. Class toggled by JS on .search-intro. */
  .search-intro--about-open .search-intro__title {
    display: none;
  }

  /* Hide the desktop inline "Where does the CO₂ come from?" button */
  .search-intro__co2-btn {
    display: none !important;
  }

  /* Hide the map-view-control CO₂ pill on mobile (chip is the access point) */
  .co2-journey-btn {
    display: none !important;
  }

  /* Tighten search-bar top padding so map dominates */
  .search-bar {
    padding-top: 10px;
    padding-bottom: max(14px, env(safe-area-inset-bottom));
  }
}

/* ── SHEET SCROLL HINT ───────────────────────────────────────────────────────
   Fixed gradient strip that sits just above the search bar when the result
   sheet content overflows. Lives outside .sheet in the DOM because .sheet
   uses transform for its slide-in animation, which would break position:fixed
   on a child element. Shown/hidden by JS via sheet-scroll-hint--visible.
   Desktop: hidden — the floating card is compact enough that scrolling is rare.
   Mobile: appears at bottom: 100px (above the ~101px result-open search bar).
*/
.sheet-scroll-hint {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 0;       /* overridden in mobile result-open state below */
  z-index: 19;     /* above sheet (z-19, earlier in DOM), below search-bar (z-20) */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(251, 248, 242, 0.96);
  border: 1px solid var(--rule);
  box-shadow: 0 2px 8px rgba(26, 26, 26, 0.10);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  pointer-events: none;
  opacity: 0;
  transition: opacity 280ms var(--ease);
}

.sheet-scroll-hint--visible {
  opacity: 1;
}

.sheet-scroll-hint__arrow {
  font-size: 11px;
  color: var(--ink-soft);
  line-height: 1;
  animation: scrollHintBounce 1.8s ease-in-out infinite;
}

@keyframes scrollHintBounce {
  0%, 100% { transform: translateY(0);   opacity: 0.6; }
  50%       { transform: translateY(4px); opacity: 1;   }
}

@media (min-width: 640px) {
  .sheet-scroll-hint { display: none; }
}

/* ── PRIVACY LINK ────────────────────────────────────────── */
.search-privacy-link {
  margin-top: 0.75rem;
  text-align: center;
}
.search-privacy-link a {
  font-family: 'Geist Mono', monospace;
  font-size: 0.65rem;
  letter-spacing: 0.05em;
  color: var(--ink-quiet);
  text-decoration: none;
  text-underline-offset: 3px;
  transition: color 0.15s;
}
.search-privacy-link a:hover {
  color: var(--ink-soft);
  text-decoration: underline;
}

/* ── EXTRA-NARROW PHONES (≤360 px) ───────────────────────────────────────────
   The 375 px design target fits cleanly. On 320–360 px screens (iPhone SE
   1st-gen and similar) the brand + four header buttons still need a final
   squeeze so the Share button is not clipped. Placed last in the file so it
   wins the cascade over the ≤639 px mobile block at overlapping widths. */
@media (max-width: 360px) {
  .header {
    padding: 14px 8px;
  }
  .brand {
    font-size: 13px;
  }
  .header-right {
    gap: 4px;
  }
  .header-right .sources-btn,
  .header-right .agi-btn,
  .header-right .share-btn {
    font-size: 10px;
    letter-spacing: 0.02em;
  }
}

/* ── PRINT STYLESHEET ───────────────────────────────────────────────────────
   Most likely real-world print scenario: a resident wants to take their
   result card to a councillor or MP meeting. Strip the map, navigation,
   overlays, modals and share buttons; show the result card body with its
   sourced consultee claims; add a footer pointing back to the live site so
   the recipient can verify. If no result is open, show the brand + a
   pointer to the live site. */
@media print {
  /* Page-wide reset to white paper */
  html, body { background: #fff !important; color: #000 !important; }
  body::before { display: none !important; }   /* paper-grain overlay */

  /* Hide every non-content surface */
  .header, #map, .map-view-control, .map-legend, .map-disclaimer,
  .sheet-scroll-hint, .search-bar, .info-modal, .sources-modal,
  .agi-modal, .co2-modal, .next-modal,
  .result-actions, .result-modes,
  .result-share-btn, .result-agi-cta, .result-co2-cta, .result-goes-cta,
  .result-record__hook,
  #sheet-close {
    display: none !important;
  }

  /* The result sheet was a fixed-position panel — flatten it into the page */
  .sheet {
    position: static !important;
    transform: none !important;
    max-height: none !important;
    height: auto !important;
    overflow: visible !important;
    box-shadow: none !important;
    border: none !important;
    background: #fff !important;
    padding: 24pt !important;
    margin: 0 !important;
    inset: auto !important;
  }
  body.result-open { background: #fff !important; }

  .result-card, .result-record { page-break-inside: avoid; }
  .result-postcode      { font-size: 18pt !important; }
  .result-distance      { font-size: 36pt !important; line-height: 1.05; }
  .result-distance-secondary { font-size: 12pt !important; }
  .result-label, .result-postcode-note { font-size: 10pt !important; }
  .result-record__heading { font-size: 9pt !important; letter-spacing: 0.10em; }
  .result-record__intro,
  .result-record__claim   { font-size: 11pt !important; line-height: 1.45; color: #222 !important; }
  .result-record__source  { color: #000 !important; }

  /* Footer with the live URL so a printed copy refers back to the site */
  .sheet::after {
    content: 'Source: peakclusterwatch.org  ·  Every claim above traces to a published Planning Inspectorate document.';
    display: block;
    margin-top: 18pt;
    padding-top: 8pt;
    border-top: 1pt solid #999;
    font-family: 'Geist Mono', monospace;
    font-size: 8pt;
    color: #666;
  }
}
