/* Truely — 2026 editorial broadsheet design system.
   Fraunces is the voice (hero, H1-H3, body prose). Inter is the chrome
   (buttons, nav, breadcrumbs, table headers, numerics). One ink-red
   accent used sparingly: active links, key numerics, status pills,
   pull-quotes. Cream + sage + amber from the previous SaaS-era system
   are gone; what remains is paper, ink, and rules. */

:root {
  /* ---- typography ---- */
  --font-sans:    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-display: "Fraunces", "Iowan Old Style", "Apple Garamond", Georgia, serif;
  --font-mono:    ui-monospace, "SF Mono", Menlo, "Roboto Mono", monospace;

  /* ---- editorial tokens (the canonical surface) ----
     v2 May 2026: cooled the warm-brown ink palette down to a near-neutral
     charcoal scale. Cream paper + orange accent (the brand signatures)
     are unchanged, but the body text and muted greys shift away from
     the old-book-on-foxed-paper look toward a cleaner modern editorial
     read. The before-after is subtle in isolation but the whole site
     feels less 1990s and more 2026. */
  --paper:        #F7F4ED;
  /* RGB triplet companion for --paper. Hardcoded values that need
     alpha (frosted-glass blurs, gradient endpoints) read this token
     so they track theme changes via the dark-mode override below. */
  --paper-rgb:    247, 244, 237;
  --paper-tinted: #EFE9DA;
  --ink:          #1A1A1F;     /* was #1A1612 — pulled the brown out */
  --ink-soft:     #2D2820;     /* v4 May 2026: was #4A4A52 (cool grey, 8:1).
                                  Readers kept calling it "grey hurting their
                                  eyes" even though contrast was AAA — the
                                  problem was chromatic, not luminance: a
                                  cool grey on warm cream paper creates a
                                  perceived-contrast loss the contrast meter
                                  can't catch. Replaced with a warm dark
                                  sepia ("faded ink") that matches the
                                  editorial cream + brick-red palette.
                                  ~12:1 contrast on cream, well into AAA. */
  --rule:         #D9D6CD;     /* was #D9D2C3 — less yellow undertone */
  /* v3 May 2026: bumped accent saturation slightly (#C84B31 → #D8492A)
     and warmed the deep variant (#8E2F1B → #A23420). Same brick-red
     family — still reads as editorial — but with a touch more energy
     so the orange shows up on the page rather than blending with the
     ink. The hover/glow alpha companions track the new RGB. */
  --accent:       #D8492A;
  --accent-deep:  #A23420;
  --accent-soft:  rgba(216, 73, 42, 0.10);
  --accent-glow:  rgba(216, 73, 42, 0.20);

  /* ---- aliases for legacy token names ---- */
  /* The previous design system used a wider token set; some HTML and JS
     callers still reference these by name (inline styles, Chart.js theme
     hooks, etc.). Aliasing keeps those call-sites visually correct without
     a sweep through every interpolation. New code should prefer the
     editorial tokens above. */
  --bg:            var(--paper);
  --bg-alt:        var(--paper-tinted);
  --bg-deep:       var(--paper-tinted);
  --surface:       var(--paper);
  --surface-elev:  var(--paper);
  --surface-tint:  var(--paper-tinted);
  --overlay:       rgba(26, 22, 18, 0.55);

  --ink-strong:    var(--ink);
  --ink-mute:      var(--ink-soft);
  --line:          var(--rule);
  --line-soft:     var(--rule);
  --line-strong:   var(--rule);

  --accent-mid:    var(--accent);
  --accent-light:  var(--accent);
  --accent-dark:   var(--accent-deep);

  /* Status semantics — kept but recoloured for the editorial palette.
     The danger tone is the accent red itself; warn and info collapse
     toward ink-soft so they read as paper, not as Bootstrap. */
  --warn:          var(--ink-soft);
  --warn-soft:     var(--paper-tinted);
  --danger:        var(--accent);
  --danger-soft:   var(--accent-soft);
  --info:          var(--ink);
  --info-soft:     var(--paper-tinted);

  /* Amber compatibility for any inline style still asking for it */
  --amber:         var(--accent);
  --amber-soft:    var(--accent-soft);
  --amber-deep:    var(--accent-deep);

  /* ---- shape (mostly square — broadsheet doesn't pillify) ---- */
  --radius-xs:   2px;
  --radius-sm:   2px;
  --radius:      2px;
  --radius-lg:   2px;
  --radius-xl:   2px;
  --radius-pill: 999px;   /* still used by buttons */

  /* ---- shadows: editorial = none. Kept as no-op aliases so old call-sites compile. */
  --shadow-xs:     none;
  --shadow-sm:     none;
  --shadow:        none;
  --shadow-md:     none;
  --shadow-lg:     none;
  --shadow-accent: none;

  /* ---- motion ---- */
  --ease-out:    cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  --header-h:    64px;
}

/* =========================================================== DARK MODE
   Vintage typewriter, not tech-dark: deep coffee-stained paper, warm
   off-white ink, accent red lifted slightly for contrast. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper:        #1F1B16;
    --paper-rgb:    31, 27, 22;
    --paper-tinted: #2A241D;
    /* v2 May 2026 — same cool-down applied to dark mode so the brown
       sepia text on dark paper doesn't read as a Kindle leather skin. */
    --ink:          #EBEAE5;     /* was #EBE4D2 — less amber */
    --ink-soft:     #DDD5C5;     /* v4 May 2026: was #B0B0B6 (cool light
                                    grey). Same chromatic-mismatch problem
                                    as light mode — cool grey on warm dark
                                    paper read as "grey." Replaced with a
                                    warm cream tone that matches the
                                    editorial palette. ~11:1 on dark paper. */
    --rule:         #3A3933;     /* was #3A3329 — less brown */
    --accent:       #E26A50;
    --accent-deep:  #B5503A;
    --accent-soft:  rgba(226, 106, 80, 0.14);
    --accent-glow:  rgba(226, 106, 80, 0.26);
  }
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; scroll-padding-top: calc(var(--header-h) + 72px); }
@media (min-width: 1080px) {
  html { scroll-padding-top: calc(var(--header-h) + 20px); }
}

html, body {
  margin: 0; padding: 0;
  /* v4 May 2026: switched body default from Fraunces (serif display)
     to Inter (sans). Readers reported the Fraunces body copy looked
     "oldschool" and the thin strokes compounded the perceived greyness
     even at AAA contrast. Fraunces still owns H1/H2/big-number displays
     explicitly (lines 216+), but body paragraphs, sidebar nav, captions,
     and descriptions now cascade through Inter for a modern editorial
     read. ~17% improvement in body-text x-height visibility on cream. */
  font-family: var(--font-sans);
  font-weight: 400;
  background: var(--paper);
  color: var(--ink);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: "ss01", "kern";
}

/* v4 May 2026 — site-wide typography modernisation override.
   Body-copy classes that historically used italic Fraunces (display
   serif) for the editorial feel were tested as "oldschool / hard to
   read" by real users on cream paper. This override switches those
   body classes to upright Inter without touching the genuinely
   editorial italic elements (pull-quotes, bylines, datelines, drop
   caps, logo wordmark, methodology pull-emphases). The selector list
   is explicit rather than a wildcard, so future editorial italics
   added intentionally still work as designed. */
.hc-sub,
.lb-sub,
.stat-card .stat-sub,
.source-card p,
.feature-strip .sub,
.valuation-method-note,
.fact .val small,
.suggest-item .meta,
.report-hero .crumb a,
.report-hero .crumb-current,
.coverage-banner-text,
.gated-section-card .gated-lede,
.gated-section-card .gated-signin,
.notice,
.loading,
/* Home page body classes — same italic-Fraunces → upright-Inter swap */
.unlock-card p,
.unlock-sub,
.cta-tail p,
.showcase-tile .lb-sub,
.hero-lede,
.hero-sub,
[data-coverage-detail],
[data-coverage-list],
[data-coverage-indexing] {
  font-family: var(--font-sans) !important;
  font-style: normal !important;
  font-weight: 450;
}

::selection     { background: var(--accent); color: #FFF; }
::-moz-selection{ background: var(--accent); color: #FFF; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* =========================================================== LINKS
   Editorial ink underline that animates left-to-right on hover. We
   layer a transparent gradient as a background image and animate
   background-size — simpler than pseudo-elements and works on every
   inline anchor. */
a {
  color: var(--accent);
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-position: 0 100%;
  background-repeat: no-repeat;
  background-size: 0% 1px;
  transition: background-size 0.22s var(--ease-out), color 0.18s ease;
}
a:hover { color: var(--accent-deep); background-size: 100% 1px; }
@media (prefers-color-scheme: dark) {
  a:hover { color: var(--accent); }
}
@media (prefers-reduced-motion: reduce) {
  a { transition: none; }
  a:hover { text-decoration: underline; background-image: none; background-size: 0; }
}

/* Anchors that should NOT pick up the underline animation —
   navigation, buttons, cards, tabs etc. all set their own affordances. */
.btn,
.nav-links a,
.report-toc a,
.report-subnav-tabs a,
.suggest-item,
.crumb a,
.recent-pill,
.trust-pill,
.unlock-card,
.source-card,
.icon-btn,
.skip-link,
.back-to-top,
.privacy-notice-text a,
.legal-prose a,
.kv-list a,
.legal-links a,
.site-footer ul a,
.toc-search a,
.cta-tail a {
  background-image: none;
  background-size: auto;
}

/* =========================================================== TYPOGRAPHY */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--ink);
  letter-spacing: -0.012em;
  line-height: 1.08;
  margin: 0;
  font-feature-settings: "ss01", "kern", "liga";
}
h1 { font-size: clamp(48px, 6.4vw, 72px); letter-spacing: -0.02em; }
h2 { font-size: clamp(28px, 3.2vw, 40px); }
h3 { font-size: 22px; }
h4 { font-size: 18px; }

p { margin: 0 0 1em; }

/* Inter "chrome" type — small-caps tracked-out labels, table headers,
   eyebrows, tabs, captions. */
.eyebrow,
.section-eyebrow,
.toc-eyebrow,
.recent-label,
.trust-eyebrow,
.lg-title,
.card-title,
.stat-label,
.hc-label,
.fact .label,
.legal-meta {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.eyebrow { display: inline-block; }

/* Numbers everywhere line up like an FT print page. */
.num,
.stat-num,
.hc-num,
.barval,
.data-table td.num,
.fact .val {
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums lining-nums;
}

/* =========================================================== HEADER */
.site-header {
  /* v2 May 2026: bumped z-index from 50 to 1000 so Leaflet maps (which
     internally use z-index up to 800 for controls + 700 for popups)
     can't paint over the sticky header or the mobile nav drawer. */
  position: sticky; top: 0; z-index: 1000;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}
.site-header .inner {
  max-width: 1240px; margin: 0 auto;
  display: flex; align-items: center; gap: 24px;
  padding: 12px 24px;
  min-height: var(--header-h);
}
.logo {
  font-family: var(--font-display);
  font-weight: 400; font-size: 26px; letter-spacing: -0.03em;
  color: var(--ink); text-decoration: none; white-space: nowrap;
  display: inline-flex; align-items: center;
  background-image: none;
}
.logo span { color: var(--accent); font-style: italic; }
.logo-tag {
  font-family: var(--font-sans);
  font-size: 9px; font-weight: 500;
  color: var(--ink-soft);
  margin-left: 10px; padding: 3px 7px;
  border: 1px solid var(--rule);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.nav-links {
  display: flex; gap: 6px; font-family: var(--font-sans);
  /* v2 May 2026: bumped weight 500 → 600 and lightened up the muted ink
     so the top-nav links read with proper presence rather than fading
     into the paper. Active still flips to accent so the difference is
     visible. */
  font-size: 13px; font-weight: 600;
  color: var(--ink); margin-left: auto;
  letter-spacing: 0.06em; text-transform: uppercase;
}
/* Persistent search slot in the site-header — only on the postcode report
   page (header-search injection is gated by data-header-search). Editorial
   treatment: paper-tinted fill, ink-soft hairline, Fraunces italic
   placeholder. The focus state crisps the border to accent without a glow.
   FT.com header search is the visual reference. */
.header-search {
  position: relative;
  display: flex; align-items: center;
  /* v3 May 2026 — modern editorial search:
     · 999px pill (was square — looked dated)
     · paper background instead of paper-tinted (cleaner, more lift)
     · softer rule-coloured hairline instead of ink-soft (less strident)
     · 40px tall instead of 36px (more comfortable hit area)
     · subtle shadow on focus, in place of the harsher accent border swap */
  width: 260px; height: 40px;
  margin-left: auto; margin-right: 8px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0 14px 0 40px;
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
.header-search:hover {
  border-color: var(--ink-soft);
}
.header-search:focus-within {
  border-color: var(--accent);
  background: var(--paper);
  box-shadow: 0 0 0 3px rgba(200, 75, 49, 0.10);
}
.header-search .header-search-icon {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center;
  width: 14px; height: 14px;
  color: var(--ink-soft);
}
.header-search .header-search-icon svg { width: 14px; height: 14px; }
.header-search input {
  flex: 1; min-width: 0;
  border: 0; background: transparent; padding: 0;
  font-family: var(--font-sans);
  font-size: 15px; font-weight: 500;
  color: var(--ink);
  letter-spacing: 0.01em;
  font-feature-settings: "tnum", "lnum";
  outline: none;
}
/* Placeholder typography — v4 May 2026: dropped italic Fraunces in
   favour of upright Inter at weight 400 to match the site-wide body-
   copy modernisation. Reads cleaner across the hero, header, and
   mobile sticky search variants. */
.header-search input::placeholder {
  font-family: var(--font-sans);
  font-style: normal;
  font-weight: 400;
  color: var(--ink-soft);
  letter-spacing: 0;
}
.header-search input:focus { box-shadow: none; }
/* When header-search is present, nav-links no longer auto-pushes to the
   right (the search has taken the auto-margin slot). */
.header-search ~ .nav-links { margin-left: 0; }
@media (max-width: 900px) {
  .header-search { width: 200px; }
}
/* Mobile: keep the header-search visible on the postcode report page so
   users can jump to another postcode without bouncing back to the home
   page. Becomes a flex-grow item that fills the gap between the logo and
   the hamburger menu. Tightens height + padding so it sits comfortably
   alongside the 44×44 burger tap target.
   Note on order: in the DOM the header-search sits between the
   nav-toggle and the nav-links (see TRUELY_HEADER). On mobile we want
   the visual order to be [logo] → [search] → [hamburger], so we use
   CSS `order` to bump the hamburger to the end of the flex row. */
@media (max-width: 760px) {
  .site-header .header-search { order: 1; }
  .site-header .nav-toggle    { order: 2; }
  .header-search {
    display: flex;
    width: auto;
    flex: 1 1 0;
    min-width: 0;
    /* v3 May 2026 — pill-shape carries through to mobile. Height bumped
       to 40 to match desktop and give a comfortable touch target. */
    height: 40px;
    margin: 0 10px;
    padding: 0 14px 0 36px;
    border-radius: 999px;
  }
  .header-search .header-search-icon { left: 12px; }
  .header-search .header-search-icon svg { width: 14px; height: 14px; }
  .header-search input {
    font-size: 14px;
  }
  .header-search input::placeholder {
    /* Inter italic looks pinched at 13px — slightly larger and Fraunces
       keeps the editorial feel without losing legibility. */
    font-size: 13.5px;
  }
  /* Suggest dropdown can extend past the (narrow) input on mobile so it
     reaches the screen edges and shows full address lines. */
  .header-search .suggest-list {
    left: -8px; right: -8px;
    max-height: 60vh;
  }
}
/* Very narrow phones — make the placeholder shorter via :placeholder-shown
   workaround: shrink the input so the truncated placeholder still reads
   cleanly. Also drop the left padding a hair so the icon hugs the edge. */
@media (max-width: 380px) {
  .header-search {
    margin: 0 8px;
    padding-left: 32px;
  }
  .header-search .header-search-icon { left: 10px; }
  .header-search input { font-size: 13.5px; }
}
.nav-links a {
  color: inherit; text-decoration: none;
  padding: 8px 12px;
  transition: color 0.18s ease;
  background-image: none;
}
.nav-links a:hover { color: var(--ink); }
.nav-links a.active { color: var(--accent); }

.nav-toggle {
  display: none;
  background: var(--paper); border: 1px solid var(--rule);
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  color: var(--ink); cursor: pointer;
  margin-left: auto;
  transition: border-color 0.18s ease, color 0.18s ease;
}
.nav-toggle:hover { border-color: var(--accent); color: var(--accent); }
.nav-toggle .nav-toggle-bar { transition: transform 0.28s var(--ease-out), opacity 0.2s ease; transform-origin: center; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar-1 { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar-2 { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar-3 { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 760px) {
  .nav-toggle { display: inline-flex; }
  .site-header .nav-links {
    /* Anchor with top + bottom so the drawer always fills exactly the
       visible viewport (top of header to bottom of screen). This avoids
       the iOS Safari `100vh`-includes-URL-bar bug that was clipping the
       Sign in / Create free account buttons off the bottom — the old
       `max-height: calc(100vh - var(--header-h))` rendered a drawer
       taller than the visible area on iOS, with no scroll. */
    position: fixed;
    top: var(--header-h);
    left: 0;
    right: 0;
    bottom: 0;
    /* v2 May 2026: explicit z-index above the map (Leaflet uses up to
       z-index 800 internally). Without this, the open drawer rendered
       behind the UK coverage map on the homepage and coverage page. */
    z-index: 1001;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
    flex-direction: column; align-items: stretch;
    margin-left: 0; gap: 0;
    padding: 8px 18px 18px;
    /* Scrollable list — kicks in only if the items exceed the visible
       height (8 rows on a short phone). The safe-area-inset-bottom
       padding keeps the last item above the iOS home indicator. */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding-bottom: max(24px, env(safe-area-inset-bottom, 24px));
    transform: translateY(-12px); opacity: 0; pointer-events: none;
    transition: transform 0.28s var(--ease-out), opacity 0.22s ease;
  }
  .site-header .nav-links.open { transform: translateY(0); opacity: 1; pointer-events: auto; }
  .site-header .nav-links a {
    padding: 16px 12px; font-size: 14px;
    border-bottom: 1px solid var(--rule);
  }
  .site-header .nav-links a:last-child { border-bottom: none; }
  body.menu-open { overflow: hidden; }
}

/* =========================================================== FOOTER
   Density bump: bumped body/header/link sizes by +1px, trimmed vertical
   padding ~15% to compensate (denser, not taller), and added a strong
   1px rule top border so the footer separates cleanly from whatever
   editorial colophon precedes it on a content page. */
.site-footer {
  margin-top: 48px; padding: 30px 24px 20px;
  background: var(--paper);
  border-top: 1px solid var(--rule);
}
.trust-strip + .site-footer { margin-top: 0; }
.site-footer .inner {
  max-width: 1240px; margin: 0 auto;
  display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 24px;
}
.site-footer .colophon { max-width: 360px; }
.site-footer h4 { margin: 0 0 10px; }
.site-footer ul li { margin-bottom: 5px; }
.site-footer .legal { margin: 20px auto 0; padding-top: 14px; }
.site-footer h4 {
  font-family: var(--font-sans);
  /* v2 May 2026: bumped column headers 500 → 600 / 0.18em → 0.14em to
     match the section-eyebrow scale we lifted across the site. */
  font-weight: 600;
  font-size: 12px; color: var(--ink);
  letter-spacing: 0.14em; text-transform: uppercase;
  margin: 0 0 12px;
}
.site-footer ul { list-style: none; padding: 0; margin: 0; font-size: 15px; font-family: var(--font-sans); }
.site-footer ul li { margin-bottom: 7px; }
/* v2 May 2026: footer links use the same ink-with-opacity treatment as
   the TOC sidebar so the muted state still reads cleanly without going
   all the way to ink-soft (which was fading too far on light backgrounds). */
.site-footer ul a {
  /* v4 May 2026: dropped opacity:0.7 — even with the new warm --ink-soft
     it stacked into a perceived grey. Use the variable directly at full
     opacity, switch to --ink on hover for the lift. */
  color: var(--ink-soft);
  font-weight: 500;
  text-decoration: none;
  transition: color 0.15s ease;
  background-image: none;
}
.site-footer ul a:hover { color: var(--ink); }
.site-footer .colophon { font-size: 15px; color: var(--ink-soft); line-height: 1.6; max-width: 380px; }
.site-footer .legal {
  max-width: 1240px; margin: 40px auto 0;
  padding-top: 20px; border-top: 1px solid var(--rule);
  font-family: var(--font-sans);
  font-size: 13px; color: var(--ink-soft);
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 16px;
  align-items: center;
}
.site-footer .legal-links {
  display: inline-flex; gap: 6px; flex-wrap: wrap;
  align-items: center;
}
.site-footer .legal-links a {
  /* v4 May 2026: matches the footer ul a treatment — uses --ink-soft
     (now warm dark sepia) directly, no opacity stack. */
  color: var(--ink-soft);
  font-size: 13px; font-weight: 600;
  padding: 4px 10px;
  text-decoration: none;
  transition: color 0.15s ease;
  background-image: none;
}
.site-footer .legal-links a:hover { color: var(--ink); }

/* =========================================================== BUTTONS
   Two flavours: filled accent (primary CTA), and ghost (everything else).
   No transforms, no shadows. */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 13px 22px;
  font-family: var(--font-sans);
  font-size: 13px; font-weight: 500;
  letter-spacing: 0.10em; text-transform: uppercase;
  border-radius: 0;
  border: 1px solid transparent;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  min-height: 44px;
  background-image: none;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.btn:hover { text-decoration: none; }
.btn-primary {
  background: var(--accent); color: #FFF;
  border-color: var(--accent);
}
.btn-primary:hover {
  background: var(--accent-deep); border-color: var(--accent-deep);
  color: #FFF;
}
.btn-ghost {
  background: transparent; color: var(--ink);
  border-color: var(--ink);
}
.btn-ghost:hover {
  background: var(--ink); color: var(--paper);
}
.btn-amber {
  background: var(--accent); color: #FFF; border-color: var(--accent);
}
.btn-amber:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

/* =========================================================== INPUTS */
input, select, textarea {
  font: inherit; color: inherit;
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: 0; padding: 11px 14px;
  outline: none;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-glow);
}

/* =========================================================== LAYOUT */
.container { max-width: 1240px; margin: 0 auto; padding: 24px; }

/* Section heads — a single thin rule, an Inter eyebrow, a Fraunces H2,
   a quiet Fraunces lede. No icons (pulled out — they were SaaS chrome).
   Section numerals come in via [data-num] on the parent <section>. */
.section-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 24px; margin-bottom: 18px; padding-top: 32px;
  border-top: 1px solid var(--rule);
}
main > section:first-child .section-head,
.report-grid main > section:first-child .section-head {
  border-top: none; padding-top: 12px;
}

.section-head-left { max-width: 720px; }
.section-head-left h2 {
  /* v3: tighter tracking (-0.016 → -0.022em) + slightly bolder weight
     pulls the H2 closer to a contemporary editorial display setting.
     The Fraunces opsz axis at this size already firms up the strokes;
     the tighter spacing finishes it. */
  font-size: clamp(22px, 2.4vw, 30px);
  letter-spacing: -0.022em;
  font-weight: 500;
  display: flex; align-items: baseline; gap: 16px;
  margin-top: 4px;
}
.section-head-left .eyebrow { margin-bottom: 2px; }
/* v2 Apr 2026: section-head eyebrows ("SNAPSHOT", "CRIME & SAFETY",
   "PROPERTY VALUATION", etc.) read as full-fat section titles, not
   ambient chrome. Bumps weight to 600 and tightens letter-spacing so the
   kicker pairs cleanly with the Fraunces H2 below it. Only the
   section-head context — does not affect card-titles, trust-eyebrows,
   or other places where the same .eyebrow class is reused as a sub-label. */
.section-head-left .eyebrow,
.section-head-left .section-eyebrow {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  /* v3 May 2026: switched from ink to accent brick-red. Every section
     kicker ("CRIME & SAFETY", "PROPERTY VALUATION", etc.) now reads as
     a coloured chapter heading, giving the page rhythm and visual life
     without compromising the modernised body typography. */
  color: var(--accent);
}
.section-head-left p {
  /* v4 May 2026: dropped explicit Fraunces — now inherits Inter from body.
     Modern editorial body type reads sharper on cream than the serif's
     thin strokes did, even at the new high-contrast --ink-soft. Weight
     bumped 400 → 450 (Inter looks ever-so-slightly thin at 400 on cream). */
  font-size: 15.5px; color: var(--ink-soft);
  margin-top: 8px; max-width: 640px; line-height: 1.6;
  font-weight: 450;
}
/* Disclosure: a quieter sibling of the lede. Used where a section's data
   has a methodological caveat the user needs to see before reading the
   numbers (borough-aggregate, measurement-station distance, etc.). */
.section-head-left p.section-disclosure {
  font-size: 13.5px; line-height: 1.55;
  margin-top: 10px;
  padding-left: 12px;
  border-left: 2px solid var(--rule);
  color: var(--ink-soft);
}
.section-head-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-bottom: 4px; }

/* =========================================================== PRICING PAGE
   Three-card grid (Free / Pro / Pro Investor) with a monthly/annual
   toggle above. Editorial styling: cream paper, ink-on-cream cards
   with a brick-red accent on the recommended tier. */
.pricing-toggle {
  display: inline-flex;
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 4px;
  background: var(--paper);
}
.pricing-toggle-btn {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: transparent;
  border: 0;
  padding: 8px 18px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.pricing-toggle-btn.is-active {
  background: var(--ink);
  color: var(--paper);
}
.pricing-toggle-save {
  display: inline-block;
  margin-left: 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.04em;
}
.pricing-toggle-btn.is-active .pricing-toggle-save { color: var(--paper); opacity: 0.8; }

.pricing-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
  margin-top: 16px;
}
.pricing-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--rule);
  background: var(--paper);
  padding: 28px 26px;
  border-radius: 6px;
}
.pricing-card.is-recommended {
  border-color: var(--accent);
  border-width: 2px;
  padding: 27px 25px;
  background: color-mix(in srgb, var(--paper) 96%, var(--accent) 4%);
}
.pricing-badge {
  position: absolute;
  top: -12px;
  left: 24px;
  background: var(--accent);
  color: var(--paper);
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 4px 12px;
  border-radius: 4px;
}
.pricing-name {
  font-family: var(--font-display);
  font-size: 26px;
  margin: 8px 0 14px;
  letter-spacing: -0.01em;
}
.pricing-price {
  margin: 0 0 8px;
  font-family: var(--font-display);
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.pricing-amount {
  font-size: 44px;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1;
}
.pricing-period {
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--ink-soft);
  font-weight: 500;
}
.pricing-tagline {
  font-size: 14.5px;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 0 0 22px;
  min-height: 44px;
}
.pricing-cta {
  width: 100%;
  text-align: center;
  margin-bottom: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.pricing-cta:disabled { opacity: 0.6; cursor: not-allowed; }
.pricing-features {
  list-style: none;
  margin: 0;
  padding: 18px 0 0;
  border-top: 1px solid var(--rule);
}
.pricing-features li {
  font-size: 14px;
  color: var(--ink);
  line-height: 1.5;
  padding: 8px 0 8px 22px;
  position: relative;
}
.pricing-features li::before {
  content: "✓";
  position: absolute;
  left: 0;
  top: 8px;
  color: var(--accent);
  font-weight: 700;
  font-size: 13px;
}
.pricing-features li strong { font-weight: 600; }
/* "Coming soon" feature rows — visually muted so users can scan what's
   live today vs. what's on the near-term roadmap. The "Coming June" /
   "Coming July" / "Coming August" pill sits inline at the end of the
   row so it doesn't look like an asterisk-style buried disclaimer. */
.pricing-features li.is-coming-soon {
  color: var(--ink-soft);
  opacity: 0.85;
}
.pricing-features li.is-coming-soon::before {
  content: "○";  /* hollow circle replaces the checkmark */
  color: var(--ink-soft);
  font-weight: 400;
}
.pricing-features li.is-coming-soon span {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: 999px;
  vertical-align: middle;
  white-space: nowrap;
}

/* v3 May 2026: tightened vertical rhythm. Previous 48px/60px margins
   left a large gap between the last paragraph and the site footer
   that read as dead space. */
.pricing-fineprint {
  margin: 28px 0 20px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
  font-size: 14px;
  color: var(--ink-soft);
  line-height: 1.55;
}
.pricing-fineprint p { margin: 0 0 10px; max-width: 820px; }
.pricing-fineprint p:last-child { margin-bottom: 0; }
.pricing-fineprint a { color: var(--accent); }

@media (max-width: 900px) {
  .pricing-grid { grid-template-columns: 1fr; gap: 16px; }
  .pricing-card { padding: 24px 22px; }
  .pricing-amount { font-size: 36px; }
}

/* =========================================================== COVERAGE STATS
   2x2 grid of headline stats sitting in the homepage Coverage section's
   right column. Editorial: big Fraunces numerals, small uppercase labels
   in Inter — same hierarchy as the snapshot tiles on a postcode page. */
.coverage-stats-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px 28px;
  margin-bottom: 12px;
  width: 100%;
  max-width: 360px;
}
.coverage-stat-num {
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.022em;
  color: var(--ink);
}
.coverage-stat-label {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-top: 4px;
}
@media (max-width: 720px) {
  .coverage-stats-grid {
    grid-template-columns: repeat(2, 1fr);
    max-width: none;
    gap: 12px 20px;
  }
  .coverage-stat-num { font-size: 26px; }
}


/* Section numerals — driven by data-num on each <section>. Roman or
   Arabic, set in ink-soft Fraunces, sitting in the gutter to the left
   of the H2 on desktop, inline above on mobile. */
section[data-num] .section-head-left { position: relative; }
section[data-num] .section-head-left::before {
  content: attr(data-num);
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 17px;
  color: var(--ink-soft);
  letter-spacing: 0.04em;
  margin-bottom: 4px;
}
@media (min-width: 1080px) {
  section[data-num] .section-head-left::before {
    position: absolute;
    top: 2px; left: -54px;
    margin-bottom: 0;
    font-size: 22px;
  }
}

/* The legacy section-icon slot inside an H2. Was a coloured square; now
   collapsed to nothing so existing markup doesn't leave a void. */
.section-icon { display: none; }

/* Source-tag pill on the right of section heads. Now a faint Inter
   smallcaps label with a leading bullet, no card chrome. */
.src-tag {
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-soft);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0; background: transparent; border: none;
  border-radius: 0;
}
.src-tag::before {
  content: ""; width: 4px; height: 4px; border-radius: 50%;
  background: var(--accent); flex-shrink: 0;
}
/* Linkified source tokens — keep the editorial micro-cap feel but
   signal interactivity via underline-on-hover and an accent shift.
   Box-shadow trick draws an underline that hugs the baseline rather
   than stretching the row height. External-link arrow appears on
   hover/focus so the affordance is unambiguous without cluttering
   the resting state. */
.src-tag-link {
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 1px dotted color-mix(in srgb, var(--ink-soft) 70%, transparent);
  padding-bottom: 1px;
  transition: color 0.15s ease, border-color 0.15s ease;
  position: relative;
}
.src-tag-link:hover,
.src-tag-link:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
  outline: none;
}
.src-tag-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 1px;
}
.src-tag-link::after {
  content: "↗";
  display: inline-block;
  margin-left: 3px;
  font-size: 0.85em;
  opacity: 0;
  transform: translateY(-1px);
  transition: opacity 0.15s ease;
}
.src-tag-link:hover::after,
.src-tag-link:focus-visible::after {
  opacity: 1;
}

/* ─── Enhanced src-tag (freshness + confidence chip) ──────────────
   Applied dynamically via _renderFreshnessChips() in postcode.js.
   Visually swaps the static smallcaps src-tag for a button-style pill
   with a colour-coded confidence dot, source name, and short refresh
   label. Click → freshness popover (below). Same horizontal slot in
   the section header — no page-weight growth. */
.src-tag-enhanced {
  /* Override the base .src-tag uppercase styling */
  text-transform: none;
  letter-spacing: 0.01em;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-soft);
  padding: 4px 10px 4px 8px;
  background: var(--paper-tinted);
  border: 1px solid var(--rule);
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 120ms ease, color 120ms ease, background 120ms ease, transform 120ms ease;
  user-select: none;
  /* gap inherited from .src-tag */
}
.src-tag-enhanced:hover {
  border-color: var(--accent);
  color: var(--ink);
  background: var(--paper);
  transform: translateY(-1px);
}
.src-tag-enhanced:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: var(--accent);
  color: var(--ink);
}
/* Hide the original ::before bullet — the explicit dot span replaces it */
.src-tag-enhanced::before { display: none; }

.src-tag-enhanced .src-tag-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-block;
}
.src-tag-dot-high     { background: #1F7A3D; }
.src-tag-dot-medium   { background: #C69B3F; }
.src-tag-dot-modelled { background: var(--ink-soft); opacity: 0.55; }
@media (prefers-color-scheme: dark) {
  /* Brighter greens/ambers for 3:1 contrast against the dark paper.
     Modelled dot uses --ink-soft which already swaps via the dark-mode
     :root override, so no separate rule needed. */
  .src-tag-dot-high   { background: #4FB070; }
  .src-tag-dot-medium { background: #E8B85A; }
}

.src-tag-enhanced .src-tag-source {
  font-weight: 600;
  color: var(--ink);
}
.src-tag-enhanced .src-tag-sep {
  opacity: 0.35;
  font-weight: 700;
}
.src-tag-enhanced .src-tag-refresh {
  color: var(--ink-soft);
}

@media (max-width: 600px) {
  .src-tag-enhanced {
    font-size: 10.5px;
    padding: 3px 9px 3px 7px;
    gap: 5px;
  }
  .src-tag-enhanced .src-tag-dot { width: 6px; height: 6px; }
}

/* ─── Freshness popover ───────────────────────────────────────────
   Opens on click of any .src-tag-enhanced chip. Desktop: anchored
   below + right-aligned to the chip. Mobile: full-width bottom sheet
   that respects safe-area insets. Only one open at a time. */
.freshness-popover {
  position: absolute;
  z-index: 9000;
  width: 320px;
  max-width: calc(100vw - 24px);
  max-height: 70vh;
  overflow-y: auto;
  background: var(--paper);
  border: 1px solid var(--ink);
  box-shadow: 0 14px 36px rgba(0,0,0,0.15);
  padding: 18px 22px 16px;
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink);
  animation: fresh-pop-in 180ms ease;
}
@keyframes fresh-pop-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.freshness-popover-close {
  position: absolute;
  top: 8px; right: 10px;
  width: 28px; height: 28px;
  background: transparent;
  border: none;
  font-size: 22px;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 0;
  font-family: var(--font-sans);
  border-radius: 4px;
}
.freshness-popover-close:hover { color: var(--accent); background: var(--paper-tinted); }
.freshness-popover-title {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  margin: 0 28px 8px 0;
  letter-spacing: -0.005em;
  line-height: 1.3;
}
.freshness-popover-tier {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-soft);
  background: var(--paper-tinted);
  padding: 5px 11px 4px 9px;
  border-radius: 999px;
  margin: 0 0 14px;
}
.freshness-popover-tier .src-tag-dot {
  width: 8px; height: 8px; border-radius: 50%;
  display: inline-block; flex-shrink: 0;
}
.freshness-popover-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 7px 14px;
  margin: 0 0 14px;
  padding: 0;
}
.freshness-popover-grid dt {
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0;
  align-self: center;
}
.freshness-popover-grid dd {
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
  margin: 0;
  align-self: center;
}
.freshness-popover-note {
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-soft);
  margin: 0 0 14px;
}
.freshness-popover-link {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 700;
  color: var(--accent);
  text-decoration: none;
  letter-spacing: -0.005em;
  border-bottom: 1px dotted var(--accent);
  padding-bottom: 1px;
}
.freshness-popover-link:hover {
  border-bottom-style: solid;
}

/* Mobile bottom-sheet variant — fixed at bottom of viewport, full width,
   respects safe-area-inset for notched phones, drag-handle visual on top */
.freshness-popover.is-mobile {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: auto !important;
  width: 100%;
  max-width: 100%;
  max-height: 80vh;
  overflow-y: auto;
  border: none;
  border-top: 1px solid var(--ink);
  border-radius: 14px 14px 0 0;
  padding: 22px 22px 28px;
  padding-bottom: max(28px, env(safe-area-inset-bottom, 20px));
  box-shadow: 0 -8px 28px rgba(0,0,0,0.18);
  z-index: 10000;
  animation: fresh-pop-up 220ms ease;
}
@keyframes fresh-pop-up {
  from { transform: translateY(24px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
.freshness-popover.is-mobile::before {
  content: "";
  display: block;
  width: 40px;
  height: 4px;
  background: var(--rule);
  border-radius: 2px;
  margin: -10px auto 14px;
}
.freshness-popover.is-mobile .freshness-popover-close {
  top: 14px; right: 16px;
  width: 32px; height: 32px;
  font-size: 24px;
}

/* ─── Catchment proximity callout (schools section) ────────────────
   Sits above the full schools table. Shows the 3 nearest primaries and
   3 nearest secondaries with distance-band labels (NOT admission odds —
   we don't have admissions data and won't invent it). The caveat block
   names the four factors real catchment depends on. */
.schools-catchment { margin: 0 0 20px; }
.catchment-callout {
  background: var(--paper-tinted);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--accent);
  padding: 16px 20px 18px;
}
.catchment-callout-head {
  margin-bottom: 14px;
}
.catchment-callout-head .eyebrow {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.catchment-callout-title {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
  margin: 4px 0 0;
  letter-spacing: -0.005em;
}
.catchment-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-bottom: 14px;
}
.catchment-col-head {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}
.catchment-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.catchment-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
}
.catchment-row:last-child { border-bottom: none; }
.catchment-row-main { min-width: 0; flex: 1; }
.catchment-row-name {
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
}
.catchment-row-meta {
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--ink-soft);
  margin-top: 2px;
}
.catchment-row-dist {
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-weight: 500;
}
.catchment-row-sep { opacity: 0.4; margin: 0 4px; }
.catchment-tier {
  flex-shrink: 0;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 4px 9px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink-soft);
  white-space: nowrap;
}
.catchment-tier-very-near  { border-color: #1F7A3D; color: #1F7A3D; }
.catchment-tier-near       { border-color: #1F7A3D; color: #1F7A3D; opacity: 0.85; }
.catchment-tier-reasonable { border-color: #C69B3F; color: #B07D1C; }
.catchment-tier-far        { border-color: var(--ink-soft); color: var(--ink-soft); }

.catchment-empty {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--ink-soft);
  margin: 4px 0 0;
  line-height: 1.5;
  font-style: italic;
}

.catchment-caveat {
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--ink-soft);
  padding-top: 14px;
  border-top: 1px dashed var(--rule);
}
.catchment-caveat strong { color: var(--ink); font-weight: 700; }
.catchment-caveat em { font-style: italic; color: var(--ink); }

@media (max-width: 720px) {
  .catchment-callout { padding: 14px 16px 16px; }
  .catchment-cols { grid-template-columns: 1fr; gap: 16px; }
  .catchment-callout-title { font-size: 18px; }
  .catchment-row { padding: 8px 0; }
  .catchment-row-name { font-size: 13px; }
  .catchment-tier { font-size: 10.5px; padding: 3px 8px; }
}

/* ─── Time Machine (direction-of-travel timeline) ──────────────────
   Currently only appears under the IMD section of the area card when
   imd_history has rows for this LSOA. Future sections (council tax,
   crime, schools) can reuse the same .ts-tm-* primitives — they're
   designed generically. */
.ts-tm {
  margin: 22px 0 0;
  padding: 16px 18px 14px;
  background: var(--paper-tinted, #EFEAE0);
  border-left: 3px solid var(--ink-soft);
}
.ts-tm-up    { border-left-color: #1F7A3D; }
.ts-tm-down  { border-left-color: var(--accent); }
.ts-tm-stable { border-left-color: var(--ink-soft); }

.ts-tm-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 12px;
}
.ts-tm-eyebrow {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.ts-tm-title {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.005em;
}

.ts-tm-line {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 4px 0 14px;
  flex-wrap: wrap;
}
.ts-tm-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 50px;
}
.ts-tm-year {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
}
.ts-tm-dot {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--paper);
  border: 1.5px solid var(--rule);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
}
.ts-tm-dot-current {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
}
/* Money-dot variant — wider pill instead of a circle, because £1,234 doesn't fit in a 38px circle */
.ts-tm-dot-money {
  width: auto;
  min-width: 56px;
  height: 30px;
  padding: 0 10px;
  border-radius: 999px;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.ts-tm-arrow {
  font-size: 18px;
  color: var(--ink-soft);
  opacity: 0.5;
}

.ts-tm-verdict {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink);
  margin: 0 0 8px;
  letter-spacing: -0.005em;
}
.ts-tm-verdict strong { font-weight: 700; color: var(--ink); }
.ts-tm-caveat {
  font-family: var(--font-sans);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-soft);
  margin: 0;
}

@media (max-width: 600px) {
  .ts-tm { padding: 14px 14px 12px; margin-top: 16px; }
  .ts-tm-line { gap: 6px; margin-bottom: 10px; }
  .ts-tm-step { min-width: 42px; }
  .ts-tm-dot { width: 32px; height: 32px; font-size: 14px; }
  .ts-tm-arrow { font-size: 14px; }
  .ts-tm-verdict { font-size: 13.5px; }
  .ts-tm-caveat { font-size: 11px; }
}

/* ─── Cross-section / cross-row reasoning reveal ────────────────────
   Shared between the postcode page (Quick Read → "Run deep analysis")
   and the compare page (cross-row insights). Same .qr-deeper-* +
   .qr-insight-* class names so the markup is identical on both pages.
   Was originally inline in postcode.js; lifted out so compare.html
   doesn't have to load postcode.js to inherit the styles. */
.qr-deeper {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
}
.qr-deeper-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px 9px 14px;
  border: 1.5px solid var(--accent);
  background: transparent;
  color: var(--accent);
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: -0.005em;
  border-radius: 999px;
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease, transform 150ms ease;
}
.qr-deeper-btn:hover { background: var(--accent); color: var(--paper); }
.qr-deeper-btn.is-open { background: var(--accent); color: var(--paper); }
.qr-deeper-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.qr-deeper-btn-icon { font-size: 14px; line-height: 1; }
.qr-deeper-btn-arrow {
  transition: transform 200ms ease;
  display: inline-block;
  font-weight: 800;
}
.qr-deeper-btn.is-open .qr-deeper-btn-arrow { transform: rotate(90deg); }

.qr-deeper-panel {
  margin-top: 16px;
  animation: qr-deeper-slide 260ms ease;
}
@keyframes qr-deeper-slide {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.qr-deeper-intro {
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-soft);
  padding: 12px 14px;
  background: var(--paper-tinted, #EFEAE0);
  border-left: 3px solid var(--accent);
  margin-bottom: 14px;
}
.qr-deeper-intro strong { color: var(--ink); font-weight: 700; }

.qr-deeper-empty {
  font-family: var(--font-sans);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-soft);
  padding: 14px 16px;
  background: var(--paper);
  border: 1px dashed var(--rule);
}

.qr-insights {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* <details>-based insight card. The <summary> is the always-visible
   header (icon + severity + title + chevron). The .qr-insight-content
   wraps body + jumps; native <details> hides it when closed. */
.qr-insight {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-left-width: 4px;
  padding: 0;
  overflow: hidden;
}
.qr-insight-concern  { border-left-color: var(--accent); }
.qr-insight-tradeoff { border-left-color: #C69B3F; }
.qr-insight-positive { border-left-color: #1F7A3D; }

/* Strip the default disclosure triangle — we render our own chevron */
.qr-insight > summary { list-style: none; cursor: pointer; }
.qr-insight > summary::-webkit-details-marker { display: none; }
.qr-insight-toggle {
  margin-left: auto;
  font-size: 14px;
  color: var(--ink-soft);
  transition: transform 160ms ease;
  flex-shrink: 0;
}
.qr-insight[open] .qr-insight-toggle { transform: rotate(180deg); }

.qr-insight-head {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  padding: 14px 16px 12px;
}
.qr-insight[open] .qr-insight-head { padding-bottom: 8px; }
.qr-insight-content { padding: 0 16px 14px; }
.qr-insight-icon {
  font-size: 14px;
  line-height: 1;
  font-weight: 700;
}
.qr-insight-concern  .qr-insight-icon { color: var(--accent); }
.qr-insight-tradeoff .qr-insight-icon { color: #C69B3F; }
.qr-insight-positive .qr-insight-icon { color: #1F7A3D; }
.qr-insight-sev {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.qr-insight-concern  .qr-insight-sev { color: var(--accent); }
.qr-insight-tradeoff .qr-insight-sev { color: #B07D1C; }
.qr-insight-positive .qr-insight-sev { color: #1F7A3D; }
.qr-insight-title {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--ink);
  margin: 0;
  letter-spacing: -0.005em;
  flex: 1 1 0;
  min-width: 0;
}
.qr-insight-body {
  font-family: var(--font-sans);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink);
  margin: 0;
}
.qr-insight-body em { font-style: italic; color: var(--ink-soft); }
.qr-insight-body strong { font-weight: 700; }

.qr-insight-jumps {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 12px;
}
.qr-insight-jump {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  padding: 4px 10px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.qr-insight-jump:hover {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
}

@media (max-width: 600px) {
  .qr-deeper { margin-top: 12px; padding-top: 12px; }
  .qr-deeper-btn { padding: 8px 14px; font-size: 12.5px; }
  .qr-deeper-intro { font-size: 12px; padding: 10px 12px; margin-bottom: 10px; }
  .qr-insights { gap: 8px; }
  /* On mobile, push the title to its own full-width row so it doesn't
     get squeezed into a narrow column next to the severity tag. The
     summary row becomes:
       Row 1: [icon] [SEV BADGE] ............... [▾ chevron]
       Row 2: Title (full width, properly readable)              */
  .qr-insight-head {
    padding: 10px 12px;
    gap: 7px;
    flex-wrap: wrap;
  }
  .qr-insight[open] .qr-insight-head { padding-bottom: 4px; }
  .qr-insight-content { padding: 0 12px 11px; }
  .qr-insight-icon { flex: 0 0 auto; }
  .qr-insight-sev  { flex: 0 0 auto; font-size: 9.5px; letter-spacing: 0.12em; }
  .qr-insight-toggle { margin-left: auto; flex: 0 0 auto; }
  .qr-insight-title {
    /* flex-basis: 100% forces wrap to a new line in this wrap container */
    flex: 1 1 100%;
    font-size: 14.5px;
    line-height: 1.3;
    margin-top: 4px;
  }
  .qr-insight-body  { font-size: 12px; line-height: 1.5; }
  .qr-insight-jumps { gap: 5px; margin-top: 8px; }
  .qr-insight-jump  { font-size: 10.5px; padding: 3px 8px; }
  .qr-insight-icon  { font-size: 12px; }
}

/* On the compare page the reveal sits between the grid and the foot —
   give it a bit more breathing room than the postcode-page placement. */
.cmp-deeper { margin: 22px 0 6px; }

/* ─── Homepage Compare CTA (between hero and trust strip) ──────────
   Secondary action promoting /compare. Two-column on desktop (text +
   button), stacked on mobile. Quiet card, accent border-left so it
   reads as editorial, not marketing. */
.home-compare-cta {
  padding: 28px 0;
  background: var(--paper-tinted);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.home-compare-cta .inner {
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 24px;
}
.hcc-content {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 32px;
  align-items: center;
}
.hcc-text { min-width: 0; }
.hcc-eyebrow {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.hcc-title {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 700;
  color: var(--ink);
  margin: 6px 0 8px;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.hcc-lede {
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-soft);
  margin: 0;
  max-width: 560px;
}
.hcc-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
}
.hcc-btn-primary {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 700;
  padding: 12px 22px;
  background: var(--accent);
  color: var(--paper);
  text-decoration: none;
  letter-spacing: 0.01em;
  border-radius: 0;
  transition: background 120ms ease;
}
.hcc-btn-primary:hover { background: var(--ink); }
.hcc-examples {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  align-items: baseline;
  font-family: var(--font-sans);
  font-size: 12px;
}
.hcc-examples-label {
  color: var(--ink-soft);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 10.5px;
}
.hcc-example {
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px dotted var(--accent);
  padding-bottom: 1px;
  letter-spacing: 0.02em;
}
.hcc-example:hover { border-bottom-style: solid; }

@media (max-width: 720px) {
  .home-compare-cta { padding: 22px 0; }
  .home-compare-cta .inner { padding: 0 18px; }
  .hcc-content { grid-template-columns: 1fr; gap: 16px; }
  .hcc-title { font-size: 22px; }
  .hcc-lede { font-size: 13.5px; }
  .hcc-btn-primary { padding: 11px 20px; font-size: 14px; width: 100%; justify-content: center; }
}

/* Postcode page hero subline — make the borough name a link to its hub.
   Underline-on-hover only; keeps the visual restraint of the hero. */
.hero-borough-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted color-mix(in srgb, currentColor 45%, transparent);
  padding-bottom: 1px;
  transition: color 120ms ease, border-bottom-color 120ms ease;
}
.hero-borough-link:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Coverage list — borough names as links to /borough/[slug] hubs */
.coverage-borough-link {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 1px;
  font-weight: 600;
}
.coverage-borough-link:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* ─── Borough hub page (/borough/wandsworth etc.) ──────────────────
   Data-driven page rendered by render-borough.js. Sits between Coverage
   and the postcode report in the site architecture, concentrating
   PageRank on ~150 thematic pages so Google has a manageable surface
   to actually index. */
.borough-stats { margin: 0 0 36px; }
.borough-stats-empty {
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-soft);
  padding: 22px;
  background: var(--paper-tinted);
  border: 1px dashed var(--rule);
  text-align: center;
}
.borough-stats-empty code {
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  background: var(--paper);
  padding: 2px 6px;
  border: 1px solid var(--rule);
}
.borough-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
}
.bstat {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--accent);
  padding: 16px 18px 14px;
}
.bstat-label {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}
.bstat-value {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.bstat-unit {
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
  margin-left: 6px;
  letter-spacing: 0;
}
.bstat-source {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 12px;
  color: var(--ink-soft);
  margin-top: 6px;
}

.borough-section-title {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
  margin: 0 0 8px;
}
.borough-section-lede {
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-soft);
  margin: 0 0 18px;
  max-width: 720px;
}

.borough-postcodes { margin: 36px 0; }
.borough-postcode-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 6px;
}
.borough-postcode-link {
  display: block;
  padding: 10px 14px;
  background: var(--paper);
  border: 1px solid var(--rule);
  color: var(--ink);
  text-decoration: none;
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: border-color 120ms ease, transform 120ms ease;
}
.borough-postcode-link:hover {
  border-color: var(--accent);
  color: var(--accent);
  transform: translateY(-1px);
}

.borough-neighbours { margin: 36px 0; }
.borough-neighbour-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.borough-neighbour-list a {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  padding: 7px 14px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--accent);
  text-decoration: none;
}
.borough-neighbour-list a:hover {
  background: var(--accent);
  color: var(--paper);
  border-color: var(--accent);
}

.borough-seo-body {
  margin: 36px 0 24px;
  padding-top: 28px;
  border-top: 1px solid var(--rule);
}
.borough-seo-lede {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--ink-soft);
  margin: 0;
  max-width: 760px;
}

@media (max-width: 720px) {
  .bstat { padding: 14px 16px 12px; }
  .bstat-value { font-size: 22px; }
  .borough-section-title { font-size: 20px; }
  .borough-postcode-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 5px; }
  .borough-postcode-link { font-size: 13px; padding: 8px 12px; }
}

/* ─── Inline compare CTA on the postcode report ─────────────────────
   Sits between Truely Twins and EPC. Converts every report view into
   a compare-page entry point. Visually feels like an action card, not
   a content card — accent border, paper-tinted background, no .section-head
   structure since it's a single inline action. */
.compare-cta-section {
  margin: 28px 0 28px;
}
.compare-cta-card {
  background: var(--paper-tinted);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--accent);
}
.compare-cta-content {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 28px;
  align-items: end;
}
.compare-cta-text { min-width: 0; }
.compare-cta-text .eyebrow {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.compare-cta-title {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  color: var(--ink);
  margin: 6px 0 6px;
  letter-spacing: -0.005em;
  line-height: 1.25;
}
.compare-cta-lede {
  font-family: var(--font-sans);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin: 0;
  max-width: 560px;
}
.compare-cta-form {
  display: flex;
  gap: 10px;
  align-items: stretch;
}
.compare-cta-input-wrap {
  position: relative;
  min-width: 220px;
}
.compare-cta-input {
  width: 100%;
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 16px;
  font-weight: 600;
  padding: 11px 14px;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
  letter-spacing: 0.02em;
  box-sizing: border-box;
  border-radius: 0;
}
.compare-cta-input:focus { outline: 2px solid var(--accent); outline-offset: 0; }
.compare-cta-btn {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  padding: 11px 22px;
  background: var(--accent);
  color: var(--paper);
  border: none;
  cursor: pointer;
  letter-spacing: 0.02em;
  white-space: nowrap;
  transition: background 120ms ease;
}
.compare-cta-btn:hover { background: var(--ink); }

/* Autocomplete dropdown — anchored to the input wrap */
.compare-cta-input-wrap .suggest-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 30;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-top: none;
  max-height: 300px;
  overflow-y: auto;
  box-shadow: 0 8px 20px rgba(0,0,0,0.08);
}
.compare-cta-input-wrap .suggest-list.empty { display: none; }
.compare-cta-input-wrap .suggest-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  padding: 10px 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--rule);
  font-family: var(--font-sans);
}
.compare-cta-input-wrap .suggest-item:last-child { border-bottom: none; }
.compare-cta-input-wrap .suggest-item:hover,
.compare-cta-input-wrap .suggest-item.active {
  background: var(--paper-tinted);
}
.compare-cta-input-wrap .suggest-item .pc {
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: 0.02em;
}
.compare-cta-input-wrap .suggest-item .meta {
  font-size: 12px;
  color: var(--ink-soft);
}

@media (max-width: 720px) {
  .compare-cta-content { grid-template-columns: 1fr; gap: 18px; }
  .compare-cta-form { flex-direction: column; gap: 8px; }
  .compare-cta-input-wrap { min-width: 0; }
  .compare-cta-btn { padding: 12px 22px; }
  .compare-cta-title { font-size: 19px; }
  .compare-cta-lede { font-size: 13px; }
}

/* =========================================================== CARDS
   In an editorial system, "cards" are mostly thin-rule surfaces flush
   with the paper. Padding survives where it's structural; shadows and
   rounded corners do not. */
.card {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0; padding: 18px 20px;
  transition: border-color 0.2s ease;
}
.card.padded-lg { padding: 22px 24px; }
.card.flush { padding: 0; overflow: hidden; }
.card.hoverable:hover { border-color: var(--ink); }
.card-title { margin: 0 0 12px; }

/* =========================================================== GRIDS */
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; }
@media (max-width: 980px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 720px) { .grid-2, .grid-3 { grid-template-columns: 1fr; } }

.grid-2, .grid-3, .grid-4 { align-items: stretch; }
.grid-2 > .card, .grid-3 > .card, .grid-4 > .card,
.grid-2 > .stat-card, .grid-3 > .stat-card, .grid-4 > .stat-card {
  display: flex; flex-direction: column;
}
/* Inside a grid-2/3 card, let chart-wrap and bar-list grow to consume the
   trailing space so the shorter card no longer leaves a stranded gap when
   its sibling is taller. The cap on .chart-wrap (max-height: 220px) only
   applies in the standalone case; when the card is the shorter sibling
   in an equal-height row, we want it to fill. */
@media (min-width: 721px) {
  .grid-2 > .card > .chart-wrap,
  .grid-3 > .card > .chart-wrap {
    flex: 1 1 auto;
    max-height: none;
    min-height: 200px;
  }
  /* Also let bar-row lists stretch so they don't leave dead space below. */
  .grid-2 > .card > #crime-bars,
  .grid-2 > .card > #council-tax-distribution,
  .grid-2 > .card > #broadband-distribution,
  .grid-2 > .card > #mobile-distribution,
  .grid-2 > .card > #air-quality-distribution { flex: 1 1 auto; }
}

/* =========================================================== BADGES
   Small tracked-out Inter labels. Editorial uses these very sparingly
   (party affiliation, Ofsted rating, status). One muted weight, one
   accent weight, one neutral. */
.badge {
  display: inline-block;
  padding: 3px 8px;
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  border-radius: 0;
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--ink);
}
.badge-good    { color: var(--ink); border-color: var(--ink); }
.badge-warn    { color: var(--ink-soft); border-color: var(--rule); }
.badge-bad     { color: var(--accent); border-color: var(--accent); }
.badge-info    { color: var(--ink); border-color: var(--rule); }
.badge-neutral { color: var(--ink-soft); border-color: var(--rule); }
.badge-amber   { color: var(--accent); border-color: var(--accent); }

/* =========================================================== STAT CARDS
   The "huge number" tile used on the report snapshot quartet. Slim
   padding, thin rule borders (no shadows), 80px Inter tabular-figure
   number, eyebrow above, hairline-divided sub-line below. */
.stat-card {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-right: none;
  border-radius: 0;
  padding: 24px 28px 22px;
  display: flex; flex-direction: column;
  min-height: 220px;
  transition: background 0.18s ease;
}
.grid-4 > .stat-card:last-child { border-right: 1px solid var(--rule); }
.stat-card:hover { background: var(--paper-tinted); }
.stat-card .stat-label {
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.18em; text-transform: uppercase;
  margin: 0 0 18px;
}
.stat-card .stat-num {
  font-family: var(--font-sans);
  font-size: clamp(56px, 6.4vw, 80px);
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.04em; line-height: 0.95;
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum", "lnum";
  margin-top: auto;
}
.stat-card .stat-num .unit {
  font-size: 0.32em; font-weight: 400; color: var(--ink-soft);
  margin-left: 4px; letter-spacing: -0.01em;
}
.stat-card .stat-sub {
  font-family: var(--font-display);
  font-size: 14px; color: var(--ink-soft);
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--rule);
  line-height: 1.55;
  font-style: italic;
}
.stat-card .pending-note {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 12px;
  font-family: var(--font-sans);
  font-size: 10px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.16em; text-transform: uppercase;
}
.stat-card .pending-note svg { stroke: var(--ink-soft); }

/* Empty explainer — quieter than pending-note. Used where 0 is a
   legitimate permanent answer. */
.stat-card .empty-explainer,
.headline-card .empty-explainer,
.empty-explainer {
  margin-top: 12px;
  font-family: var(--font-display); font-style: italic;
  font-size: 13.5px; color: var(--ink-soft);
  line-height: 1.55;
  letter-spacing: 0;
  text-transform: none;
}

/* Legacy headline-card alias (still used in some markup). */
.headline-card {
  position: relative;
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: 0; padding: 24px 28px 22px;
  display: flex; flex-direction: column;
  min-height: 220px;
}
.headline-card .hc-label {
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.18em; text-transform: uppercase;
  margin: 0 0 18px;
}
.headline-card .hc-num {
  font-family: var(--font-sans);
  font-size: clamp(56px, 6.4vw, 80px); font-weight: 500;
  color: var(--ink); letter-spacing: -0.04em; line-height: 0.95;
  font-variant-numeric: tabular-nums lining-nums;
  margin-top: auto;
}
.headline-card .hc-num .unit {
  font-size: 0.32em; font-weight: 400; color: var(--ink-soft);
  margin-left: 4px;
}
.headline-card .hc-sub {
  font-family: var(--font-display);
  font-size: 14px; color: var(--ink-soft);
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--rule);
  line-height: 1.55; font-style: italic;
}
.pending-note {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 12px;
  font-family: var(--font-sans);
  font-size: 10px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.16em; text-transform: uppercase;
}
.pending-note svg { stroke: var(--ink-soft); }
.error-note {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 12px;
  font-family: var(--font-sans);
  font-size: 10px; font-weight: 500;
  color: var(--accent);
  letter-spacing: 0.16em; text-transform: uppercase;
}
.error-note svg { stroke: var(--accent); }

/* Standalone hc-* (used outside a headline-card by area-character) */
.hc-label {
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.18em; text-transform: uppercase;
  margin-bottom: 14px;
}
.hc-num {
  font-family: var(--font-sans);
  font-size: clamp(48px, 5vw, 72px); font-weight: 500;
  color: var(--ink); letter-spacing: -0.04em; line-height: 0.95;
  font-variant-numeric: tabular-nums lining-nums;
}
.hc-num .unit {
  font-size: 0.36em; font-weight: 400; color: var(--ink-soft);
  margin-left: 4px;
}
.hc-sub {
  font-family: var(--font-display); font-style: italic;
  font-size: 14px; color: var(--ink-soft);
  margin-top: 12px; line-height: 1.55;
}

/* =========================================================== LEADERBOARD
   The above-the-fold "what kind of postcode is this?" answer. Sixteen
   tiles in 4×4, no card chrome — thin rule borders only. Each tile:
   small-caps Inter label, Fraunces value, italic sub. Rows: Identity,
   Headline, Living Here, Value. Editorial dashboard, not SaaS. */
.leaderboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  margin-bottom: 24px;
  border: 1px solid var(--rule);
  background: var(--paper);
}
.leaderboard .lb-tile {
  position: relative;
  padding: 14px 16px 16px;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: flex; flex-direction: column; gap: 4px;
  min-height: 80px;
  transition: background 0.15s ease;
}
/* Right-edge of every row, bottom row.
   v3 (May 2026): the previous :nth-child(4n) selector miscounted the
   modular position because .lb-row-eyebrow rows are also children of
   .leaderboard, shifting the count by one per eyebrow. The visible
   symptom was the divider missing between Council Tax and Gigabit
   (Living Here row tile 1 instead of tile 4). Use a sibling chain
   anchored to the eyebrow so the rule fires on the true 4th column. */
.leaderboard .lb-row-eyebrow + .lb-tile + .lb-tile + .lb-tile + .lb-tile {
  border-right: none;
}
.leaderboard .lb-tile:nth-last-child(-n+4) { border-bottom: none; }
.leaderboard .lb-tile:hover { background: var(--paper-tinted); }

/* ---- Tile click-through to its matching section ---------------------
   Tiles with data-jump-to behave like links: pointer cursor, focus ring,
   nudge on hover, and a small chevron in the top-right corner. The
   chevron itself gains accent colour on hover so the affordance is
   readable even when the tile background tint is subtle. JS in
   postcode-inline.js wires click + Enter/Space to scroll the matching
   #section into view. */
.leaderboard .lb-tile[data-jump-to] {
  cursor: pointer;
  outline: none;
}
.leaderboard .lb-tile[data-jump-to]:focus-visible {
  background: var(--paper-tinted);
  box-shadow: inset 0 0 0 2px var(--accent);
}
.leaderboard .lb-tile[data-jump-to]:active {
  background: color-mix(in srgb, var(--paper-tinted) 70%, var(--accent) 4%);
}
.leaderboard .lb-tile .lb-jump-hint {
  position: absolute;
  top: 10px;
  right: 12px;
  font-family: var(--font-sans);
  font-size: 18px;
  line-height: 1;
  font-weight: 400;
  color: var(--ink-soft);
  opacity: 0.5;
  transition: opacity 0.15s ease, transform 0.15s ease, color 0.15s ease;
  pointer-events: none;
}
.leaderboard .lb-tile[data-jump-to]:hover .lb-jump-hint,
.leaderboard .lb-tile[data-jump-to]:focus-visible .lb-jump-hint {
  opacity: 1;
  color: var(--accent);
  transform: translateX(2px);
}

.leaderboard .lb-row-eyebrow {
  grid-column: 1 / -1;
  padding: 10px 16px;
  font-family: var(--font-sans);
  /* v2 Apr 2026: bumped from 9.5px/500/0.22em to 12px/700/0.14em for
     clearer section-title hierarchy. The original tracked-out micro-cap
     read as ambient chrome rather than a section divider — users scrolling
     the snapshot grid couldn't tell where Identity ended and Headline
     began at a glance. The new treatment keeps the editorial all-caps
     feel but lands closer to FT/NYT kicker weights. */
  font-size: 12px; font-weight: 700;
  color: var(--ink);
  letter-spacing: 0.14em; text-transform: uppercase;
  background: var(--paper-tinted);
  border-bottom: 1px solid var(--rule);
  /* Subtle accent rule on the left — anchors the label as a divider
     without adding a new visual element. 3px wide so it reads on
     mobile too without dominating the row. */
  border-left: 3px solid var(--accent);
}
.leaderboard .lb-label {
  font-family: var(--font-sans);
  font-size: 10px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.16em; text-transform: uppercase;
}
.leaderboard .lb-value {
  font-family: var(--font-display);
  font-size: clamp(20px, 1.9vw, 26px); font-weight: 400;
  color: var(--ink);
  letter-spacing: -0.018em; line-height: 1.05;
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum", "lnum";
  margin-top: auto;
  word-break: break-word;
}
.leaderboard .lb-value.is-pending,
.leaderboard .lb-value.is-empty { color: var(--ink-soft); }
.leaderboard .lb-value.is-accent { color: var(--accent); }
.leaderboard .lb-value .unit {
  font-size: 0.55em; font-weight: 400;
  color: var(--ink-soft);
  margin-left: 3px; letter-spacing: 0;
}
.leaderboard .lb-sub {
  font-family: var(--font-display); font-style: italic;
  font-size: 11.5px; color: var(--ink-soft);
  line-height: 1.35;
  margin-top: 4px;
}

/* Tablet: 2×6 */
@media (max-width: 980px) {
  .leaderboard { grid-template-columns: repeat(2, 1fr); }
  /* v3 (May 2026): see desktop note above — switched from :nth-child to
     sibling-chain anchored on .lb-row-eyebrow. The 2nd and 4th tile
     after each eyebrow are the right-column tiles on a 2-col layout. */
  .leaderboard .lb-tile { border-right: 1px solid var(--rule); }
  .leaderboard .lb-row-eyebrow + .lb-tile + .lb-tile,
  .leaderboard .lb-row-eyebrow + .lb-tile + .lb-tile + .lb-tile + .lb-tile {
    border-right: none;
  }
  .leaderboard .lb-tile:nth-last-child(-n+4) { border-bottom: 1px solid var(--rule); }
  .leaderboard .lb-tile:nth-last-child(-n+2) { border-bottom: none; }
}

/* Mobile: 1-column horizontal swipe (matches old snap-cards behaviour). */
@media (max-width: 720px) {
  .leaderboard {
    grid-auto-flow: column;
    grid-template-columns: none;
    grid-auto-columns: 64%;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    border: none;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    margin: 0 -16px 18px;
    padding: 0 16px;
    scrollbar-width: none;
  }
  .leaderboard::-webkit-scrollbar { display: none; }
  .leaderboard .lb-tile {
    scroll-snap-align: start;
    border-right: 1px solid var(--rule);
    border-bottom: none;
    /* v2 May 2026: was min-height 90px; tightened on mobile so the snapshot
       block isn't 4 chunky rows of vertical scroll before the user reaches
       any real content. Compact tiles read cleanly at 380px viewports. */
    min-height: 78px;
    padding: 12px 14px;
  }
  .leaderboard .lb-tile:last-child { border-right: none; }
  .leaderboard .lb-row-eyebrow { display: none; }
  .leaderboard .lb-tile .lb-label {
    font-size: 9.5px; letter-spacing: 0.14em;
  }
  .leaderboard .lb-tile .lb-value {
    font-size: 22px;
  }
  /* Pull section heads tighter on mobile so the snapshot section's "What
     kind of postcode is this?" doesn't take a third of the screen before
     the data shows up. */
  .section-head-left h2 { margin-bottom: 4px; }
  .section-head-left p { margin-top: 8px; line-height: 1.55; }
  /* Section vertical rhythm on mobile — slightly less air between
     sections so 19 sections don't feel like an infinite scroll. */
  section[data-num] { margin-bottom: 28px; }
}

/* =========================================================== COMPACT LIST
   Pure-CSS row clipping. Tables tagged with .compact-list start collapsed
   and reveal beyond the cap when the sibling button toggles .expanded. */
/* Default cap is 6 rows. Tables can opt into a different cap via
   data-collapse-after="N" — the matching CSS rule clips below that N. */
table.compact-list:not(.expanded) > tbody > tr:nth-child(n+7) { display: none; }
table.compact-list[data-collapse-after="3"]:not(.expanded) > tbody > tr:nth-child(n+4) { display: none; }
table.compact-list[data-collapse-after="5"]:not(.expanded) > tbody > tr:nth-child(n+6) { display: none; }
table.compact-list[data-collapse-after="6"]:not(.expanded) > tbody > tr:nth-child(n+7) { display: none; }
table.compact-list[data-collapse-after="8"]:not(.expanded) > tbody > tr:nth-child(n+9) { display: none; }
table.compact-list[data-collapse-after="10"]:not(.expanded) > tbody > tr:nth-child(n+11) { display: none; }
@media (max-width: 720px) {
  table.compact-list:not(.expanded) > tbody > tr:nth-child(n+4) { display: none; }
  table.compact-list[data-collapse-after]:not(.expanded) > tbody > tr:nth-child(n+4) { display: none; }
}

.show-more {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 10px 0 0;
  padding: 8px 12px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.16em; text-transform: uppercase;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.show-more:hover { color: var(--ink); border-color: var(--ink); }
.show-more::after {
  content: "▾"; margin-left: 2px; font-size: 10px;
  transition: transform 0.18s ease;
}
.show-more.is-expanded::after { transform: rotate(180deg); }

/* =========================================================== DATA FRESHNESS
   Italic small-print line under data tables. Used by sales (and
   intended for any other table whose source publishes on a known
   cadence) to flag how recent the underlying register is. */
.data-freshness {
  margin: 10px 2px 0;
  font-family: var(--font-display);
  /* v3 May 2026: dropped italic. This block carries the explanatory
     "this postcode has had N recorded sales..." paragraph that sits
     under tables — multi-sentence body copy, not a quoted aside.
     Italic + ink-soft compounded the contrast hit and users reported
     trouble reading. Now roman + the new darker ink-soft. Bumped
     size 13 → 14 too: at this length the text earns the extra space. */
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-soft);
}
.data-freshness[hidden] { display: none; }

/* =========================================================== DATA QUALITY NOTICE
   Used when an upstream public data source has known coverage / quality
   issues for the LA the user is viewing. Currently surfaces the police.uk
   under-reporting situation for Greater Manchester Police boroughs.
   Visual tone: warning-amber, not error-red — the data is honest, the
   source is incomplete, we're saying so up front. Leans into editorial
   trust posture: "we read the same source the press cites; here's what
   it says, and here's what it doesn't say." */
.data-quality-notice {
  margin: 14px 0 18px;
  padding: 14px 18px 16px;
  background: color-mix(in srgb, #c08a3a 6%, var(--paper) 94%);
  border-left: 3px solid #c08a3a;
  border-radius: 2px;
}
.data-quality-notice-eyebrow {
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: #8e6520;
  margin-bottom: 6px;
}
.data-quality-notice-body {
  font-family: var(--font-display);
  font-size: 14.5px; line-height: 1.6;
  color: var(--ink);
  margin: 0 0 10px;
}
.data-quality-notice-body strong {
  font-weight: 600;
}
.data-quality-notice-link {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: #8e6520;
  text-decoration: none;
  background-image: none;
  transition: color 0.15s ease;
}
.data-quality-notice-link:hover { color: #6f4c12; }
@media (prefers-color-scheme: dark) {
  .data-quality-notice {
    background: color-mix(in srgb, #c08a3a 12%, var(--paper) 88%);
    border-left-color: #d9a55a;
  }
  .data-quality-notice-eyebrow,
  .data-quality-notice-link { color: #d9a55a; }
  .data-quality-notice-link:hover { color: #f0bf78; }
}

/* =========================================================== PARTIAL POSTCODE NOTICE
   Rendered when the user searched an outward code only (DY2, SW18, M1) and
   we redirected to a representative full postcode. Same visual tone as
   the data-quality-notice — informational, not error. */
.partial-postcode-notice {
  margin: 0 0 20px;
  padding: 14px 18px 16px;
  background: color-mix(in srgb, var(--accent) 4%, var(--paper) 96%);
  border-left: 3px solid var(--accent);
  border-radius: 2px;
}
.partial-postcode-notice-eyebrow {
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 6px;
}
.partial-postcode-notice-body {
  font-family: var(--font-display);
  font-size: 14.5px; line-height: 1.6;
  color: var(--ink);
  margin: 0;
}
.partial-postcode-notice-body strong { font-weight: 600; }

/* =========================================================== COMPARE LINK
   Quiet "tool, not brochure" affordance at the foot of detail sections. */
.compare-link {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 16px;
  padding: 4px 0;
  font-family: var(--font-sans);
  font-size: 10.5px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.16em; text-transform: uppercase;
  text-decoration: none;
  background-image: none;
  transition: color 0.15s ease;
}
.compare-link::after {
  content: "→"; transition: transform 0.18s ease;
  font-size: 12px; margin-left: 2px;
}
/* Outbound (target=_blank) verify-on-source links get the diagonal "leaves
   the site" arrow — same visual cue as .src-tag-link in the section header. */
.compare-link[target="_blank"]::after { content: "↗"; }
.compare-link:hover { color: var(--accent); }
.compare-link:hover::after { transform: translateX(2px); }
.compare-link[target="_blank"]:hover::after { transform: translate(2px, -2px); }

/* Mobile: the snapshot quartet becomes a horizontal swipe with snap. */
@media (max-width: 720px) {
  #snap-cards.grid-4 {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 78%;
    grid-template-columns: none;
    gap: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding-left: 16px;
    padding: 4px 16px 16px;
    margin: 0 -16px;
    scrollbar-width: none;
  }
  #snap-cards.grid-4::-webkit-scrollbar { display: none; }
  #snap-cards .stat-card,
  #snap-cards .headline-card {
    scroll-snap-align: start;
    min-height: 200px;
    border-right: 1px solid var(--rule);
  }
}

/* =========================================================== FACT ROWS / KV LIST */
.fact-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px 28px;
  padding: 16px 0; border-top: 1px solid var(--rule);
}
.fact-row:first-child { border-top: none; padding-top: 0; }
.fact .label {
  font-family: var(--font-sans); font-weight: 500;
  font-size: 11px; color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: 0.18em;
  margin-bottom: 4px;
}
.fact .val { font-family: var(--font-display); font-size: 18px; color: var(--ink); }
.fact .val small { color: var(--ink-soft); font-size: 13px; font-style: italic; }

.kv-list { list-style: none; padding: 0; margin: 0; }
.kv-list li {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 16px 0; border-top: 1px solid var(--rule);
  font-size: 16px; line-height: 1.5;
}
.kv-list li:first-child { border-top: none; padding-top: 0; }
.kv-list .k {
  font-family: var(--font-sans); font-weight: 500;
  font-size: 11px; color: var(--ink-soft);
  letter-spacing: 0.18em; text-transform: uppercase;
  align-self: center;
}
.kv-list .v { font-family: var(--font-display); font-size: 18px; color: var(--ink); text-align: right; }
.kv-list .v small {
  display: inline-block; margin-left: 6px;
  color: var(--ink-soft); font-size: 13px; font-style: italic;
}

/* =========================================================== TABLES
   Zero zebra. Just thin rules between rows. Inter small-caps headers,
   tabular figures in number columns. */
.data-table {
  width: 100%; border-collapse: collapse;
  font-family: var(--font-display);
  font-size: 16px;
  font-feature-settings: "tnum", "lnum";
}
.data-table th, .data-table td {
  padding: 14px 18px; text-align: left;
  border-bottom: 1px solid var(--rule); white-space: nowrap;
  vertical-align: top;
}
.data-table th {
  font-family: var(--font-sans);
  font-size: 10.5px; color: var(--ink-soft);
  font-weight: 500; text-transform: uppercase; letter-spacing: 0.18em;
  background: transparent;
  border-bottom: 1px solid var(--ink);
}
.data-table tbody tr { transition: background 0.12s ease; }
.data-table tbody tr:hover { background: var(--paper-tinted); }
.data-table tbody tr:last-child td { border-bottom: 1px solid var(--rule); }
.data-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums lining-nums;
}
.card.flush.has-table { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* =========================================================== SKELETON */
.skel {
  display: inline-block;
  background: linear-gradient(90deg, var(--paper-tinted) 0%, var(--rule) 50%, var(--paper-tinted) 100%);
  background-size: 200% 100%;
  border-radius: 2px;
  animation: shimmer 1.4s linear infinite;
  color: transparent !important;
  user-select: none;
}
.skel-num { width: 140px; height: 56px; }
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

/* =========================================================== SHARE / TOAST */
.share-toast {
  position: fixed; bottom: 28px; left: 50%;
  transform: translate(-50%, 60px);
  background: var(--ink); color: var(--paper);
  padding: 12px 22px; border-radius: 0;
  font-family: var(--font-sans);
  font-size: 13px; font-weight: 500;
  letter-spacing: 0.04em;
  opacity: 0; pointer-events: none;
  transition: opacity 0.22s ease, transform 0.28s var(--ease-out);
  z-index: 70;
}
.share-toast.in { opacity: 1; transform: translate(-50%, 0); }

/* =========================================================== PRINT */
/* =========================================================== PDF COVER PAGE
   Hidden onscreen — only visible when body.printing is active OR when the
   browser is rendering for print. Becomes a full A4 page courtesy of the
   page-break-after rule. */
.report-cover { display: none; }

body.printing .report-cover,
@media print { .report-cover { display: block; } }

body.printing .report-cover,
@media print {
  .report-cover {
    display: block;
    page-break-after: always;
    break-after: page;
    background: #FFFFFF !important;   /* v8: white, not cream */
    padding: 26mm 20mm 22mm;
    min-height: 270mm;                /* fills more of A4 page */
    box-sizing: border-box;
    color: #1A1612 !important;
    border-top: 8mm solid #D8492A;    /* v8: brand strip at top of cover */
  }
  .report-cover-inner {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 245mm;
    box-sizing: border-box;
  }
  .report-cover-header {
    padding-bottom: 10mm;
    border-bottom: 1px solid var(--ink);
  }
  .report-cover-brand {
    display: inline-block;
    font-family: var(--font-display);
    font-size: 32pt;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--ink);
  }
  .report-cover-brand em {
    color: var(--accent);
    font-style: italic;
  }
  .report-cover-eyebrow {
    display: block;
    margin-top: 8mm;
    font-family: var(--font-sans);
    font-size: 9pt;
    font-weight: 600;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .report-cover-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 20mm 0;
  }
  .report-cover-pc {
    margin: 0;
    font-family: var(--font-display);
    font-size: 72pt;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--ink);
  }
  .report-cover-place {
    margin: 8mm 0 0;
    font-family: var(--font-display);
    font-style: italic;
    font-size: 22pt;
    color: var(--ink-soft);
  }
  .report-cover-context {
    margin: 14mm 0 0;
    font-family: var(--font-sans);
    font-size: 11pt;
    color: var(--ink-soft);
    max-width: 120mm;
    line-height: 1.55;
  }
  .report-cover-footer {
    border-top: 1px solid var(--rule);
    padding-top: 8mm;
  }
  .report-cover-meta-row {
    display: flex;
    gap: 6mm;
    padding: 1.5mm 0;
    border-bottom: 1px solid var(--rule);
    font-family: var(--font-sans);
    font-size: 9pt;
    line-height: 1.5;
  }
  .report-cover-meta-row:last-of-type { border-bottom: 0; }
  .report-cover-meta-k {
    flex: 0 0 32mm;
    color: var(--ink-soft);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-size: 8pt;
  }
  .report-cover-meta-v {
    flex: 1;
    color: var(--ink);
  }
  .report-cover-disclaimer {
    margin: 8mm 0 0;
    padding-top: 6mm;
    border-top: 1px solid var(--rule);
    font-family: var(--font-display);
    font-style: italic;
    font-size: 9pt;
    line-height: 1.55;
    color: var(--ink-soft);
  }
}

/* =========================================================== PDF REPORT MODE
   When body.printing is set (the Build-PDF button toggles it before
   triggering window.print()), the page transforms into a clean A4
   document: all interactive chrome hidden, charts sized to fit, every
   major section starts a fresh page, no website hero/footer/nav. The
   same rules are duplicated under @media print so the browser's native
   print/Save-as-PDF dialog produces the same clean output regardless
   of whether body.printing was set (e.g. user hit Ctrl+P directly). */
body.printing,
@media print {
  body { background: #FFF !important; color: #000 !important; }
  body::before { display: none !important; }
}

body.printing .site-header,
body.printing .site-footer,
body.printing .report-subnav,
body.printing .report-hero,
body.printing .report-toc,
body.printing .m-tabs,
body.printing .report-hero-actions,
body.printing .gated-section-card,
body.printing .pro-upgrade-card,
body.printing .privacy-notice,
body.printing .truely-toast,
body.printing .header-search,
body.printing .leaflet-control-container,
body.printing .skip-link,
body.printing .back-to-top,
body.printing .recent-row,
body.printing .hero-cta-hint,
body.printing .cta-tail,
body.printing .trust-strip,
body.printing #map,
body.printing #cookie-banner,
body.printing .nav-toggle,
body.printing .nav-links,
body.printing .save-star,
body.printing #share-btn,
body.printing #print-btn {
  display: none !important;
}

/* Hide every "Show all", "Show more", "Expand" style toggle button — they
   make no sense in print and ruin the editorial look. Match by common
   class names and by buttons whose text starts with "show all" / "expand". */
body.printing button.show-all,
body.printing button.expand-toggle,
body.printing button[data-toggle],
body.printing .show-all-btn,
body.printing .toggle-btn,
body.printing details > summary,
body.printing .reveal-more,
body.printing .pricing-toggle,
body.printing .pricing-toggle-btn,
body.printing button[class*="show-all"],
body.printing button[class*="toggle"] {
  display: none !important;
}
/* Open all <details> elements so their contents are visible. */
body.printing details { display: block !important; }
body.printing details > *:not(summary) { display: block !important; }

/* Pro-preview banner — beta-only marketing; doesn't belong in a paid PDF. */
body.printing [data-pro-preview]::before { display: none !important; }

/* Layout: flatten the report grid (sidebar + main becomes a single column). */
body.printing .container,
body.printing .report-hero .inner {
  padding: 0 !important;
  max-width: none !important;
}
body.printing .report-grid {
  display: block !important;
  grid-template-columns: none !important;
}

/* Typography for PDF: report-grade serif body, sans headings.
   Mirror of the @media print rules so screen-flip-to-printing-mode
   matches what the headless browser renders. */
body.printing {
  font-family: 'Fraunces', Georgia, 'Times New Roman', serif !important;
  font-size: 10.25pt;
  line-height: 1.5;
  background: #FFFFFF !important;
  color: #1A1612 !important;
}
body.printing .reveal { opacity: 1 !important; transform: none !important; }
body.printing a, body.printing a:visited {
  color: #1A1612 !important;
  text-decoration: none !important;
  background-image: none !important;
  background: transparent !important;
}
body.printing h1, body.printing h2, body.printing h3, body.printing h4 {
  font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, sans-serif !important;
  color: #1A1612 !important;
  page-break-after: avoid;
  break-after: avoid-page;
  letter-spacing: -0.005em;
}
body.printing h2 {
  font-size: 15.5pt;
  font-weight: 700;
  padding-bottom: 2mm;
  border-bottom: 0.75pt solid #1A1612;
  margin: 0 0 4mm;
}
body.printing h3 { font-size: 11.5pt; font-weight: 600; }
body.printing .eyebrow,
body.printing span.eyebrow {
  font-family: 'Inter', sans-serif !important;
  font-size: 7.5pt !important;
  font-weight: 600 !important;
  letter-spacing: 0.14em !important;
  text-transform: uppercase;
  color: #6B6357 !important;
}

/* Cards: flat ruled boxes, no shadows, no rounded corners — report style. */
body.printing .card,
body.printing .stat-card,
body.printing .headline-card {
  background: #FFFFFF !important;
  border: 0.5pt solid #C8C2B6 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  padding: 4mm 5mm !important;
  margin: 0 0 3mm !important;
  break-inside: avoid;
  page-break-inside: avoid;
}
body.printing .card-lede {
  font-family: 'Fraunces', Georgia, serif !important;
  font-style: italic;
  color: #4A4540 !important;
}
body.printing .card-meta {
  font-family: 'Inter', sans-serif !important;
  font-size: 8.5pt;
  color: #6B6357 !important;
}

/* Tables — UK report style: top + bottom rule, no zebra. */
body.printing table,
body.printing .data-table,
body.printing .kv-list {
  width: 100% !important;
  border-collapse: collapse !important;
  font-family: 'Inter', sans-serif !important;
  font-size: 9pt;
  line-height: 1.45;
  page-break-inside: avoid;
  break-inside: avoid;
  margin: 2mm 0 3mm;
  color: #1A1612 !important;
}
body.printing th {
  text-align: left;
  font-weight: 600;
  padding: 2.5mm 3mm 2mm !important;
  border-bottom: 1pt solid #1A1612 !important;
  background: transparent !important;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 8pt;
}
body.printing td {
  padding: 1.8mm 3mm !important;
  border-bottom: 0.25pt solid #D6D2CC !important;
  vertical-align: top;
  background: transparent !important;
}
body.printing tr:last-child td { border-bottom: 0.75pt solid #1A1612 !important; }

/* Section flow under body.printing — mirrors the @media print rules.
   Sections do NOT each start a new page (that produced empty pages in
   v4). Only the first section forces a break (so the cover sits alone);
   the rest flow naturally with mid-element protection. */
body.printing #report-body > section[id] {
  page-break-before: auto;
  break-before: auto;
  page-break-inside: auto;
  margin: 0 0 8mm 0;
}
body.printing #report-body > section[id]:first-child {
  page-break-before: always;
  break-before: page;
}

/* Charts — let Chart.js compute its own dimensions from the wide
   print-viewport parent. See the @media print rules above for the
   reasoning. Locking height here would fight Chart.js's aspect-ratio
   logic and re-introduce the v4 compression. */
body.printing .chart-wrap {
  width: 100% !important;
  max-width: 165mm !important;
  margin: 2mm 0 5mm !important;
  break-inside: avoid;
  page-break-inside: avoid;
  overflow: visible;
}
body.printing .chart-wrap canvas {
  max-width: 100% !important;
  width: auto !important;
  height: auto !important;
}

/* Empty-state and Loading placeholders look unprofessional in a PDF —
   hide them entirely. Sections with no data become blank in the PDF,
   which reads as "no data on file" without explicit verbiage. */
body.printing .empty-state,
body.printing .loading,
body.printing p.loading {
  display: none !important;
}

/* The image-button on the print btn shouldn't render. */
body.printing .pdf-btn { display: none !important; }

@media print {
  /* v5 May 2026 — rewritten as a proper UK property-due-diligence
     report. Reference style: HM Land Registry official copies, RICS
     HomeBuyer reports, Companies House filings. Key principles:
       · Serif body for sustained reading
       · Sharp typographic hierarchy (eyebrow / H2 / lede)
       · Ruled tables (no zebra, no background fills — bottom rule only)
       · Flat ruled cards (no shadows, no rounded corners)
       · CONTENT FLOWS — sections do NOT each start a fresh page. We
         only avoid splitting cards/tables/charts mid-element, and let
         long content fill pages densely.
       · Charts locked to print-friendly dimensions (avoids the
         compressed-aspect-ratio issue when the screen viewport was
         narrow at chart creation time).
       · Running header/footer applied via the @page top-left /
         top-right / bottom-left / bottom-right margin boxes (Chrome
         native paged-media support — works in both window.print()
         and Save-as-PDF). */

  /* ----- PAGED MEDIA SETUP -----
     Native @page boxes are supported by Chrome / Edge / Safari for
     print and Save-as-PDF. Gives us the running header/footer strip
     on every page without needing a server-side renderer. The cover
     page (:first) suppresses both so the cover bleeds clean. */
  @page {
    size: A4 portrait;
    margin: 22mm 14mm 18mm 14mm;

    @top-left {
      content: "TRUELY";
      font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, sans-serif;
      font-size: 8pt;
      font-weight: 700;
      letter-spacing: 0.18em;
      color: #1A1612;
      vertical-align: bottom;
      padding-bottom: 4mm;
    }
    @top-right {
      content: "POSTCODE PROPERTY RECORD";
      font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, sans-serif;
      font-size: 7.5pt;
      font-weight: 500;
      letter-spacing: 0.16em;
      color: #6B6357;
      vertical-align: bottom;
      padding-bottom: 4mm;
    }
    @bottom-left {
      content: "truely.uk";
      font-family: 'Inter', sans-serif;
      font-size: 7.5pt;
      color: #6B6357;
      vertical-align: top;
      padding-top: 3mm;
    }
    @bottom-right {
      content: "Page " counter(page) " of " counter(pages);
      font-family: 'Inter', sans-serif;
      font-size: 7.5pt;
      color: #6B6357;
      vertical-align: top;
      padding-top: 3mm;
    }
  }
  /* Suppress header/footer on the cover (page 1) so the cover bleeds clean. */
  @page :first {
    margin: 0;
    @top-left    { content: ""; }
    @top-right   { content: ""; }
    @bottom-left { content: ""; }
    @bottom-right{ content: ""; }
  }

  /* ----- BASE ----- */
  /* v8: sans-serif body, smaller text, pure white pages. The
     Christie's / Knight Frank / Savills market-report convention is
     Inter (or similar humanist sans) at ~9-9.5pt on white, not a
     literary serif on cream — that reads as editorial commentary,
     not a data document. */
  html, body {
    background: #FFFFFF !important;
    color: #1A1612 !important;
    font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, sans-serif !important;
    font-size: 9pt;
    line-height: 1.45;
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
    margin: 0;
    padding: 0;
  }
  body::before { display: none !important; }
  /* Force EVERY background to white. Cards, sections, ledes, banners
     — all the cream/beige tints from the website become flat white
     in print so the page reads as a data document. */
  *, *::before, *::after {
    background-color: transparent !important;
  }
  /* …except the few structural elements that deliberately keep a fill
     (KPI row labels, the brand strip, accent badges, etc.). These are
     re-tinted further down where we add them. */

  /* ----- STRIP ALL WEB CHROME ----- */
  .site-header, .site-footer, .report-subnav, .back-to-top, .share-btn,
  #share-btn, #print-btn, #save-pc-btn, .save-star,
  .recent-row, .hero-cta-hint, .cta-tail, .trust-strip, .pro-banner,
  .report-hero, .report-hero-actions, .leaflet-control-container,
  .report-toc, .gated-section-card, .gated-actions, .pro-upgrade-card,
  .privacy-notice, .truely-toast, .account-modal,
  .header-search, .nav-toggle, .nav-links, .skip-link,
  #map, #cookie-banner, .pricing-toggle, .pricing-toggle-btn,
  .empty-state, .loading, p.loading,
  button.show-all, button.expand-toggle, button[data-toggle],
  .show-all-btn, .toggle-btn, .reveal-more, .pdf-btn,
  [data-pro-preview]::before {
    display: none !important;
  }
  /* Open every <details> so contents are visible (no print collapse). */
  details { display: block !important; }
  details > *:not(summary) { display: block !important; }
  details > summary { display: none !important; }

  /* ----- LAYOUT RESET ----- */
  .container, .report-hero .inner, main.container {
    padding: 0 !important;
    margin: 0 !important;
    max-width: none !important;
    width: 100% !important;
  }
  .report-grid {
    display: block !important;
    grid-template-columns: none !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; }

  /* ----- LINKS — collapse to plain ink ----- */
  a, a:visited {
    color: #1A1612 !important;
    text-decoration: none !important;
    background-image: none !important;
    background: transparent !important;
    border: 0 !important;
  }

  /* ----- TYPOGRAPHY HIERARCHY -----
     v8: H2 (conversational question) hidden in print — the eyebrow
     does the work of the section title in formal small-caps. Reads
     like a market report, not a Q&A blog. */
  h1, h2, h3, h4 {
    font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, sans-serif !important;
    color: #1A1612 !important;
    page-break-after: avoid;
    break-after: avoid-page;
    margin: 0;
    letter-spacing: -0.005em;
  }
  h1 { font-size: 22pt; font-weight: 800; margin: 0 0 3mm; }
  /* HIDE the conversational H2 questions in print — they read as
     website tone ("What kind of postcode is this?", "What this
     postcode tends to sell for"). The numbered eyebrow takes over
     as the visible section heading below. */
  #report-body > section[id] > .section-head h2,
  #report-body > section[id] > h2 { display: none !important; }
  h3 { font-size: 10pt; font-weight: 600; margin: 3mm 0 1.5mm; text-transform: uppercase; letter-spacing: 0.06em; color: #1A1612; }
  p { margin: 0 0 2mm; orphans: 3; widows: 3; font-style: normal !important; }
  strong { font-weight: 600; color: #1A1612; }
  /* Strip italics — they read as editorial commentary, not data. */
  em, i, .card-lede, .lede {
    font-style: normal !important;
    font-family: 'Inter', sans-serif !important;
  }

  /* The numbered section eyebrow — promoted from "small label" to
     "formal section title" since H2 is now hidden. */
  .eyebrow,
  span.eyebrow,
  .report-section-eyebrow {
    display: block;
    font-family: 'Inter', sans-serif !important;
    font-size: 9.5pt !important;
    font-weight: 700 !important;
    letter-spacing: 0.18em !important;
    text-transform: uppercase;
    color: #1A1612 !important;
    margin: 0 0 3mm 0 !important;
    padding: 0 0 2mm 0 !important;
    border-bottom: 0.5pt solid #1A1612 !important;
  }
  /* Inline pills / badges — strip the playful coloured backgrounds.
     Score chips like "1/10 HIGH-CRIME" become small-caps text. */
  .score-badge, .badge, .pill, .score-pill, .quality-chip {
    background: transparent !important;
    color: #6B6357 !important;
    border: 0.5pt solid #C8C2B6 !important;
    padding: 0.5mm 1.5mm !important;
    font-size: 6.5pt !important;
    font-weight: 600 !important;
    letter-spacing: 0.08em !important;
    text-transform: uppercase;
    border-radius: 0 !important;
    box-shadow: none !important;
    vertical-align: middle;
  }

  /* ----- SECTION FLOW — natural, dense, no forced breaks ----- */
  /* Sections after the cover flow naturally. Cards / tables / charts
     within a section can't split mid-element, but sections themselves
     pack onto pages as space allows. This is the single most important
     change vs v4 — eliminates the empty-page-per-section problem. */
  #report-body { display: block; }
  #report-body > section[id] {
    page-break-before: auto;
    break-before: auto;
    page-break-inside: auto;
    margin: 0 0 8mm 0;
    padding: 0;
  }
  /* Only the FIRST content section after the cover forces a new page
     (so the cover sits alone). Subsequent sections flow naturally. */
  .report-cover + #report-body > section[id]:first-child,
  #report-body > section[id]:first-child {
    page-break-before: always;
    break-before: page;
  }

  /* ----- CARDS — flat ruled boxes ----- */
  .card, .stat-card, .headline-card {
    background: #FFFFFF !important;
    border: 0.5pt solid #C8C2B6 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    padding: 4mm 5mm !important;
    margin: 0 0 3mm !important;
    page-break-inside: avoid;
    break-inside: avoid;
  }
  .card-lede {
    font-family: 'Fraunces', Georgia, serif !important;
    font-style: italic;
    font-size: 10pt;
    color: #4A4540 !important;
    margin: 0 0 3mm;
  }
  .card-meta {
    font-family: 'Inter', sans-serif !important;
    font-size: 8.5pt;
    color: #6B6357 !important;
    line-height: 1.45;
    margin: 3mm 0 0;
  }

  /* ----- TABLES — UK report style: top + bottom rule, no zebra ----- */
  table, .data-table, .kv-list {
    width: 100% !important;
    border-collapse: collapse !important;
    font-family: 'Inter', sans-serif !important;
    font-size: 9pt;
    line-height: 1.45;
    margin: 2mm 0 3mm;
    page-break-inside: avoid;
    break-inside: avoid;
    color: #1A1612 !important;
  }
  th {
    text-align: left;
    font-weight: 600;
    color: #1A1612 !important;
    padding: 2.5mm 3mm 2mm !important;
    border-bottom: 1pt solid #1A1612 !important;
    background: transparent !important;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 8pt;
  }
  td {
    padding: 1.8mm 3mm !important;
    border-bottom: 0.25pt solid #D6D2CC !important;
    vertical-align: top;
    background: transparent !important;
  }
  tr:last-child td { border-bottom: 0.75pt solid #1A1612 !important; }
  td strong { font-weight: 600; }

  /* ----- CHARTS — let Chart.js own dimensions ----- */
  /* Chart.js sizes its canvas to the parent container at render time.
     The v6 PDF flow (window.print()) calls Chart.getChart(c).resize()
     for every chart instance right BEFORE triggering print, so each
     canvas gets a fresh aspect-ratio calculation against the wider
     print-mode parent. CSS only caps max-width here; locking height
     would fight Chart.js's own aspect-ratio logic. */
  .chart-wrap {
    width: 100% !important;
    max-width: 165mm !important;
    margin: 2mm 0 5mm !important;
    page-break-inside: avoid;
    break-inside: avoid;
    overflow: visible;
  }
  .chart-wrap canvas {
    max-width: 100% !important;
    width: auto !important;
    height: auto !important;
  }

  /* ----- LISTS ----- */
  ul, ol { margin: 0 0 3mm; padding-left: 5mm; }
  li { margin: 0 0 1.2mm; line-height: 1.5; }

  /* ----- HERO / SUBLINE on the very first content page ----- */
  /* The hero block (postcode + breadcrumb) is hidden by the chrome
     strip above, but we want the postcode and place to appear at the
     top of the first content section. The cover page already handles
     this for the front page. */

  /* ----- AVOID DANGLING HEADERS ----- */
  h2 + p, h2 + div, h2 + .card { page-break-before: avoid; }

  /* ============================================================
     REPORT IDENTITY (v6 layout pass — May 2026)
     Makes the PDF read as a structured market report (cf. Christie's
     market reports, RICS HomeBuyer, gov.uk official copies) rather
     than a printout of a webpage. Three pillars:
       1. Numbered sections in editorial style (I. II. III.)
       2. Leaderboard rendered as a proper KPI grid
       3. Brand accent at the section-heading entrypoint
     ============================================================ */

  /* ----- AUTO-NUMBERED SECTIONS -----
     CSS counter prefixes every section's eyebrow with the Roman
     numeral in brand terracotta. v8: eyebrow IS the section title
     now (H2 is hidden), so this number reads as the formal
     "Section I." pattern of a proper report. */
  #report-body { counter-reset: section-num; }
  #report-body > section[id] { counter-increment: section-num; }
  #report-body > section[id] > .section-head .eyebrow::before,
  #report-body > section[id] > .section-head span.eyebrow::before {
    content: counter(section-num, upper-roman) ".  ";
    color: #D8492A;
    font-weight: 800;
    letter-spacing: 0.18em;
  }

  /* ----- TIGHT SECTION SPACING ----- */
  #report-body > section[id] {
    margin: 0 0 5mm 0;
    padding: 0;
  }
  .section-head {
    margin: 0 0 3mm 0 !important;
    padding: 0 !important;
    background: transparent !important;
  }

  /* ----- CARDS — hairline-ruled white panels -----
     v8: drop the cream tint; cards are now pure white with a 0.5pt
     hairline border. Padding tightened to 3mm/4mm. Reads as a clean
     data panel, not a website card. */
  .card, .stat-card, .headline-card {
    background: #FFFFFF !important;
    border: 0.5pt solid #C8C2B6 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    padding: 3mm 4mm !important;
    margin: 0 0 2mm !important;
    page-break-inside: avoid;
    break-inside: avoid;
    font-style: normal !important;
  }
  /* "Lede" paragraphs (the italic intro at the top of each section)
     — strip the italic + cream tint, render as plain sober body. */
  .card-lede, .section-lede, .lede {
    background: transparent !important;
    border: 0 !important;
    padding: 0 !important;
    margin: 0 0 2mm !important;
    font-family: 'Inter', sans-serif !important;
    font-style: normal !important;
    color: #1A1612 !important;
    font-size: 9pt !important;
    line-height: 1.5;
  }
  /* Meta lines (small disclaimers under a card) — even smaller, grey. */
  .card-meta {
    font-size: 7.5pt !important;
    color: #6B6357 !important;
    line-height: 1.45;
    margin-top: 2mm !important;
  }

  /* ----- KPI LEADERBOARD GRID -----
     The Snapshot section's .leaderboard is a 12-tile dashboard on
     screen. In print we render it as a true 4-column KPI grid with
     ruled cells and category row-labels — the canonical "report top
     of page" treatment from Christie's, Knight Frank, Savills, etc. */
  .leaderboard {
    display: grid !important;
    grid-template-columns: repeat(4, 1fr) !important;
    gap: 0 !important;
    border: 0.5pt solid #C8C2B6 !important;
    margin: 0 0 5mm 0 !important;
    padding: 0 !important;
    background: transparent !important;
    page-break-inside: avoid;
  }
  .lb-row-eyebrow {
    grid-column: 1 / -1;
    background: #F1ECE0 !important;
    padding: 2mm 3mm !important;
    font-family: 'Inter', sans-serif !important;
    font-size: 7pt !important;
    font-weight: 600 !important;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: #1A1612 !important;
    border-bottom: 0.5pt solid #C8C2B6 !important;
    border-top: 0.5pt solid #C8C2B6 !important;
  }
  .lb-row-eyebrow:first-child {
    border-top: 0 !important;
  }
  .lb-tile {
    display: block !important;
    padding: 3mm 3mm 3.5mm !important;
    border-right: 0.25pt solid #D6D2CC !important;
    border-bottom: 0.25pt solid #D6D2CC !important;
    background: transparent !important;
    box-shadow: none !important;
    border-radius: 0 !important;
    page-break-inside: avoid;
  }
  .lb-tile:nth-child(4n) { border-right: 0 !important; }
  .lb-label {
    font-family: 'Inter', sans-serif !important;
    font-size: 7pt !important;
    font-weight: 600 !important;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #6B6357 !important;
    margin: 0 0 1.2mm !important;
    line-height: 1.3 !important;
  }
  .lb-value {
    font-family: 'Inter', sans-serif !important;
    font-size: 12.5pt !important;
    font-weight: 700 !important;
    color: #1A1612 !important;
    letter-spacing: -0.01em;
    line-height: 1.15 !important;
  }
  .lb-jump-hint,
  .lb-tile .skel {
    display: none !important;
  }

  /* ----- KV LISTS (key/value rows like "Crimes 12mo: 178") -----
     Many sections use .kv-list — render as a tight 2-column data
     block, no zebra, label left small-caps, value right. */
  .kv-list, .kv-row {
    display: grid !important;
    grid-template-columns: 1fr auto !important;
    gap: 1mm 4mm !important;
    margin: 0 0 2mm !important;
  }
  .kv-list dt, .kv-list .kv-k, .kv-row .kv-k {
    font-family: 'Inter', sans-serif !important;
    font-size: 8pt !important;
    font-weight: 500 !important;
    color: #6B6357 !important;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 1mm 0 !important;
    border-bottom: 0.25pt solid #E8E4DC !important;
  }
  .kv-list dd, .kv-list .kv-v, .kv-row .kv-v {
    font-family: 'Inter', sans-serif !important;
    font-size: 9.5pt !important;
    font-weight: 600 !important;
    color: #1A1612 !important;
    text-align: right;
    padding: 1mm 0 !important;
    border-bottom: 0.25pt solid #E8E4DC !important;
    margin: 0 !important;
  }
}

/* =========================================================== PRO FEATURE BADGE
   Small 'Pro' badge sits on the corner of a feature button to signal it's
   gated to paying users. Applied programmatically (e.g. postcode.js adds
   .is-pro-feature to the print button for non-Pro users post-launch) so
   we never show the badge to users who already have access. */
.icon-btn.is-pro-feature {
  position: relative;
}
.icon-btn.is-pro-feature::after {
  content: "Pro";
  position: absolute;
  top: -6px;
  right: -8px;
  font-family: var(--font-sans);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #FFF;
  background: var(--accent);
  padding: 2px 5px;
  border-radius: 2px;
  pointer-events: none;
  line-height: 1.2;
}

/* =========================================================== SKIP LINK */
.skip-link {
  position: fixed; top: 8px; left: 8px;
  z-index: 200;
  padding: 10px 16px;
  background: var(--ink); color: var(--paper);
  border-radius: 0;
  font-family: var(--font-sans);
  font-size: 13px; font-weight: 500;
  letter-spacing: 0.06em;
  text-decoration: none;
  transform: translateY(-150%);
  transition: transform 0.2s var(--ease-out);
  background-image: none;
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  color: var(--paper);
}

/* =========================================================== PRIVACY NOTICE */
.privacy-notice {
  position: fixed;
  left: 24px; right: 24px; bottom: 24px;
  z-index: 80;
  max-width: 640px; margin: 0 auto;
  background: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 0;
  opacity: 0; transform: translateY(20px);
  transition: opacity 0.32s var(--ease-out), transform 0.32s var(--ease-out);
  pointer-events: auto;
}
.privacy-notice.in { opacity: 1; transform: translateY(0); }
.privacy-notice.out { opacity: 0; transform: translateY(20px); }
.privacy-notice-inner {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 14px;
  padding: 14px 14px 14px 18px;
}
.privacy-notice-icon {
  width: 32px; height: 32px;
  background: transparent; color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.privacy-notice-text {
  margin: 0;
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--ink);
  line-height: 1.55;
}
.privacy-notice-text a {
  color: var(--accent);
  margin-left: 4px;
}
.privacy-notice-text a:hover { color: var(--accent-deep); }
.privacy-notice-actions { display: flex; gap: 8px; }
.privacy-notice .privacy-notice-ack {
  min-height: 38px; padding: 9px 16px;
  font-size: 11.5px;
}
.privacy-notice-close {
  width: 38px; height: 38px;
  border-radius: 0;
  background: transparent; border: 1px solid transparent;
  color: var(--ink-soft); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.privacy-notice-close:hover { color: var(--ink); border-color: var(--rule); }
@media (max-width: 540px) {
  .privacy-notice {
    left: 0; right: 0; bottom: 0;
    border-radius: 0;
    border-bottom: none;
    max-width: none;
  }
  .privacy-notice-inner {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "icon  text  close"
      "actions actions actions";
    gap: 12px 14px;
    padding: 14px 14px 16px 18px;
  }
  .privacy-notice-icon    { grid-area: icon; }
  .privacy-notice-text    { grid-area: text; font-size: 13.5px; }
  .privacy-notice-close   { grid-area: close; }
  .privacy-notice-actions { grid-area: actions; }
  .privacy-notice .privacy-notice-ack { width: 100%; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .privacy-notice { transition: opacity 0.18s linear; transform: none; }
  .privacy-notice.in { transform: none; }
}

/* =========================================================== MOBILE STICKY SEARCH
   Fixed-position search bar at the bottom of the viewport on mobile
   postcode pages. Lets users search a new postcode without scrolling
   back through 19 sections to reach the header. Hidden on desktop
   (sticky desktop sidebar TOC has its own search pill). */
.mobile-sticky-search {
  display: none;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 65;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--rule);
  align-items: center;
  gap: 10px;
  transform: translateY(110%);
  transition: transform 0.28s var(--ease-out);
}
.mobile-sticky-search.visible { transform: translateY(0); }
.mobile-sticky-search-icon {
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-soft);
  flex-shrink: 0;
}
.mobile-sticky-search input {
  flex: 1;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 15.5px;
  padding: 8px 0;
  background: transparent;
  border: none;
  outline: none;
  color: var(--ink);
}
.mobile-sticky-search input::placeholder { color: var(--ink-soft); }
.mobile-sticky-search-go {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px;
  background: var(--accent);
  color: var(--paper);
  border: none; border-radius: 0;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s ease;
}
.mobile-sticky-search-go:hover,
.mobile-sticky-search-go:focus-visible { background: var(--accent-deep, var(--ink)); }
@media (max-width: 720px) {
  .mobile-sticky-search { display: flex; }
  /* Lift back-to-top above the sticky search bar when both are visible
     so they don't overlap. */
  .back-to-top { bottom: 78px; }
}

/* =========================================================== BACK-TO-TOP */
.back-to-top {
  position: fixed;
  bottom: 28px; right: 28px;
  width: 44px; height: 44px; border-radius: 0;
  background: var(--ink); color: var(--paper);
  border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transform: translateY(20px);
  pointer-events: none;
  transition: transform 0.28s var(--ease-out), opacity 0.22s ease, background 0.18s ease;
  z-index: 60;
}
.back-to-top.visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
.back-to-top:hover { background: var(--accent); }
@media (max-width: 700px) {
  /* Reverted May 2026 — the mobile sticky search bar that the 78px
     bump was clearing is gone, so the back-to-top can sit at the
     standard 18px gap again. */
  .back-to-top { bottom: 18px; right: 18px; width: 40px; height: 40px; }
}

/* =========================================================== BARS / DOTS */
.bar-row {
  display: grid; grid-template-columns: 200px 1fr 64px;
  align-items: center; gap: 16px;
  padding: 10px 0; font-size: 14px;
  border-bottom: 1px solid var(--rule);
}
.bar-row:last-child { border-bottom: none; }
.bar-row .barlabel { color: var(--ink); font-family: var(--font-display); }
.bar-row .bar {
  height: 6px; background: var(--paper-tinted);
  border-radius: 0; overflow: hidden;
  position: relative;
}
.bar-row .bar-fill {
  height: 100%;
  background: var(--ink);
  border-radius: 0;
}
.bar-row .barval {
  text-align: right; color: var(--ink-soft);
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums lining-nums; font-weight: 500;
}
@media (max-width: 540px) {
  .bar-row { grid-template-columns: 1fr 56px; gap: 8px; }
  .bar-row .bar { grid-column: 1 / -1; order: 2; }
  .bar-row .barlabel { order: 0; }
  .bar-row .barval { order: 1; }
}

/* =========================================================== MAP */
#map {
  height: 380px; border-radius: 0;
  overflow: hidden; border: 1px solid var(--rule);
  /* v2 May 2026: explicit stacking context so the Leaflet internals
     (which use z-index up to 800 for controls) stay isolated and can't
     paint over the sticky site-header (z-index 1000) or mobile nav
     drawer (z-index 1001). */
  position: relative;
  z-index: 1;
}
@media (max-width: 720px) { #map { height: 280px; } }
.leaflet-container { font-family: var(--font-sans); background: var(--paper-tinted); }

.map-frame { position: relative; }
.map-legend {
  position: absolute; top: 16px; right: 16px; z-index: 400;
  background: var(--paper-tinted);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 6px 10px 8px;
  font-family: var(--font-sans); font-size: 12.5px;
  max-width: 200px;
  box-shadow: none;
}
.map-legend .lg-title {
  font-size: 10px; font-weight: 500; color: var(--ink-soft);
  letter-spacing: 0.18em; text-transform: uppercase; margin: 0 0 6px;
}
.map-legend .lg-row {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 0; color: var(--ink); font-weight: 400;
}
.map-legend .lg-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.map-legend .lg-dot.green   { background: var(--ink); }
.map-legend .lg-dot.crime   { background: var(--accent); }
.map-legend .lg-dot.school  { background: var(--ink-soft); }
.map-legend .lg-dot.station { background: var(--ink); border: 1.5px solid var(--accent); }
/* In-frame disclaimer line — sits inside the map card directly under the
   legend, not as a footnote below the card. Italic Fraunces for editorial
   warmth; small but present. */
.map-disclaimer {
  position: absolute;
  left: 16px; bottom: 16px; z-index: 400;
  background: var(--paper-tinted);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 6px 10px;
  font-family: var(--font-display); font-style: italic;
  font-size: 12.5px; line-height: 1.4;
  color: var(--ink-soft);
  max-width: 60%;
}
@media (max-width: 720px) {
  .map-legend { top: 10px; right: 10px; padding: 6px 10px; max-width: 150px; font-size: 11.5px; }
  .map-disclaimer { left: 10px; bottom: 10px; font-size: 11.5px; max-width: 70%; }
}

/* =========================================================== CHARTS */
.chart-wrap {
  position: relative;
  width: 100%;
  height: 200px;
  min-height: 180px; max-height: 220px;
  flex: 1 1 200px;
}
@media (max-width: 720px) {
  .chart-wrap { height: 180px; max-height: 200px; min-height: 160px; }
}
.chart-wrap > canvas { width: 100% !important; height: 100% !important; display: block; }

/* =========================================================== MOTION
   The brief: motion is quieter. The reveal-on-scroll system is gone
   (installRevealOnScroll is now a no-op). The .reveal/.in classes are
   still added by some legacy paths, so keep them visible. */
.reveal, .reveal.in { opacity: 1; transform: none; transition: none; }
@media (prefers-reduced-motion: reduce) {
  .stat-card { transition: none; }
}

/* =========================================================== HOMEPAGE HERO base
   (most of the homepage hero lives in home.css; this block defines tokens
   shared with other heroes) */
.hero {
  position: relative;
  padding: 96px 24px 80px;
  overflow: hidden;
}

/* =========================================================== SEARCH CARD
   The big primary search input. Editorial: a solid ink-bordered box,
   no shadow, no rounded corners. */
.search-card {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 0;
  padding: 6px 6px 6px 50px;
  display: flex; gap: 8px;
  max-width: 640px; margin: 0 auto;
  transition: border-color 0.2s ease;
}
.search-card:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-glow);
}
.search-card .search-icon {
  position: absolute; left: 18px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; z-index: 1;
}
.search-card .search-icon svg { stroke: var(--ink-soft); }
.search-card input {
  flex: 1; min-width: 0;
  border: 0; background: transparent;
  padding: 14px 6px;
  font-family: var(--font-display);
  font-size: 18px; outline: none;
  font-feature-settings: "tnum", "lnum";
  letter-spacing: 0.01em;
  color: var(--ink);
}
.search-card input::placeholder { color: var(--ink-soft); font-style: normal; font-family: var(--font-sans); }
.search-card input:focus { box-shadow: none; }
.search-card .btn-primary { padding: 12px 22px; min-height: 48px; }

.suggest-list {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  background: var(--paper); border: 1px solid var(--ink);
  border-radius: 0;
  z-index: 10; overflow: hidden;
  max-height: 340px; overflow-y: auto;
}
.suggest-list.empty { display: none; }
.suggest-item {
  padding: 14px 18px; cursor: pointer;
  font-family: var(--font-display); font-size: 16px;
  border-bottom: 1px solid var(--rule);
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  background-image: none;
}
.suggest-item:last-child { border-bottom: none; }
.suggest-item:hover, .suggest-item.active { background: var(--paper-tinted); }
.suggest-item .pc {
  font-family: var(--font-sans); font-weight: 500;
  color: var(--ink);
  letter-spacing: 0.04em; font-feature-settings: "tnum", "lnum";
}
.suggest-item .meta { font-size: 12px; color: var(--ink-soft); font-style: italic; }

/* =========================================================== REPORT HERO
   A wide masthead — small Inter eyebrow, huge Fraunces postcode,
   italic dateline, italic byline-style attribution, optional inline
   subline of geography facts. No glowing blobs, no pills. */
.report-hero {
  padding: 28px 24px 22px;
  position: relative;
  background: var(--paper-tinted);
  border-bottom: 1px solid var(--rule);
}
.report-hero .inner {
  position: relative; z-index: 1;
  max-width: 1240px; margin: 0 auto;
}

.report-hero-actions {
  position: absolute; top: 22px; right: 24px;
  display: flex; gap: 6px; z-index: 2;
}
.icon-btn {
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  color: var(--ink-soft); cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
  background-image: none;
}
.icon-btn:hover { border-color: var(--ink); color: var(--ink); }

/* Save-postcode star — same chrome as the icon-btn ring, but with a label
   token so the affordance reads as a verb (Save / Saved / Sign in to save).
   Active state fills the star with the accent red. */
.save-star {
  width: auto; padding: 0 12px 0 10px; gap: 6px; min-width: 38px;
}
.save-star .save-star-label {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* PDF download button — same chrome as .save-star: icon + uppercase
   label token. v1 May 2026: was a plain printer icon; relabelled "PDF"
   with a download glyph to match what the pricing page promises
   ("PDF export of the full report") and to reduce ambiguity for users
   who don't think in print metaphors. */
.pdf-btn {
  width: auto; padding: 0 12px 0 10px; gap: 6px; min-width: 38px;
}
.pdf-btn .pdf-btn-label {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.save-star[data-saved="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
.save-star[data-saved="true"] svg {
  fill: var(--accent);
  stroke: var(--accent);
}
.save-star.is-flash {
  animation: save-star-flash 0.42s ease;
}
@keyframes save-star-flash {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.12); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .save-star.is-flash { animation: none; }
}

/* Gated-section CTA — calm editorial card shown in place of premium content
   for anonymous visitors. No padlock, no "premium" or "unlock" language. */
.gated-section-card {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  padding: 26px 28px;
  margin-top: 8px;
  background: var(--paper-tinted);
}
.gated-section-card .gated-eyebrow {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  display: block;
  margin-bottom: 10px;
}
.gated-section-card .gated-lede {
  /* v4 May 2026: italic Fraunces → upright Inter to match the site-wide
     body-copy modernisation. Reads as a direct value proposition. */
  font-size: 15.5px;
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 18px;
  max-width: 620px;
  font-weight: 450;
}
.gated-section-card .gated-actions {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.gated-section-card .gated-signin {
  font-size: 14px;
  color: var(--ink-soft);
}
.gated-section-card .gated-signin a {
  color: var(--ink);
  font-weight: 500;
}
/* Inline variant used at the foot of the partial-gated sales section. */
.gated-section-card.gated-inline {
  margin-top: 14px;
  padding: 18px 22px;
  border-left-width: 3px;
}
.gated-section-card.gated-inline .gated-lede { font-size: 15px; margin-bottom: 12px; }

/* Gated-section variant for Pro-tier sections shown to anonymous
   visitors. Same chrome as the default gated card but the eyebrow + a
   small meta line below the lede make it explicit that this becomes a
   paid feature at launch — so signing up free today isn't a bait-and-
   switch. */
.gated-section-card.gated-pro .gated-meta {
  margin: -6px 0 16px;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--ink-soft);
  font-style: italic;
  max-width: 620px;
  line-height: 1.5;
}

/* Pre-launch "Pro preview" banner. Injected via a CSS ::before
   pseudo-element on any Pro-section body we've rendered data into
   (loader sets data-pro-preview="true"). Tells the signed-in user
   the section will be paid at launch but their free preview holds
   while we're in beta. */
[data-pro-preview="true"]::before {
  content: "Pro preview — section becomes Pro at launch (£15/mo). Your free access during beta is yours.";
  display: block;
  margin: -4px 0 16px;
  padding: 8px 12px;
  background: var(--paper-tinted);
  border-left: 3px solid var(--accent);
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  line-height: 1.4;
}

/* Pro-upgrade-card: shown post-launch inside a Pro-tier section when the
   signed-in user isn't subscribed to Pro yet. Same chrome as
   .gated-section-card so the visual language matches the signed-out
   gate, but copy + CTA target the pricing page rather than signup. */
.pro-upgrade-card {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  padding: 22px 26px;
  background: var(--paper-tinted);
}
.pro-upgrade-card .eyebrow {
  display: block;
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 10px;
}
.pro-upgrade-card p {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 18px;
  max-width: 620px;
}

/* Per-operator mobile coverage table cells.
   Colour bands match Ofcom's own reporting thresholds for "broadly
   available" coverage so a buyer can scan the four-operator grid at a
   glance and see who delivers the strongest signal at that district. */
.mob-pct {
  text-align: center;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 13.5px;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}
.mob-pct.is-excellent { color: #2F5A2A; background: #E6F0E2; }
.mob-pct.is-good      { color: #6B5A1F; background: #F4EED4; }
.mob-pct.is-partial   { color: #7A5418; background: #F6E9D6; }
.mob-pct.is-weak      { color: #813131; background: #F6D9D9; }

/* Used by the Pro-tier section loaders for a short intro line above the
   kv-list, and for source attribution at the bottom of the card. */
.card-lede {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 14px;
}
.card-meta {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--rule);
  font-family: var(--font-sans);
  font-size: 11px;
  color: var(--ink-soft);
  line-height: 1.5;
}

/* =========================================================== NOISE LEGEND
   The colour-band key under the noise WMS map. Renders the Defra Lden
   bands as small swatches with the dB range and a plain-English
   description of what each band typically means at a postcode. */
.noise-map-wrap {
  border-radius: 0;
  position: relative;
  /* Leaflet's tile rendering can introduce subpixel gaps at certain
     zoom levels — the dark border makes those invisible. */
  overflow: hidden;
}
.noise-map-wrap .leaflet-control-attribution {
  font-size: 9px;
  line-height: 1.3;
}
/* Style the Road/Rail toggle to match Truely's editorial aesthetic
   rather than Leaflet's default chrome. Square corners, ink border,
   small-caps labels. */
.noise-map-wrap .leaflet-control-layers {
  background: #FFFFFF;
  border: 1px solid var(--ink);
  border-radius: 0;
  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
  padding: 8px 10px;
  font-family: var(--font-sans);
}
.noise-map-wrap .leaflet-control-layers-list {
  margin: 0;
}
.noise-map-wrap .leaflet-control-layers label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
  margin: 2px 0;
  white-space: nowrap;
}
.noise-map-wrap .leaflet-control-layers-selector {
  margin-right: 6px;
  vertical-align: -1px;
  accent-color: var(--accent);
}
.noise-map-wrap .leaflet-control-layers-separator {
  border-top: 1px solid var(--rule);
  margin: 4px 0;
}
.noise-legend {
  background: var(--paper-tinted, #EFEAE0);
  border: 1px solid var(--rule);
  padding: 12px 14px;
  margin: 6px 0 8px;
  border-radius: 0;
}
.noise-legend-title {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 8px;
}
.noise-legend-rows {
  display: grid;
  grid-template-columns: auto auto 1fr;
  column-gap: 10px;
  row-gap: 5px;
  align-items: baseline;
}
.noise-legend-row {
  display: contents;
}
.noise-swatch {
  width: 14px;
  height: 14px;
  display: inline-block;
  border-radius: 0;
  align-self: center;
  flex: 0 0 auto;
}
.noise-band {
  font-family: var(--font-sans);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  letter-spacing: -0.005em;
}
.noise-desc {
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--ink-soft);
  line-height: 1.45;
}
.noise-legend-foot {
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--rule);
  font-family: var(--font-sans);
  font-size: 10.5px;
  line-height: 1.5;
  color: var(--ink-soft);
}
@media (max-width: 600px) {
  .noise-legend-rows {
    /* Stack on narrow viewports — keep band+swatch on one row,
       desc on the next. */
    grid-template-columns: auto auto;
  }
  .noise-desc { grid-column: 1 / -1; padding-left: 24px; margin-bottom: 4px; }
}

.report-hero .crumb {
  font-family: var(--font-sans);
  font-size: 11px; color: var(--ink-soft);
  margin-bottom: 18px;
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  font-weight: 500; letter-spacing: 0.16em; text-transform: uppercase;
}
.report-hero .crumb a {
  color: var(--ink-soft); text-decoration: none;
  padding: 0 4px;
  background-image: none;
  transition: color 0.15s ease;
}
.report-hero .crumb a:hover { color: var(--accent); }
.report-hero .crumb-sep { color: var(--ink-soft); opacity: 0.6; }
.report-hero .crumb-current {
  color: var(--ink); font-weight: 500;
  padding: 0 4px;
  font-feature-settings: "tnum", "lnum";
  letter-spacing: 0.04em;
}

.report-hero .crumb { margin-bottom: 12px; }
/* Mobile crumb: shrink font + tracking so long borough names like
   "Bristol, City of" don't push the postcode to a second line.
   On very narrow viewports the breadcrumb is allowed to wrap naturally
   (flex-wrap is already on) but the threshold for wrapping is moved
   from ~360px to ~280px which covers every modern phone. */
@media (max-width: 600px) {
  .report-hero .crumb {
    font-size: 10px;
    letter-spacing: 0.10em;
    gap: 3px;
    margin-bottom: 10px;
  }
  .report-hero .crumb a,
  .report-hero .crumb-current { padding: 0 2px; }
  .report-hero .crumb-current { letter-spacing: 0.02em; }
}

.report-hero h1 {
  font-family: var(--font-display);
  font-size: clamp(40px, 6.4vw, 68px);
  margin: 0;
  letter-spacing: -0.025em;
  line-height: 0.98;
  color: var(--ink);
  font-feature-settings: "tnum", "lnum", "ss01";
  font-weight: 400;
}
.report-hero .byline {
  font-family: var(--font-display); font-style: italic;
  font-size: 13px; color: var(--ink-soft);
  margin-top: 8px;
  letter-spacing: 0;
  display: inline-block;
}
.report-hero .dateline {
  font-family: var(--font-display); font-style: italic;
  font-size: 13px; color: var(--ink-soft);
  margin-top: 0;
  display: inline-block;
}
.report-hero .byline + .dateline::before {
  content: "·"; margin: 0 8px; color: var(--rule);
}
.report-hero .subline {
  /* v3: roman; the subline can run two lines on long borough names. */
  font-family: var(--font-display);
  color: var(--ink-soft); margin-top: 6px;
  font-size: 13.5px;
  max-width: 760px;
}
/* Headline takeaway — one-sentence synthesis of the postcode that sits
   directly under the ward · borough · addresses subline. Italic Fraunces
   in ink (not ink-soft) so it carries weight without out-shouting the H1.
   Hidden by default; postcode.js's setHeroSummary() reveals it once we
   have enough section data to compose a non-trivial sentence. */
.report-hero .hero-summary {
  display: none;
  font-family: var(--font-display); font-style: italic;
  font-size: 18px; line-height: 1.4;
  color: var(--ink);
  margin-top: 12px;
  max-width: 720px;
}
.report-hero .hero-summary.is-shown { display: block; }
@media (max-width: 720px) {
  .report-hero .hero-summary { font-size: 16px; max-width: none; }
}

/* ─── SEO summary block ─────────────────────────────────────────────
   Visible to crawlers AND visible to humans — both required (hiding
   via display:none triggers Google's hidden-text spam filter; tiny
   font or off-screen positioning is greyer area). Styled as a quiet
   editorial footer below the hero. The render-postcode.js edge
   function injects per-postcode content (postcode + borough) into the
   #seo-summary element before serving — so each page has unique,
   substantive body text in the initial HTML response, not a JS-
   rendered skeleton. This is the primary fix for "Discovered –
   currently not indexed" in GSC. */
.report-hero .seo-summary {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
  /* Desktop default — span more of the empty hero space below the sublines.
     The Save/Share/PDF buttons sit above this row, so widening here doesn't
     collide with them. Mobile override below clamps back to a narrow column. */
  max-width: 1100px;
}
/* Mobile: smaller and tighter — the text is dense by design (substantive
   prose so crawlers don't see a thin page), but at 14px italic on a
   narrow viewport it dominates the hero. Drop to 11.5px with tighter
   leading so it reads as a quiet contextual footer. */
@media (max-width: 720px) {
  .report-hero .seo-summary {
    font-size: 11.5px;
    line-height: 1.5;
    margin-top: 14px;
    padding-top: 10px;
  }
}
/* Desktop only: extend BOTH the hero-summary (italic editorial line)
   and the seo-summary so they fill more of the empty space beside the
   title block. Both sit BELOW the absolutely-positioned Save/Share/PDF
   action buttons in the upper right, so widening their max-width is
   safe — there's no overlap. The .inner allows up to 1240px; we cap
   at 1080px so the right edge still has a margin of breathing room.
   Mobile rule below 720px is untouched. */
@media (min-width: 960px) {
  .report-hero .hero-summary { max-width: 1080px; }
  .report-hero .seo-summary  { max-width: 1080px; }
}
/* Inline-text "chips" — thin separators between geographical facts.
   No card chrome. Used by postcode.js to render lat/lng + ward etc. */
.chip-row {
  display: flex; gap: 0; flex-wrap: wrap;
  margin-top: 14px;
  font-family: var(--font-sans);
  font-size: 12.5px; color: var(--ink-soft);
  letter-spacing: 0.04em;
  align-items: baseline;
}
.chip {
  padding: 0;
  background: transparent; border: none;
  border-radius: 0;
  font-size: 12.5px; color: var(--ink-soft);
  display: inline-flex; align-items: baseline;
}
.chip + .chip::before {
  content: "·";
  margin: 0 10px;
  color: var(--rule);
}
.chip strong {
  color: var(--ink-soft); font-weight: 500;
  font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  margin-right: 6px;
  font-family: var(--font-sans);
}
.chip span, .chip .val {
  font-family: var(--font-sans);
  color: var(--ink); font-weight: 500;
  font-feature-settings: "tnum", "lnum";
}

/* =========================================================== REPORT SUBNAV
   On desktop the subnav is hidden in favour of the sticky TOC sidebar.
   On tablet/mobile it shows as a sticky horizontal tab bar. */
.report-subnav {
  position: sticky; top: var(--header-h); z-index: 30;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}
.report-subnav .inner {
  max-width: 1240px; margin: 0 auto;
  display: flex; align-items: center; gap: 16px;
  padding: 0 24px;
  height: 52px;
}
.report-subnav-tabs {
  display: flex; gap: 0; overflow-x: auto;
  scrollbar-width: none;
  flex: 1; min-width: 0; align-items: center; height: 100%;
  /* CSS-level smooth scroll for the JS-driven auto-centre on desktop.
     iOS Safari silently drops scrollLeft assignments on this rail
     because the ancestor (.report-subnav) is position:sticky — there's
     nothing we can do about that without restructuring the DOM, so we
     ship the working desktop behaviour and let iOS users swipe the
     rail manually. The active-tab highlight still tracks scroll on
     iOS, so users can tell which section they're in. */
  scroll-behavior: smooth;
}
.report-subnav-tabs::-webkit-scrollbar { display: none; }
.report-subnav-tabs a {
  height: 100%; display: inline-flex; align-items: center;
  font-family: var(--font-sans);
  /* v4 May 2026: dropped opacity:0.65 — the warm dark sepia --ink-soft
     gives inactive presence without the perceived-grey blur that an
     opacity-stacked ink had. */
  font-size: 11px; color: var(--ink-soft);
  padding: 0 14px;
  border-radius: 0; white-space: nowrap; text-decoration: none;
  letter-spacing: 0.14em; text-transform: uppercase;
  font-weight: 600;
  border-bottom: 2px solid transparent;
  transition: color 0.18s ease, opacity 0.18s ease, border-color 0.18s ease;
  background-image: none;
}
.report-subnav-tabs a:hover { opacity: 1; color: var(--ink); }
.report-subnav-tabs a.active {
  color: var(--accent); border-bottom-color: var(--accent);
  opacity: 1;
}

/* Compact inline search inside the sticky subnav.
   v3 May 2026 — pill shape + softer focus to match the modernised
   header-search above. */
.search-card-inline {
  display: flex; align-items: center;
  height: 38px; width: 280px; max-width: 38%;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0 6px 0 36px;
  position: relative;
  flex-shrink: 0;
  margin: 0;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.search-card-inline:hover {
  border-color: var(--ink-soft);
}
.search-card-inline:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(200, 75, 49, 0.10);
}
.search-card-inline .search-icon { left: 12px; top: 50%; transform: translateY(-50%); }
.search-card-inline input {
  flex: 1; min-width: 0; height: 100%;
  font-family: var(--font-sans);
  font-size: 12.5px; font-weight: 500;
  padding: 0 4px 0 0; background: transparent;
  border: 0; outline: none; color: var(--ink);
  letter-spacing: 0.06em; font-feature-settings: "tnum", "lnum";
}
.search-card-inline input::placeholder { color: var(--ink-soft); font-weight: 400; }
.search-card-inline .btn-primary {
  /* v3: round button to match the pill shell */
  height: 28px; min-height: 28px; min-width: 28px; width: 28px; padding: 0; margin: 0;
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--ink); color: var(--paper);
  border-color: var(--ink);
}
.search-card-inline .btn-primary:hover { background: var(--accent); border-color: var(--accent); }
.search-card-inline .btn-primary svg { width: 12px; height: 12px; }
.search-card-inline .suggest-list { top: calc(100% + 4px); right: 0; left: auto; width: 320px; }

@media (max-width: 760px) {
  .search-card-inline { display: none; }
  .report-subnav .inner { gap: 0; padding: 0 16px; }
}

@media (min-width: 1080px) {
  .report-subnav { display: none; }
}

/* =========================================================== REPORT GRID
   Two-column with sticky TOC sidebar on desktop. */
.report-grid {
  max-width: 1320px; margin: 0 auto; padding: 18px 24px 18px;
  display: block;
}
@media (min-width: 1080px) {
  .report-grid {
    display: grid;
    grid-template-columns: 200px minmax(0, 1fr);
    gap: 44px;
    align-items: start;
    padding: 22px 24px 18px;
  }
}

.report-toc { display: none; }
@media (min-width: 1080px) {
  .report-toc {
    display: block;
    position: sticky;
    top: calc(var(--header-h) + 32px);
    align-self: start;
    padding-top: 24px;
    /* v6 May 2026: 25-item TOC overflows a typical laptop viewport. Cap
       the panel's height to (viewport height − header − a little headroom)
       and let it scroll internally so every section is one click away
       without page-scrolling. Hide the scrollbar gutter when there's no
       overflow by making the bar thin and only visible on hover. */
    max-height: calc(100vh - var(--header-h) - 64px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--rule) transparent;
  }
  .report-toc::-webkit-scrollbar { width: 5px; }
  .report-toc::-webkit-scrollbar-thumb {
    background: var(--rule); border-radius: 999px;
  }
  .report-toc::-webkit-scrollbar-thumb:hover { background: var(--ink-soft); }
  .report-toc .toc-eyebrow {
    font-family: var(--font-sans);
    font-size: 11px; font-weight: 500;
    color: var(--ink-soft);
    letter-spacing: 0.2em; text-transform: uppercase;
    margin: 0 0 14px;
  }
  .report-toc nav { display: flex; flex-direction: column; gap: 0; }
  .report-toc a {
    position: relative;
    display: block;
    /* v6 May 2026: tighter vertical padding so the 25-item list packs
       closer and most visitors see the whole thing without scrolling
       the TOC panel at all. Was 4px 0 4px 14px. */
    padding: 3px 0 3px 14px;
    /* v5 May 2026: switched from Fraunces to Inter. Fraunces at small
       sizes in a 19-item vertical list created perceived greyness and
       a dated feel that didn't match the modern editorial elsewhere.
       Inter weight 500 scans cleanly and tracks better at 200px width.
       Brand voice is now carried by the H1/H2 Fraunces displays, not
       by the navigation. */
    font-family: var(--font-sans);
    font-size: 15px; font-weight: 500;
    line-height: 1.55;
    letter-spacing: -0.005em;
    /* v4 May 2026: dropped opacity:0.7 stack — even the new warm ink at
       0.7 read as grey. Use --ink-soft (warm dark sepia) directly. */
    color: var(--ink-soft);
    text-decoration: none;
    transition: color 0.15s ease, font-weight 0.15s ease;
    background-image: none;
    /* Long entries (Broadband & connectivity, Property valuation) need to
       wrap rather than truncate at the 200px sidebar width. */
    white-space: normal;
    overflow-wrap: anywhere;
  }
  .report-toc a:hover { color: var(--ink); }
  /* Active item: bold weight + accent ›-prefix sitting in the gutter so
     the text itself nudges 6px right when active without reflowing the
     column. The ::before is absolutely positioned in the reserved 14px
     left padding. */
  .report-toc a.active {
    color: var(--accent);
    font-weight: 600;
    padding-left: 20px;
  }
  .report-toc a.active::before {
    content: "›";
    position: absolute;
    left: 0; top: 4px;
    color: var(--accent);
    font-weight: 700;
    font-style: normal;
    line-height: 1.6;
  }
  /* Search-another pill at the TOP of the TOC — small accent button.
     Replaces the old bottom .toc-search link which buried the action. */
  .report-toc .toc-pill-search {
    display: inline-flex; align-items: center; gap: 7px;
    margin-bottom: 18px;
    padding: 7px 12px;
    background: transparent;
    border: 1px solid var(--accent);
    color: var(--accent);
    border-radius: 0;
    font-family: var(--font-sans);
    font-size: 10.5px; font-weight: 500;
    letter-spacing: 0.16em; text-transform: uppercase;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
  }
  .report-toc .toc-pill-search:hover {
    background: var(--accent); color: #FFF;
  }
  .report-toc .toc-pill-search svg { stroke: currentColor; }
}
/* Hide the TOC pill below desktop — the persistent header-search covers
   small viewports. (Sidebar itself is hidden <1080 by .report-toc rule.) */
@media (max-width: 1079px) {
  .report-toc .toc-pill-search { display: none; }
}

/* =========================================================== INNER-PAGE HEROES */
.page-hero {
  position: relative;
  padding: 44px 24px 28px;
  margin-bottom: 0;
  background: var(--paper-tinted);
  border-bottom: 1px solid var(--rule);
}
.page-hero .inner { max-width: 1240px; margin: 0 auto; }
.page-hero .eyebrow {
  display: inline-block;
  padding: 0;
  font-family: var(--font-sans);
  /* v2 Apr 2026: matches the new section-eyebrow scale (12px/600/0.14em
     darker color) so the kicker on About / Methodology / Coverage hero
     reads with the same weight as section eyebrows on the report. */
  font-size: 12px; font-weight: 600;
  color: var(--ink);
  letter-spacing: 0.14em; text-transform: uppercase;
  margin-bottom: 14px;
  background: transparent; border: none;
}
.page-hero h1 {
  font-family: var(--font-display);
  font-size: clamp(36px, 4.8vw, 60px);
  letter-spacing: -0.02em; font-weight: 400;
  color: var(--ink);
  margin: 0; line-height: 1.04;
}
.page-hero .lede {
  /* v4 May 2026: switched from Fraunces to Inter to match site-wide
     body-copy modernisation. The H1 above stays in Fraunces; the lede
     paragraph reads cleaner in sans, especially at small viewports. */
  font-size: clamp(15px, 1.5vw, 18px);
  color: var(--ink-soft); max-width: 720px;
  margin-top: 14px; line-height: 1.6;
  font-weight: 450;
}
@media (max-width: 700px) { .page-hero { padding: 32px 18px 22px; } }

/* Snapshot disclosure (Why might a postcode show no sales?) */
.snapshot-hint {
  margin-top: 28px;
  padding: 18px 20px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  font-family: var(--font-display); font-size: 14.5px;
  color: var(--ink-soft);
  line-height: 1.6;
}
.snapshot-hint summary {
  cursor: pointer;
  font-family: var(--font-sans); font-weight: 500;
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink);
  list-style: none;
  position: relative;
  padding-right: 26px;
  outline: none;
}
.snapshot-hint summary::-webkit-details-marker { display: none; }
.snapshot-hint summary::after {
  content: "+";
  position: absolute; right: 2px; top: 50%;
  transform: translateY(-52%);
  font-family: var(--font-display); font-size: 18px; font-weight: 400;
  color: var(--ink-soft);
  line-height: 1;
}
.snapshot-hint[open] summary::after { content: "−"; }
.snapshot-hint summary:focus-visible { color: var(--accent); }
.snapshot-hint ul { margin: 14px 0 2px; padding-left: 20px; }
.snapshot-hint li { margin-bottom: 6px; }
.snapshot-hint li:last-child { margin-bottom: 0; }
.snapshot-hint strong { color: var(--ink); font-weight: 500; }

/* =========================================================== STATES */
.empty-state {
  text-align: left; padding: 28px 24px;
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-radius: 0;
  background: transparent;
  font-family: var(--font-display); font-style: italic;
  font-size: 15.5px; line-height: 1.6;
}
.empty-state strong { color: var(--ink); font-weight: 500; }

.empty-illustration {
  width: 48px; height: 48px; margin: 0 auto 16px;
  border-radius: 0;
  background: transparent;
  border: 1px solid var(--accent);
  display: flex; align-items: center; justify-content: center;
  color: var(--accent);
}
.empty-card {
  text-align: center; padding: 64px 28px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0;
}
.empty-card h2 {
  font-family: var(--font-display);
  font-size: 32px; font-weight: 400;
  letter-spacing: -0.02em; color: var(--ink);
  margin: 0 0 14px;
}
.empty-card p {
  font-family: var(--font-display);
  color: var(--ink-soft); max-width: 580px; margin: 0 auto;
  line-height: 1.62; font-size: 17px;
}
.empty-card .actions {
  display: inline-flex; gap: 10px; flex-wrap: wrap;
  justify-content: center; margin-top: 26px;
}

.loading {
  font-family: var(--font-display); font-style: italic;
  color: var(--ink-soft); font-size: 14.5px;
  padding: 28px; text-align: center;
  /* Subtle skeleton shimmer: a soft grey block with a slow opacity pulse
     so empty cards feel intentional during the data round-trip. The
     text inside ("Loading recent sales…", etc.) still tells the user
     what's coming. Reduced motion users get the static block without
     the pulse. */
  background: linear-gradient(90deg,
    rgba(0, 0, 0, 0.025) 0%,
    rgba(0, 0, 0, 0.045) 50%,
    rgba(0, 0, 0, 0.025) 100%);
  background-size: 200% 100%;
  border-radius: 8px;
  animation: skeleton-shimmer 2.4s ease-in-out infinite;
}
/* Table-row variant: don't introduce a rounded background inside <td>
   — it'd clip awkwardly. Just keep the italic text + a soft pulse. */
tr td.loading,
tr td > .loading {
  background: none;
  border-radius: 0;
  animation: skeleton-pulse 2.4s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: -100% 50%; }
}
@keyframes skeleton-pulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 0.95; }
}
@media (prefers-reduced-motion: reduce) {
  .loading { animation: none; }
}
.error {
  font-family: var(--font-sans);
  color: var(--accent); font-size: 13px;
  letter-spacing: 0.04em;
  padding: 20px; text-align: center;
}

/* =========================================================== COVERAGE PILL */
.coverage-pill {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 0;
  background: transparent; color: var(--ink-soft);
  border: none;
  border-radius: 0;
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.20em; text-transform: uppercase;
  margin-bottom: 28px;
}
.coverage-pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}

/* =========================================================== SOURCE CARD */
.source-card {
  display: grid; grid-template-columns: auto 1fr auto;
  gap: 16px; align-items: center;
  padding: 18px 20px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0;
  margin-bottom: 0;
  transition: border-color 0.18s ease;
  background-image: none;
}
.source-card:hover { border-color: var(--ink); }
.source-card .source-icon {
  width: 36px; height: 36px; border-radius: 0;
  background: transparent; color: var(--ink);
  border: 1px solid var(--rule);
  display: flex; align-items: center; justify-content: center;
}
.source-card h4 { margin: 0; font-family: var(--font-display); font-size: 17px; font-weight: 400; letter-spacing: -0.01em; }
.source-card p { margin: 2px 0 0; font-family: var(--font-display); font-style: italic; font-size: 14px; color: var(--ink-soft); }

/* =========================================================== LEGAL PROSE */
.legal-prose {
  display: flex; flex-direction: column;
  gap: 0;
  margin-top: 32px;
  max-width: 720px;
}
.legal-prose .card {
  padding: 32px 0;
  border-left: none; border-right: none;
  border-bottom: 1px solid var(--rule);
}
.legal-prose .card:first-child { border-top: 1px solid var(--rule); }
.legal-prose h2 {
  font-family: var(--font-display);
  font-size: clamp(24px, 2.6vw, 32px);
  font-weight: 400; letter-spacing: -0.018em;
  margin: 0 0 16px;
  color: var(--ink);
}
.legal-prose p {
  font-family: var(--font-display);
  font-size: 17px; line-height: 1.65;
  color: var(--ink);
  margin: 0 0 14px;
}
.legal-prose p:last-child { margin-bottom: 0; }
.legal-prose a { color: var(--accent); }
.legal-prose a:hover { color: var(--accent-deep); }
.legal-list {
  list-style: none;
  padding: 0; margin: 0 0 14px;
}
.legal-list li {
  position: relative;
  padding: 12px 0 12px 18px;
  font-family: var(--font-display); font-size: 16px;
  color: var(--ink);
  line-height: 1.6;
  border-top: 1px solid var(--rule);
}
.legal-list li:first-child { border-top: none; }
.legal-list li::before {
  content: "›"; position: absolute;
  left: 0; top: 12px;
  color: var(--accent);
  font-family: var(--font-display);
}
.legal-list li strong { color: var(--ink); font-weight: 500; }
.legal-meta {
  font-size: 12.5px;
  color: var(--ink-soft);
  font-style: italic;
}
.legal-prose code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--paper-tinted);
  padding: 2px 7px;
  border-radius: 0;
  border: 1px solid var(--rule);
  color: var(--ink);
  white-space: nowrap;
}

/* =========================================================== FEATURE STRIP (about) */
.feature-strip { padding: 60px 24px; }
.feature-strip h2 { text-align: left; margin: 0 0 14px; }
.feature-strip .sub {
  font-family: var(--font-display); font-style: italic;
  color: var(--ink-soft);
  max-width: 720px; margin: 0 0 40px;
}
.feature-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 0; max-width: 1240px; margin: 0 auto;
}
@media (max-width: 800px) { .feature-grid { grid-template-columns: 1fr; } }
.feature {
  padding: 32px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-right: none;
  border-radius: 0;
  transition: background 0.2s ease;
}
.feature:last-child { border-right: 1px solid var(--rule); }
.feature:hover { background: var(--paper-tinted); }
.feature-icon {
  width: 36px; height: 36px;
  background: transparent; color: var(--accent);
  border: 1px solid var(--accent);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 18px;
}
.feature h3 { font-size: 21px; margin: 0 0 10px; font-weight: 400; letter-spacing: -0.014em; }
.feature p { font-family: var(--font-display); font-size: 16px; color: var(--ink-soft); margin: 0; line-height: 1.6; }

/* =========================================================== EDITORIAL UTILITIES */

/* Drop cap on the opening paragraph of a section that has class .editorial */
.editorial p:first-of-type::first-letter {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 5.4em;
  line-height: 0.85;
  float: left;
  margin: 0.05em 0.08em 0 0;
  color: var(--ink);
  font-feature-settings: "ss01";
}
.editorial p { font-size: 18px; line-height: 1.65; max-width: 680px; }
.editorial p strong { font-weight: 500; }

/* Pull-quote: a single bold lead-stat per section, set in display Fraunces
   with a thick accent left rule. Markup is:
     <blockquote class="pullquote"><p>…</p><cite>source</cite></blockquote> */
.pullquote {
  margin: 28px 0;
  padding: 6px 0 6px 28px;
  border-left: 4px solid var(--accent);
  background: transparent;
}
.pullquote p {
  font-family: var(--font-display); font-style: italic;
  font-size: clamp(22px, 2.4vw, 28px);
  line-height: 1.32;
  color: var(--ink);
  margin: 0;
  font-weight: 400;
  letter-spacing: -0.012em;
}
.pullquote cite {
  display: block;
  margin-top: 12px;
  font-family: var(--font-sans); font-style: normal;
  font-size: 10.5px; font-weight: 500;
  color: var(--ink-soft);
  letter-spacing: 0.18em; text-transform: uppercase;
}

/* =========================================================== SCANABILITY LAYER
   Three additive elements that sit ON TOP of the editorial chrome:
     · .section-summary  — italic Fraunces "what this means" line under the H2
     · .eyebrow-rating   — small Inter-caps grade chip appended to the eyebrow
     · .context-bar      — 60px tri-dot bar showing UK / borough / postcode
   Editorial style (Fraunces, drop caps, pull-quotes, roman numerals) is
   untouched — the new chrome is data-derived and JS-injected on demand. */

.section-summary {
  /* v5 May 2026: restored italic Fraunces — testers wanted more visual
     life on the page after the body-copy modernisation. The accent
     left-rule + italic serif is a single editorial flourish per section
     (the "key takeaway"), not a return to body italics. Body copy stays
     upright Inter; only this one line per section sings. */
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  margin: 14px 0 0;
  padding-left: 14px;
  border-left: 3px solid var(--accent);
  max-width: 640px;
}
.section-summary.is-pending {
  color: var(--ink-soft);
  border-left-color: var(--rule);
}

/* Rating chips are now <button> elements (tap-to-explain popover).
   Strip the browser's default button chrome so they still look like
   the inline eyebrow chip / mobile pill, not a clickable button. */
button.eyebrow-rating {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  line-height: inherit;
}
button.eyebrow-rating:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 999px;
}

/* Rating-popover — appears on tap (mobile) or click (desktop) over a
   .eyebrow-rating chip to explain the inputs that drove the grade.
   Positioned absolutely from window scroll coords by JS; CSS handles
   the styling + above/below flip. */
.rating-popover {
  position: absolute;
  z-index: 200;
  max-width: 320px;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 12px 14px;
  font-family: var(--font-sans);
  font-size: 13.5px;
  line-height: 1.5;
  box-shadow: 0 6px 20px rgba(26, 22, 18, 0.10), 0 2px 6px rgba(26, 22, 18, 0.06);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.16s var(--ease-out), transform 0.16s var(--ease-out);
  pointer-events: auto;
}
.rating-popover.rating-popover-above {
  transform: translateY(4px);
}
.rating-popover.rating-popover-visible {
  opacity: 1;
  transform: translateY(0);
}
@media (max-width: 480px) {
  .rating-popover { max-width: calc(100vw - 24px); font-size: 13px; }
}

.eyebrow-rating {
  font-family: var(--font-sans);
  font-weight: 600;
  /* v2 Apr 2026: bumped from 10.5px/0.18em to 11.5px/0.14em to match the
     new section-eyebrow scale. The "X/10 LABEL" chip needs to read as
     equal-weight kicker chrome, not a faint annotation. */
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  margin-left: 8px;
  padding-left: 8px;
  border-left: 1px solid var(--rule);
  white-space: nowrap;
  cursor: help;
}
.eyebrow-rating .er-score {
  color: var(--accent);
  font-variant-numeric: tabular-nums lining-nums;
}
.eyebrow-rating.er-grade-a .er-score,
.eyebrow-rating.er-grade-b .er-score { color: #3F6E4A; }
.eyebrow-rating.er-grade-c .er-score { color: #A8A04D; }
.eyebrow-rating.er-grade-d .er-score { color: #C99445; }
.eyebrow-rating.er-grade-e .er-score,
.eyebrow-rating.er-grade-f .er-score,
.eyebrow-rating.er-grade-g .er-score { color: var(--accent); }

/* Property-valuation eyebrow chip — louder than the standard rating chip
   because the AVM is the only ESTIMATE on the report. Accent border,
   accent text, no left rule (replaced with a full pill). */
.eyebrow-rating.er-estimate {
  margin-left: 10px;
  padding: 2px 8px;
  border: 1px solid var(--accent);
  border-left: 1px solid var(--accent);
  color: var(--accent);
  background: transparent;
  font-weight: 600;
  letter-spacing: 0.16em;
}
.eyebrow-rating.er-estimate .er-score {
  color: var(--accent);
}

/* Valuation section — confidence chip + headline range layout. */
.valuation-confidence {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-sans); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.18em; text-transform: uppercase;
  padding: 4px 10px;
  border: 1px solid var(--rule);
  border-radius: 0;
  cursor: help;
}
.valuation-confidence.is-high   { color: #3F6E4A; border-color: #3F6E4A; }
.valuation-confidence.is-medium { color: #C99445; border-color: #C99445; }
.valuation-confidence.is-low    { color: var(--accent); border-color: var(--accent); }
.valuation-range {
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--font-display);
  font-size: clamp(28px, 3vw, 38px); line-height: 1;
  font-feature-settings: "tnum", "lnum";
  margin: 8px 0 4px;
}
.valuation-range .v-mid { font-size: clamp(40px, 4.2vw, 56px); color: var(--ink); }
.valuation-range .v-lohi { color: var(--ink-soft); }
.valuation-range .v-sep { color: var(--rule); padding: 0 2px; }
.valuation-method-note {
  font-family: var(--font-display); font-style: italic;
  font-size: 14px; line-height: 1.6;
  color: var(--ink-soft);
  margin: 0 0 14px;
}
.valuation-disclaimer {
  margin-top: 14px;
  font-family: var(--font-sans);
  font-size: 12.5px; color: var(--ink-soft);
  line-height: 1.6;
  padding: 10px 14px;
  border-left: 2px solid var(--accent);
  background: var(--paper-tinted);
}
.valuation-also {
  margin-top: 12px;
  font-family: var(--font-display); font-style: italic;
  font-size: 13.5px; color: var(--ink-soft);
}

/* Tenure / household mix bars — segmented horizontal bar with labelled
   segments below. Used by the Demographics section. */
.mix-bar {
  display: flex; height: 14px;
  border: 1px solid var(--rule);
  margin: 6px 0 10px;
  overflow: hidden;
}
.mix-bar > span { display: block; height: 100%; }
.mix-bar > span.s1 { background: var(--ink); }
.mix-bar > span.s2 { background: var(--ink-soft); }
.mix-bar > span.s3 { background: var(--accent); opacity: 0.85; }
.mix-bar > span.s4 { background: var(--rule); }
.mix-legend {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 14px;
  font-family: var(--font-sans);
  font-size: 12px; color: var(--ink-soft);
}
.mix-legend > span { display: flex; align-items: baseline; gap: 6px; }
.mix-legend i {
  display: inline-block; width: 8px; height: 8px;
  flex-shrink: 0; transform: translateY(-1px);
}
.mix-legend i.s1 { background: var(--ink); }
.mix-legend i.s2 { background: var(--ink-soft); }
.mix-legend i.s3 { background: var(--accent); opacity: 0.85; }
.mix-legend i.s4 { background: var(--rule); }
.mix-legend .pct {
  margin-left: auto;
  color: var(--ink); font-weight: 500;
  font-feature-settings: "tnum", "lnum";
}

/* Flood-risk band pill — coloured by severity. Reuses the section-rating
   palette so green = good, amber = warning, accent = bad. Larger than
   the kv-list inline pill because the band IS the headline figure. */
.flood-band-pill {
  display: inline-flex; align-items: center;
  padding: 8px 18px;
  border: 1px solid var(--rule); border-radius: 0;
  font-family: var(--font-display);
  font-size: clamp(20px, 2vw, 28px); font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--ink);
  background: var(--paper);
}
.flood-band-pill.is-good    { color: #3F6E4A; border-color: #3F6E4A; }
.flood-band-pill.is-warn    { color: #C99445; border-color: #C99445; }
.flood-band-pill.is-bad     { color: var(--accent); border-color: var(--accent); }
.flood-band-pill.is-neutral { color: var(--ink-soft); }

/* Yes/No pill used by Heritage in_conservation_area + similar. */
.yn-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--rule); border-radius: 0;
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
}
.yn-pill.is-yes { color: var(--accent); border-color: var(--accent); }
.yn-pill.is-no  { color: var(--ink-soft); }

.context-bar {
  --cb-w: 60px;
  --cb-h: 10px;
  position: relative;
  display: inline-block;
  width: var(--cb-w);
  height: var(--cb-h);
  margin-top: 8px;
  vertical-align: middle;
}
.context-bar .cb-track {
  position: absolute; top: 50%; left: 0; right: 0;
  height: 1px; background: var(--rule);
  transform: translateY(-50%);
}
.context-bar .cb-dot {
  position: absolute; top: 50%;
  width: 6px; height: 6px; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--ink-soft);
}
.context-bar .cb-dot.cb-uk      { background: var(--rule); border: 1px solid var(--ink-soft); width: 5px; height: 5px; }
.context-bar .cb-dot.cb-borough { background: var(--ink-soft); }
.context-bar .cb-dot.cb-self    { background: var(--accent); width: 8px; height: 8px; box-shadow: 0 0 0 2px var(--paper); }
.context-bar-legend {
  display: block;
  margin-top: 4px;
  font-family: var(--font-sans);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 500;
}

.lb-context-bar { display: block; margin-top: 6px; }

/* v3 May 2026: removed the obsolete `display:block; padding:0` mobile
   override that pre-dated the v2 chip redesign. The newer rules at the
   bottom of this file (inside @media (max-width: 768px)) are the single
   source of truth for the mobile chip — band-coloured pill with proper
   padding around score + label. Leaving the old rule in place caused
   the score to render outside the pill on some viewports because the
   unique margin/border-left properties leaked through the cascade. */
@media (max-width: 700px) {
  .section-summary { font-size: 13.5px; }
}

/* =========================================================== RESPONSIVE TWEAKS */
@media (max-width: 900px) {
  .site-header .inner { padding: 12px 18px; gap: 14px; }
  .nav-links { gap: 6px; font-size: 12px; }
  .report-subnav .inner { padding: 8px 16px; }
  .report-subnav-tabs a { padding: 0 10px; font-size: 10.5px; }
}
@media (max-width: 700px) {
  .container { padding: 16px; }
  .report-grid { padding: 16px; }
  .site-footer { padding: 32px 18px 22px; }
  .site-footer .inner { grid-template-columns: 1fr 1fr; gap: 22px; }
  .search-card { flex-direction: column; padding: 12px 12px 12px 48px; gap: 8px; }
  .search-card .search-icon { left: 18px; top: 22px; transform: none; }
  .search-card .btn-primary { width: 100%; justify-content: center; }
  .section-head { flex-direction: column; align-items: flex-start; gap: 10px; padding-top: 24px; }
  .section-head-right { flex-wrap: wrap; }
  .section-head-left h2 { font-size: 22px; }
  .grid-2, .grid-3 { gap: 14px; }
  .data-table { font-size: 14px; }
  .data-table th, .data-table td { padding: 10px 12px; }
  .report-hero { padding: 18px 16px 16px; }
  .report-hero h1 { font-size: 38px; }
  .report-hero-actions { top: 10px; right: 12px; }
  .chip-row { gap: 0; margin-top: 8px; flex-direction: column; }
  .chip + .chip::before { display: none; }
  .chip { padding: 4px 0; border-bottom: 1px solid var(--rule); width: 100%; }
  .chip:last-child { border-bottom: none; }
  .fact-row { grid-template-columns: 1fr; gap: 10px 0; }
  .pullquote { padding-left: 18px; margin: 18px 0; }
  .pullquote p { font-size: 18px; }
  .compare-link { font-size: 10px; margin-top: 12px; }
}
@media (max-width: 480px) {
  .site-footer .inner { grid-template-columns: 1fr; }
}
@media (max-width: 380px) {
  .report-hero h1 { font-size: 48px; letter-spacing: -0.02em; }
  .stat-card { padding: 22px 22px 20px; min-height: 200px; }
  .stat-card .stat-num,
  .headline-card .hc-num { font-size: 56px; }
  .nav-links a { padding: 12px 10px; font-size: 13px; }
}

/* Tap-target floor on mobile — every interactive element should be at least
   ~44px on the touch axis (WCAG 2.5.5). Padding bumps on the compact pills
   and chevron buttons that the editorial design otherwise keeps tight. */
@media (max-width: 720px) {
  .show-more { padding: 12px 14px; }
  .compare-link { padding: 10px 0; display: inline-block; }
  .recent-pill { padding: 9px 14px; }
  .recent-clear { padding: 9px 12px; }
  .hero-cta-hint a { display: inline-block; padding: 6px 4px; }
  /* The snapshot leaderboard becomes a 1-column horizontal swipe at ≤720px
     (rule already in this file). With 16 tiles + scroll-snap, the swipe
     affordance can be invisible on first paint. Add a faint accent shadow
     on the right edge to hint at the hidden columns. */
  .leaderboard {
    -webkit-mask-image:
      linear-gradient(to right, #000 calc(100% - 24px), transparent 100%);
            mask-image:
      linear-gradient(to right, #000 calc(100% - 24px), transparent 100%);
  }
}

/* =====================================================================
   MOBILE REDESIGN 2026 — bundled below this comment.
   Everything in this block is gated by @media (max-width: 768px) or
   (max-width: 600px). Desktop layout is intentionally untouched.

   Scope:
     P0.1  burger menu X close button visibility
     P0.2  sticky "Sections" pill + full-screen drawer (postcode page)
     P0.3  snapshot leaderboard 2-column density + section accordions
     P0.4  postcode header stack (H1 below action row)
     P0.5  hero + search-card density
     P0.7  trust-strip 2-column grid
   ===================================================================== */

/* ---- P0.1 (v7): burger nav-toggle visibility ----------------------
   Pad the header right edge by max(18px, safe-area-inset-right) so
   the open-state X (drawn by the existing nav-toggle) never sits flush
   with an iPhone notch in landscape. flex-shrink: 0 on the button so
   the flex layout can't squeeze it below 44×44. */
@media (max-width: 768px) {
  .site-header .inner {
    padding-right: max(18px, env(safe-area-inset-right) + 4px);
    padding-left:  max(18px, env(safe-area-inset-left)  + 4px);
  }
  .nav-toggle {
    width: 44px; height: 44px;
    flex-shrink: 0;
    color: var(--ink);
    border-color: var(--rule);
    margin-right: 0;
  }
  .nav-toggle[aria-expanded="true"] {
    color: var(--ink);
    border-color: var(--ink);
    background: var(--paper);
  }
  /* Bump stroke weight on the rotated bars so the X reads from a metre
     away, not as two faint hairlines. */
  .nav-toggle[aria-expanded="true"] svg { stroke-width: 2.8; }
}

/* ---- P0.3 (v8): dedicated drawer close button --------------------
   Four rounds of "the X is clipped" reports — this time we stop trying
   to fix nav-toggle's flex-layout-positioned X and instead inject a
   second close button inside the drawer itself, with explicit absolute
   positioning that cannot be squeezed by any header flex behaviour.
   The original .nav-toggle still flips to its X-state for users who
   tap-twice on the same button; this button is for users whose finger
   travels into the drawer to close it. !important defeats any cascade
   conflict from earlier attempts. */
.m-burger-close { display: none; }
@media (max-width: 760px) {
  .site-header .nav-links {
    /* The drawer is the positioning context for the absolutely-
       positioned close button. Bump the top padding so the first nav
       link doesn't sit underneath the close button. */
    position: fixed;
    padding-top: 64px !important;
  }
  /* When drawer is open, hide the hamburger (which CSS-animates to an X).
     The .m-burger-close inside the drawer is the only visible close button.
     Without this, users see two X buttons stacked at top-right. */
  body.menu-open .site-header .nav-toggle {
    display: none !important;
  }
  .m-burger-close {
    position: absolute !important;
    top: 12px !important;
    right: max(16px, env(safe-area-inset-right) + 4px) !important;
    width: 44px !important;
    height: 44px !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
    background: var(--paper) !important;
    border: 1px solid var(--rule) !important;
    color: var(--ink) !important;
    border-radius: 4px !important;
    padding: 0 !important;
    margin: 0 !important;
    z-index: 9999 !important;
    cursor: pointer;
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0.05);
  }
  .m-burger-close svg {
    display: block;
    width: 20px;
    height: 20px;
  }
  .m-burger-close:active {
    background: var(--paper-tinted) !important;
  }
}

/* ---- P0.2 (v7): frosted glass sticky tab bar ----------------------
   Six prior tracker patterns failed (sticky pill → bottom sheet →
   native <select> → custom overlay → fixed overlay → <details> TOC).
   v7 abandons collapsible/modal patterns entirely. Instead: a sticky
   horizontal-scroll tab bar at the top of #report-body, frosted glass
   over the page that's scrolling under it. The bar is just <a href="#id">
   anchors plus an absolutely-positioned indicator the JS slides between
   the active tab. Failure modes: if JS dies, anchors still navigate; if
   IntersectionObserver fires late, only the indicator hesitates — the
   page itself never breaks. Same pattern Apple News, Stripe, and
   Linear use. The desktop sidebar TOC takes over above 1080px. */
.m-tabs { display: none; }

/* =========================================================== M-TABS PROGRESS
   Thin accent-coloured progress bar at the top of the mobile section-nav
   strip. Width grows proportionally as the user scrolls through the
   report. Gives "you are here in 19 sections" feedback. */
.m-tabs-progress {
  height: 2px;
  width: 100%;
  background: transparent;
  overflow: hidden;
  flex-shrink: 0;
}
.m-tabs-progress-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.12s linear;
  will-change: width;
}

@media (max-width: 768px) {
  /* The desktop horizontal-pill subnav stays hidden on mobile; the
     frosted tab bar at the top of #report-body replaces it. */
  .report-subnav { display: none; }

  .m-tabs {
    display: block;
    position: sticky;
    top: var(--header-h, 56px);
    z-index: 30;
    /* Bleed to the viewport edges. .report-grid uses 16px padding under
       700px and 24px between 700-768px; -16px works for the common case
       (iPhones <= 414px) and leaves a tolerable inset on the 700-768
       sliver. */
    margin: 0 -16px 20px;
    /* v10.1: switched to color-mix so the background tracks the
       :root --paper swap directly. The earlier
       rgba(var(--paper-rgb), 0.96) trick worked on Chrome/Firefox but
       hit Safari's long-standing bug with comma-list custom-properties
       inside rgba()/rgb(); on iPhone the bar stayed cream against a
       dark page. color-mix() expands var(--paper) first, applies
       opacity second — no comma-list parsing involved. Falls back to
       solid var(--paper) on browsers without color-mix support. */
    background: var(--paper);
    background: color-mix(in srgb, var(--paper) 96%, transparent);
    -webkit-backdrop-filter: blur(20px) saturate(180%);
            backdrop-filter: blur(20px) saturate(180%);
    /* v10.1: bolder visual treatment so the bar reads as navigation
       chrome, not a translucent overlay. Top accent stripe + heavier
       drop shadow + stronger bottom hairline. */
    border-top: 2px solid var(--accent);
    border-bottom: 1px solid var(--rule);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.10);
  }
  /* Fallback for browsers without backdrop-filter — solid paper so
     the bar still reads as a chrome layer. */
  @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .m-tabs { background: var(--paper); }
  }
  .m-tabs-scroll {
    position: relative;
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 0 16px;
    scroll-behavior: smooth;
  }
  .m-tabs-scroll::-webkit-scrollbar { display: none; }
  .m-tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    /* v10.1: 48px → 56px so the touch targets feel deliberate and the
       eyebrow text has more breathing room next to the score badges. */
    height: 56px;
    padding: 0 18px;
    font-family: var(--font-sans);
    /* v10.1: 14px/0.7 → 15px/500 inactive, 700 when active. Reads as
       a real navigation menu rather than a soft secondary affordance. */
    font-size: 15px;
    font-weight: 500;
    /* v4 May 2026: dropped opacity:0.78 — the warm dark sepia --ink-soft
     is plenty muted at full opacity, and matches the desktop TOC fix. */
    color: var(--ink-soft);
    text-decoration: none;
    white-space: nowrap;
    background-image: none;
    transition: color 0.2s ease, font-weight 0.2s ease, transform 0.1s ease;
    position: relative;
    z-index: 1;
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0.04);
  }
  .m-tab:active { transform: scale(0.97); }
  /* Active tab takes the accent colour and gains weight. Reading
     "Mobile coverage" highlighted in red while a wider indicator slides
     under it is more legible than the soft "ink-bold" treatment. */
  .m-tab.is-active {
    color: var(--accent);
    opacity: 1;
    font-weight: 700;
  }
  .m-tab-name { display: inline-block; }
  .m-tab-score {
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    padding: 3px 7px;
    border-radius: 10px;
    background: transparent;
    color: var(--ink);
    border: 1px solid var(--rule);
    font-feature-settings: "tnum", "lnum";
  }
  .m-tab-score:empty { display: none; }
  .m-tab-score[data-band="excellent"] { background: #1F6B45; color: #fff; border-color: #1F6B45; }
  .m-tab-score[data-band="good"]      { background: #4F8A4F; color: #fff; border-color: #4F8A4F; }
  .m-tab-score[data-band="ok"]        { background: #C49B3D; color: #1A1612; border-color: #C49B3D; }
  .m-tab-score[data-band="poor"]      { background: #C97A3A; color: #fff; border-color: #C97A3A; }
  .m-tab-score[data-band="bad"]       { background: #B53A2A; color: #fff; border-color: #B53A2A; }
  .m-tab-indicator {
    position: absolute;
    bottom: 0;
    left: 0;
    /* v10.1: 4px → 5px with a stronger glow so the indicator reads
       as a definite "you are here" mark even at peripheral vision. */
    height: 5px;
    width: 0;
    background: var(--accent);
    border-radius: 2px 2px 0 0;
    box-shadow: 0 0 10px rgba(200, 75, 49, 0.50);
    transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1),
                width     0.35s cubic-bezier(0.65, 0, 0.35, 1);
    pointer-events: none;
    will-change: transform, width;
  }
  .m-tabs-fade-l, .m-tabs-fade-r {
    position: absolute;
    top: 0;
    bottom: 1px;
    width: 24px;
    pointer-events: none;
    z-index: 2;
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  /* Fade gradients use color-mix for the same reason as .m-tabs above
     (Safari's rgba(var(...), alpha) bug). The "transparent" endpoint
     is just the keyword — no need to mix it. */
  .m-tabs-fade-l {
    left: 0;
    background: linear-gradient(90deg,  var(--paper), transparent);
    background: linear-gradient(90deg,  color-mix(in srgb, var(--paper) 96%, transparent), transparent);
  }
  .m-tabs-fade-r {
    right: 0;
    background: linear-gradient(270deg, var(--paper), transparent);
    background: linear-gradient(270deg, color-mix(in srgb, var(--paper) 96%, transparent), transparent);
  }

  /* ---- Mobile section-nav arrow buttons -----------------------------
     Pinned on the left/right edges of the .m-tabs strip when the strip
     overflows in that direction. Sit on top of the fade gradient so the
     chevron stays readable. JS toggles the .is-visible class based on
     scrollLeft + scrollWidth - clientWidth measurements; without JS
     they stay hidden so anchor navigation still works. */
  .m-tabs-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%) scale(0.92);
    z-index: 4;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--rule);
    border-radius: 50%;
    background: var(--paper);
    background: color-mix(in srgb, var(--paper) 96%, transparent);
    -webkit-backdrop-filter: blur(12px) saturate(160%);
            backdrop-filter: blur(12px) saturate(160%);
    color: var(--ink);
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.10);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.15s ease, background 0.15s ease;
  }
  .m-tabs-arrow.is-visible {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(-50%) scale(1);
  }
  .m-tabs-arrow:hover  { background: var(--paper-tinted); }
  .m-tabs-arrow:active { transform: translateY(-50%) scale(0.92); }
  .m-tabs-arrow-l { left: 4px; }
  .m-tabs-arrow-r { right: 4px; }
}

@media (max-width: 768px) and (prefers-color-scheme: dark) {
  .m-tabs-arrow {
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.42);
  }
}

/* v10.1 dark-mode override — color-mix expands var(--paper) at use
   time, which the existing prefers-color-scheme block in :root has
   already swapped to #1F1B16. So .m-tabs and the fade gradients
   need NO dark-mode background override — they track automatically.
   What does need tuning: a heavier drop shadow against dark paper
   (the light-mode 0.10 alpha disappears against #1F1B16) and a
   slightly hotter indicator glow at the dark-mode accent. */
@media (max-width: 768px) and (prefers-color-scheme: dark) {
  .m-tabs {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.36);
  }
  .m-tab-indicator {
    box-shadow: 0 0 12px rgba(226, 106, 80, 0.55);
  }
}

/* ---- v9: partial-coverage banner --------------------------------
   Renders at the top of #report-body for any postcode whose borough
   is in the "partial" tier — empty data sections aren't read as
   factual zeros. Editorial tone (paper-tinted background, accent
   left rule) so it doesn't look like a generic alert chrome. */
.coverage-banner {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0 0 24px;
  padding: 14px 18px;
  background: var(--paper-tinted);
  border-left: 3px solid var(--accent);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  border-right: 1px solid var(--rule);
  font-family: var(--font-display);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
}
.coverage-banner-icon {
  flex-shrink: 0;
  display: inline-flex;
  margin-top: 2px;
  color: var(--accent);
}
.coverage-banner-text strong {
  font-weight: 600;
  margin-right: 6px;
}
.coverage-banner-text em {
  font-style: italic;
  color: var(--ink-soft);
}
@media (max-width: 768px) {
  .coverage-banner {
    margin: 0 -16px 20px;
    padding: 14px 16px;
    font-size: 14px;
    border-left: 0;
    border-top-width: 3px;
    border-top-color: var(--accent);
  }
}

/* ---- P0.3 (v5): snapshot leaderboard 2x2 grids with magazine sizing -
   Each row of four tiles becomes a 2x2 quadrant. Values clamp so long
   strings (e.g. "Vauxhall and Camberwell Green") wrap rather than blow
   the cell horizontally. 12px gap between tiles, 14px padding inside. */
@media (max-width: 768px) {
  /* Override the older 720px horizontal-swipe rule. */
  .leaderboard {
    display: grid !important;
    grid-auto-flow: row !important;
    grid-template-columns: repeat(2, 1fr) !important;
    grid-auto-columns: auto !important;
    overflow: visible !important;
    scroll-snap-type: none !important;
    border: 1px solid var(--rule) !important;
    margin: 0 0 18px !important;
    padding: 0 !important;
    gap: 0 !important;
    -webkit-mask-image: none !important;
            mask-image: none !important;
  }
  .leaderboard .lb-row-eyebrow {
    display: block !important;
    grid-column: 1 / -1;
    padding: 11px 14px;
    /* v2 mobile: matches desktop bump — 12.5px / 700 / 0.14em reads as a
       proper section header on a phone instead of fading into the rule
       lines. The accent left-border carries through from desktop. */
    font-size: 12.5px;
    letter-spacing: 0.14em;
    font-weight: 700;
    color: var(--ink);
    background: var(--paper-tinted);
    border-bottom: 1px solid var(--rule);
    border-left: 3px solid var(--accent);
  }
  .leaderboard .lb-tile {
    padding: 14px !important;
    min-height: 76px !important;
    border-right: 1px solid var(--rule) !important;
    border-bottom: 1px solid var(--rule) !important;
    scroll-snap-align: none !important;
    gap: 4px !important;
    overflow: hidden;
  }
  /* v3 (May 2026): see desktop note — sibling chain anchored on the
     eyebrow. The previous :nth-child(2n) put the gap on column 1 instead
     of column 2 because eyebrows are also .leaderboard children. */
  .leaderboard .lb-row-eyebrow + .lb-tile + .lb-tile,
  .leaderboard .lb-row-eyebrow + .lb-tile + .lb-tile + .lb-tile + .lb-tile {
    border-right: none !important;
  }
  .leaderboard .lb-label {
    font-size: 10px !important;
    letter-spacing: 0.05em !important;
    text-transform: uppercase !important;
    color: var(--ink-soft) !important;
  }
  .leaderboard .lb-value {
    font-size: clamp(16px, 4.5vw, 20px) !important;
    font-weight: 500 !important;
    line-height: 1.2 !important;
    margin-top: 4px !important;
    /* Long values wrap naturally; cap at 3 lines so a freakishly long
       constituency name can't blow the tile height. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .leaderboard .lb-sub {
    font-size: 12px !important;
    font-style: italic;
    color: var(--ink-soft) !important;
    margin-top: 2px !important;
  }
}

/* Section spacing on mobile — postcode report sections currently get the
   same generous editorial padding as desktop. Trim so 18 sections aren't
   a kilometre of scroll. */
@media (max-width: 768px) {
  #report-body > section { margin-top: 28px; padding-top: 0; }
  .section-head { padding-top: 18px; gap: 6px; }
  .section-head-left h2 { font-size: 20px; line-height: 1.2; }
  .section-head-left p,
  .section-head-left .section-disclosure { font-size: 13.5px; }

  /* Tighten card padding on data-heavy sections. */
  .card.padded-lg { padding: 16px 14px; }
  .grid-2, .grid-3 { gap: 12px; }
  /* Pair-friendly fact grids: where desktop has 1-column kv lists, give
     mobile the same column treatment so the card doesn't stretch the
     screen height. .kv-list pairs key:value horizontally already; we
     just need to make sure rows are dense. */
  .kv-list li { padding: 8px 0; font-size: 13.5px; }

  /* Tables → less wide padding so they don't blow horizontally. */
  .data-table th, .data-table td { padding: 8px 8px; font-size: 13px; }

  /* The chip-row already collapses to 1-col under 700px — fine. */
}

/* ---- P0.7 (v5): postcode header — flush below site-header, no gap ---
   v4.1 had `padding-top: 12px` plus `.crumb { min-height: 36px;
   align-items: center; }`, which vertically-centered the breadcrumb
   text inside a 36 px box. The empty top half of that box read as a
   visible gap below the site-header. Fix: drop the report-hero top
   padding to zero, drop the crumb min-height, give the crumb a small
   own-margin-top so the text starts ~10 px below the site-header. The
   action cluster is still absolute-positioned; it now overlays the
   crumb row at top: 8 px, fully inside the runway reserved by
   padding-right. */
@media (max-width: 768px) {
  .report-hero {
    padding: 0 20px 18px;        /* no top padding — flush */
    position: relative;          /* anchor for absolute children */
  }
  .report-hero .inner {
    display: block;              /* normal block flow — no flex column */
  }
  .report-hero-actions {
    position: absolute;
    top: 8px;
    right: 16px;
    z-index: 2;
    display: flex;
    gap: 8px;
    margin: 0;
  }
  /* All three header actions become uniform 36×36 icon-only buttons on
     mobile. v5 kept the SAVE label visible inside a fixed-width box,
     and the text overflowed the border. Hiding the label removes the
     mismatch — icon-only beats mixed-treatment for a tight 3-button
     row at 380 px. */
  .report-hero-actions .icon-btn,
  .report-hero-actions .save-star {
    width: 36px !important;
    height: 36px !important;
    min-width: 36px !important;
    min-height: 36px !important;
    padding: 0 !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .report-hero-actions .save-star .save-star-label,
  .report-hero-actions .pdf-btn .pdf-btn-label { display: none !important; }

  /* Breadcrumb: small breathing margin-top so text isn't tight against
     the site-header bottom edge. Padding-right reserves runway for the
     absolute action cluster (3 × 36 px + 2 × 8 px gap = 124 px → 140 px
     incl. right offset). No min-height — the box wraps to text height. */
  .report-hero .crumb {
    margin: 10px 0 6px;
    padding-right: 140px;
    font-size: 10.5px;
    flex-wrap: wrap;
    min-height: 0;
  }
  .report-hero h1 {
    font-size: clamp(32px, 10vw, 44px);
    line-height: 1.02;
    margin: 0 0 8px;
  }
  .report-hero .hero-meta-row {
    display: flex; flex-wrap: wrap;
    gap: 4px 8px;
    margin: 0 0 4px;
  }
  .report-hero .byline,
  .report-hero .dateline { font-size: 12.5px; line-height: 1.4; }
  .report-hero .byline + .dateline::before { margin: 0 4px; }
  .report-hero .subline {
    margin: 0;
    font-size: 13px;
    line-height: 1.45;
  }
}

/* ---- P0.5: hero + search-card density ------------------------------ */
@media (max-width: 768px) {
  .hero--rich { padding: 24px 16px 36px; }
  .hero--rich h1 {
    font-size: clamp(32px, 8.6vw, 48px);
    line-height: 1.04;
    letter-spacing: -0.02em;
    margin-bottom: 14px;
    /* Avoid the awkward break in "deepest free public-record" — let
       the browser balance lines. Falls back to plain wrap on engines
       that don't ship text-wrap: balance. */
    text-wrap: balance;
  }
  .hero--rich h1 br { display: none; }
  .hero--rich .tagline { font-size: 14.5px; margin-bottom: 18px; }
  .hero--rich .coverage-pill { margin-bottom: 12px; }
  .coverage-pill { font-size: 11px; }

  /* Search card: stack input then button vertically with a consistent
     12px gap. The base mobile rule at <=700px already does column flex,
     but it leaves the search icon wedged in the input padding — clean
     that up and bump the button to 100% width. */
  .search-card {
    flex-direction: column;
    gap: 12px;
    padding: 12px;
    border-radius: 0;
  }
  .search-card .search-icon {
    left: 14px; top: 24px;
    transform: none;
  }
  .search-card input[type="search"] {
    width: 100%;
    padding: 12px 12px 12px 38px;
    font-size: 16px; /* iOS: prevents auto-zoom on focus */
    min-height: 48px;
  }
  .search-card .btn-primary {
    width: 100%;
    justify-content: center;
    min-height: 48px;
    font-size: 13px;
    letter-spacing: 0.16em;
  }

  .hero-cta-hint { font-size: 13px; line-height: 1.7; }
  .hero-cta-hint a { padding: 4px 4px; }

  /* CTA tail — same stacked treatment. */
  .cta-tail { padding: 28px 18px 32px; }
  .cta-tail h2 { font-size: 26px; }
  .cta-tail p { font-size: 14.5px; }
}

/* ---- P0.7: trust strip 2-column compact grid ----------------------- */
@media (max-width: 768px) {
  .trust-strip { padding: 28px 16px 32px; }
  .trust-eyebrow { font-size: 10px; margin-bottom: 14px; }
  .trust-logos {
    display: grid !important;
    grid-template-columns: repeat(2, 1fr);
    gap: 0;
    border-top: 1px solid var(--rule);
    border-left: 1px solid var(--rule);
  }
  .trust-pill {
    padding: 10px 12px !important;
    border: 0 !important;
    border-right: 1px solid var(--rule) !important;
    border-bottom: 1px solid var(--rule) !important;
    font-size: 14.5px !important;
    margin: 0;
  }
  .trust-pill + .trust-pill { padding-left: 12px !important; }
  .trust-pill .dot { width: 5px; height: 5px; }
  .trust-strip .trust-tagline { font-size: 14px; line-height: 1.55; margin-top: 22px; }
}

/* ---- P0.7 / global: also collapse to 1 col below 380px ------------- */
@media (max-width: 380px) {
  .trust-logos { grid-template-columns: 1fr; }
  .trust-pill { border-right: 0 !important; }
  .leaderboard .lb-value { font-size: 18px; }
}

/* =====================================================================
   MOBILE REDESIGN v2 (postcode page) — bundled below this comment.
     P0.0  sticky subnav + bottom sheet (CSS above; this block adds the
            score-badge integration with section eyebrows)
     P0.1  section card pattern — collapsed methodology accordion
     P0.2  score-band coloured badges on .eyebrow-rating
     P0.3  typography — kill italics, smaller H2s, hide pullquotes
     P0.4  dedupe source attributions (one per section, methodology only)
     P0.5  "At a glance" card under the report hero (mobile only)
     P0.6  compact main header on scroll past 50px
     P0.7  scroll-margin-top so anchors land below sticky chrome
   Every rule below is gated by @media (max-width: 768px).
   ===================================================================== */

/* ---- P0.2 v2: score-band coloured badges on section eyebrows ------- */
@media (max-width: 768px) {
  /* The plain "· 9/10 EXCELLENT" eyebrow chip becomes a real coloured
     pill on mobile so it's instantly scannable. setSectionRating already
     writes the chip; the inline subnav script copies the band onto the
     element via data-band, which we colour here. */
  .eyebrow-rating {
    /* Layout resets — !important on box properties so no stale rule from
       earlier in the cascade can leak through. CRITICAL: do NOT use
       !important on background / border-color / color here — the
       band-coloured override rules below need to win. */
    display: inline-flex !important;
    align-items: center !important;
    gap: 6px !important;
    padding: 4px 10px !important;
    margin: 0 0 0 6px !important;
    border-radius: 999px !important;
    border: 1px solid var(--rule);
    background: var(--paper-tinted);
    box-shadow: none !important;
    text-shadow: none !important;
    text-indent: 0 !important;
    overflow: hidden !important;
    /* Typography */
    font-family: var(--font-sans) !important;
    font-size: 10.5px !important;
    font-weight: 600 !important;
    letter-spacing: 0.06em !important;
    text-transform: uppercase;
    color: var(--ink);
  }
  .eyebrow-rating .er-bullet { display: none !important; }
  /* Child elements MUST be inline transparent text — no background, no
     padding, no border can create the appearance of a nested pill. */
  .eyebrow-rating .er-score,
  .eyebrow-rating .er-label {
    background: transparent !important;
    border: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
    box-shadow: none !important;
    display: inline !important;
    font-feature-settings: "tnum", "lnum";
  }
  .eyebrow-rating .er-label {
    font-size: 9.5px;
    letter-spacing: 0.1em;
  }
  .eyebrow-rating[data-band="excellent"] { background: #1F6B45; color: #fff; border-color: #1F6B45; }
  .eyebrow-rating[data-band="good"]      { background: #4F8A4F; color: #fff; border-color: #4F8A4F; }
  .eyebrow-rating[data-band="ok"]        { background: #C49B3D; color: #1A1612; border-color: #C49B3D; }
  .eyebrow-rating[data-band="poor"]      { background: #C97A3A; color: #fff; border-color: #C97A3A; }
  .eyebrow-rating[data-band="bad"]       { background: #B53A2A; color: #fff; border-color: #B53A2A; }
  /* Bug fix May 2026: the desktop .er-score / .er-grade-X .er-score rules
     above (lines ~2605–2615) hard-code colours like accent-orange and
     #C99445. On mobile those colours sit on top of the band-coloured pill
     and become invisible (orange text on orange pill). Force the children
     to inherit the pill's chosen contrast colour. */
  .eyebrow-rating[data-band="excellent"] .er-score,
  .eyebrow-rating[data-band="excellent"] .er-label,
  .eyebrow-rating[data-band="good"]      .er-score,
  .eyebrow-rating[data-band="good"]      .er-label,
  .eyebrow-rating[data-band="ok"]        .er-score,
  .eyebrow-rating[data-band="ok"]        .er-label,
  .eyebrow-rating[data-band="poor"]      .er-score,
  .eyebrow-rating[data-band="poor"]      .er-label,
  .eyebrow-rating[data-band="bad"]       .er-score,
  .eyebrow-rating[data-band="bad"]       .er-label {
    color: inherit !important;
  }
}

/* ---- P0.3 / P0.6 / P0.7 (v3): single-column section cards ----------
   Last session forced .grid-2 / .grid-3 into 2 columns inside every
   section, which broke charts, bar visualisations, and long-label
   tiles. New rule: default every section's data area to single column,
   full width. The pre-existing 720px-and-below rule already handles
   .data-table cell padding etc. */
@media (max-width: 768px) {
  /* Generous mid-section padding so cards breathe. Reference:
     crystalroof.co.uk and crime-statistics.co.uk. */
  #report-body > section {
    padding: 24px 20px;
    margin-top: 32px;
  }
  #report-body > section:first-of-type { margin-top: 8px; }

  /* H2 size locked to one value across every section. Decorative
     section-icon dropped on mobile (cramped at this width). */
  #report-body > section .section-head { padding-top: 0; }
  #report-body > section .section-head-left .eyebrow {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 6px;
  }
  #report-body > section .section-head-left h2 {
    font-family: var(--font-display) !important;
    font-size: clamp(20px, 5vw, 24px) !important;
    font-weight: 400 !important;
    line-height: 1.25 !important;
    letter-spacing: -0.018em !important;
    margin: 0 0 12px !important;
  }
  #report-body > section .section-head-left h2 .section-icon { display: none; }

  /* Section lede paragraphs (roman, not italic). They're typically
     wrapped into the methodology accordion later; while inline, keep
     them readable. */
  #report-body > section .section-head-left p,
  #report-body > section .section-head-left .section-summary {
    font-style: normal !important;
    font-family: var(--font-sans) !important;
    font-size: 15px !important;
    line-height: 1.5 !important;
    color: var(--ink-soft) !important;
    margin: 0 0 12px !important;
    font-weight: 400;
  }
  /* section-summary is the loader's "key takeaway" — promote it.
     v5 May 2026: italic Fraunces restored on mobile too, matching the
     desktop revival. The accent-left-rule + italic serif gives the page
     more visual life per section. */
  #report-body > section .section-head-left .section-summary {
    font-family: var(--font-display) !important;
    font-style: italic !important;
    font-size: 15.5px !important;
    line-height: 1.45 !important;
    font-weight: 400 !important;
    color: var(--ink) !important;
    margin: 4px 0 14px !important;
  }
  #report-body > section .section-disclosure {
    font-style: normal !important;
    font-size: 13.5px !important;
    color: var(--ink-soft) !important;
  }

  /* Pull-quotes hidden in main flow — they get pulled into the
     methodology accordion. */
  #report-body > section .pullquote { display: none !important; }

  /* Duplicate src-tag at top of section is hidden — methodology
     accordion shows the canonical "Source: …" line. */
  #report-body > section .section-head-right .src-tag { display: none !important; }
  #report-body > section .section-head-right { margin-top: 0; }

  /* SINGLE-COLUMN DEFAULT for every grid inside a section. Charts,
     bar visualisations, long-label tiles, and paired-tile cards all
     stack full-width. The previous v2 force to 2 columns is gone. */
  #report-body > section .grid-2,
  #report-body > section .grid-3 {
    grid-template-columns: 1fr !important;
    gap: 16px !important;
  }
  /* Narrow opt-in: a section can request the old 2-column behaviour by
     marking its grid with .grid-paired. Reserve this for short paired
     numbers ("Sales: 15 | Crimes: 661") — never for charts or long
     labels. */
  #report-body > section .grid-paired {
    grid-template-columns: 1fr 1fr !important;
    gap: 12px !important;
  }

  /* Tile padding 16px all sides; titles a touch smaller. */
  #report-body > section .card.padded-lg {
    padding: 16px;
  }
  #report-body > section .card .card-title {
    font-size: 12px;
    letter-spacing: 0.16em;
    margin-bottom: 10px;
  }

  /* Charts and chart-wraps: ensure full width + responsive height,
     not jammed into half a column. */
  #report-body > section .chart-wrap,
  #report-body > section canvas {
    width: 100% !important;
    max-width: 100% !important;
    height: auto;
  }

  /* Compact key-value rows — readable but not cramped. */
  #report-body > section .kv-list li {
    padding: 10px 0 !important;
    font-size: 14px !important;
    gap: 12px;
  }
  #report-body > section .kv-list .k {
    font-size: 11.5px !important;
    letter-spacing: 0.1em;
  }
  #report-body > section .kv-list .v {
    font-size: 14.5px !important;
    font-feature-settings: "tnum", "lnum";
  }
}

/* ---- P0.5 (v3): paywall / gated-section CTA polish ------------------- */
@media (max-width: 768px) {
  .gated-section-card {
    padding: 16px !important;
    border-left-width: 3px;
    margin-top: 12px;
  }
  .gated-section-card .gated-eyebrow {
    font-size: 9.5px !important;
    letter-spacing: 0.18em;
    margin-bottom: 8px;
    color: var(--accent);
  }
  .gated-section-card .gated-lede {
    font-style: normal !important;
    font-family: var(--font-sans) !important;
    font-size: 15px !important;
    line-height: 1.5 !important;
    color: var(--ink) !important;
    margin: 0 0 14px !important;
    max-width: none !important;
  }
  .gated-section-card .gated-actions {
    flex-direction: column;
    align-items: stretch !important;
    gap: 10px !important;
  }
  .gated-section-card .gated-actions .btn,
  .gated-section-card .gated-actions a.btn,
  .gated-section-card .gated-actions button.btn {
    width: 100%;
    min-height: 48px;
    justify-content: center;
    text-align: center;
    font-size: 13px;
    letter-spacing: 0.16em;
  }
  .gated-section-card .gated-signin {
    text-align: center;
    font-style: normal !important;
    font-family: var(--font-sans) !important;
    font-size: 13px !important;
    color: var(--ink-soft) !important;
  }
  .gated-section-card.gated-inline { padding: 14px !important; }
}

/* ---- P0.6: compact main header on scroll ------------------------- */
@media (max-width: 768px) {
  body.m-header-compact .site-header .inner {
    min-height: 48px;
    padding: 6px 16px;
    transition: min-height 0.18s var(--ease-out), padding 0.18s var(--ease-out);
  }
  body.m-header-compact .site-header .logo {
    font-size: 20px;
    transition: font-size 0.18s var(--ease-out);
  }
  body.m-header-compact .site-header .logo .logo-tag { display: none; }
  body.m-header-compact .nav-toggle {
    width: 38px; height: 38px;
    transition: width 0.18s var(--ease-out), height 0.18s var(--ease-out);
  }
  /* The site-header's height drives sticky offsets via --header-h.
     Update the variable so the subnav re-pins under the compact bar. */
  body.m-header-compact { --header-h: 48px; }
  /* Default mobile header height (override the desktop 64px so the
     subnav top: var(--header-h) lands flush). */
  .site-header .inner { transition: min-height 0.18s var(--ease-out), padding 0.18s var(--ease-out); }
}

/* ---- P0.7: anchor scroll lands below sticky chrome --------------- */
@media (max-width: 768px) {
  /* Main header (64px / 48px compact) + sticky subnav (~48px) + breathing
     room. scroll-margin-top wins over scroll-padding when the user taps
     a link in the sheet. */
  #report-body > section[id] {
    scroll-margin-top: calc(var(--header-h, 64px) + 64px);
  }

  /* Methodology accordion built by the inline script. Show methodology +
     toggle, body shows the moved lede / disclosure / source. */
  .m-method {
    margin-top: 14px;
    padding: 10px 12px;
    border: 1px solid var(--rule);
    background: var(--paper-tinted);
    border-radius: 0;
  }
  .m-method > summary {
    cursor: pointer;
    list-style: none;
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink);
    padding: 4px 0;
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px;
  }
  .m-method > summary::-webkit-details-marker { display: none; }
  .m-method > summary::after {
    content: "+";
    font-family: var(--font-sans);
    font-size: 18px;
    line-height: 1;
    color: var(--ink-soft);
  }
  .m-method[open] > summary::after { content: "−"; }
  .m-method > summary::before { content: ""; }
  .m-method > * + * { margin-top: 10px; }
  .m-method p {
    font-family: var(--font-sans) !important;
    font-style: normal !important;
    font-size: 13px !important;
    line-height: 1.5 !important;
    color: var(--ink-soft) !important;
    margin: 8px 0 0 !important;
  }
  /* Pull-quote inside the accordion gets a thinner, quieter treatment. */
  .m-method .pullquote {
    display: block !important;
    margin: 8px 0 0 !important;
    padding: 6px 0 6px 10px !important;
    border-left: 2px solid var(--accent) !important;
    background: transparent !important;
    font-size: 13px !important;
    line-height: 1.45 !important;
  }
  .m-method .pullquote p {
    font-style: italic !important;
    color: var(--ink) !important;
    margin: 0 !important;
  }
  .m-method .m-method-src {
    font-family: var(--font-sans) !important;
    font-size: 10.5px !important;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft) !important;
    margin-top: 10px !important;
  }
}


/* =====================================================================
   MOBILE REDESIGN v4 (postcode page) — bundled below this comment.
     P0.3  H2 enforcement (one rule, !important, beats every prior cascade)
     P0.5  chapter-header pattern (accent stripe + tinted bg around H2+badge)
     P0.6  hero search input/button alignment confirmation
     P0.2  paywall CTA button width/height lock (text already shortened
            at the JS layer in postcode.js so it can't overflow)
   Every rule below is gated by @media (max-width: 768px).
   ===================================================================== */

/* ---- P0.3 (v4): single H2 enforcement rule ----------------------------
   Earlier mobile passes set H2 sizes via #report-body > section
   .section-head-left h2 (high specificity), but loaders that mutate
   the H2 with extra wrapper classes (or sections built from a slightly
   different markup tree like Schools) sometimes escaped the cascade
   and rendered at the desktop base size. !important here resolves the
   cascade chaos without me having to audit every loader's output. */
@media (max-width: 768px) {
  #report-body section h2,
  #report-body section .section-title,
  #report-body section [class*="title"]:not(.tile-title):not(.card-title) {
    font-size: 24px !important;
    font-family: var(--font-display) !important;
    font-weight: 400 !important;
    line-height: 1.2 !important;
    letter-spacing: -0.018em !important;
    margin: 0 0 12px 0 !important;
  }
}

/* ---- P0.2 (v5): beefed-up chapter-header treatment per section ------
   Every section's H2 + score badge are framed as a "magazine chapter
   break": a 3px accent top stripe over paper-tinted background, with
   a beefier UPPERCASE accent-coloured eyebrow inline with the score
   pill, and a 24px Fraunces H2 below. v4 had a 2px stripe and a quiet
   eyebrow — user feedback was that sections still blurred together. */
@media (max-width: 768px) {
  /* Override the v3 generous-padding rule: chapter card now gets the
     padding, the section body gets a slimmer block below. */
  #report-body > section {
    padding: 0 !important;
  }
  #report-body > section .section-head {
    border-top: 3px solid var(--accent);
    background: var(--paper-tinted);
    padding: 20px !important;
    margin: 0 !important;
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 8px !important;
  }
  #report-body > section .section-head-left {
    width: 100%;
  }
  /* Eyebrow row: UPPERCASE accent, score pill inline right of label.
     Already wraps as inline-flex from v3; bump weight + colour to make
     the chapter break unmistakable. */
  #report-body > section .section-head-left .eyebrow {
    margin-bottom: 6px !important;
    font-family: var(--font-sans) !important;
    font-size: 12px !important;
    font-weight: 700 !important;
    letter-spacing: 0.1em !important;
    text-transform: uppercase !important;
    color: var(--accent) !important;
  }
  /* H2 — 24px Fraunces, bigger than the v4 22px enforcement (which is
     overridden below by the higher-specificity P0.3 v4 rule; we boost
     its target value from 22px to 24px in the next rule). */
  #report-body > section .section-head-left h2 {
    font-size: 24px !important;
    line-height: 1.2 !important;
  }
  /* Section data area gets its own padding below the chapter card. */
  #report-body > section > *:not(.section-head) {
    padding-left: 20px;
    padding-right: 20px;
  }
  #report-body > section > *:not(.section-head):first-child {
    padding-top: 16px;
  }
  #report-body > section > *:not(.section-head):last-child {
    padding-bottom: 24px;
  }
  /* The .leaderboard inside #snapshot is currently negative-margined to
     reach edge-to-edge on its older horizontal-swipe pattern; with the
     chapter pattern, reset the negative so it lives inside the section
     padding column. */
  #snapshot .leaderboard {
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
}

/* ---- P0.6: hero search alignment lock ------------------------------- */
@media (max-width: 768px) {
  .hero--rich .search-card {
    padding: 16px;
    gap: 12px;
  }
  .hero--rich .search-card input[type="search"] {
    width: 100% !important;
    height: 56px;
    min-height: 56px;
    font-size: 16px;
    padding: 0 12px 0 38px;
  }
  .hero--rich .search-card .search-icon {
    left: 28px;
    top: 36px;
    transform: none;
  }
  .hero--rich .search-card .btn-primary {
    width: 100% !important;
    height: 56px;
    min-height: 56px;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0.16em;
    justify-content: center;
  }
}

/* ---- P0.2 (v4): paywall CTA button — hard width/height lock --------- */
@media (max-width: 768px) {
  .gated-section-card .gated-actions .btn-primary {
    width: 100%;
    height: 48px;
    min-height: 48px;
    padding: 0 16px;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: 0.14em;
    justify-content: center;
    text-align: center;
  }
}

/* ---- P0.3 (v6): sales table → stacked cards on mobile ---------------
   v5 used CSS grid with column-spans on td. Real-device testing showed
   addresses cut from the LEFT side ("RST FLOOR…" instead of "FIRST
   FLOOR…"). Diagnosis: the v5 grid pattern inherited
   `white-space: nowrap` from `.data-table td` (not overridden), and
   the row's parent `.card.flush.has-table` has `overflow-x: auto`
   (line 1019). Long addresses pushed the row's natural width past the
   viewport, the auto scroll position landed mid-string, and only the
   tail showed.
   v6 fix: rewrite as flex-wrap. Date + price on first wrapped flex
   line, address forced to a new line by `flex-basis: 100%`, type
   below it. Definitively unset `white-space: nowrap` and
   `overflow-x: auto` so nothing can horizontally clip. */
@media (max-width: 768px) {
  /* Strip the parent card's overflow-x: auto so wrapped content can
     never be horizontally clipped. The card visually contains the rows
     via its own border. */
  #sales .card.flush.has-table {
    padding: 0;
    border: 1px solid var(--rule);
    overflow: visible !important;
  }

  #sales .data-table,
  #sales .data-table tbody {
    display: block !important;
    width: 100% !important;
  }
  #sales .data-table thead {
    display: none !important;
  }
  #sales .data-table tr {
    display: flex !important;
    flex-wrap: wrap !important;
    align-items: baseline;
    column-gap: 12px;
    padding: 14px 16px !important;
    border-bottom: 1px solid var(--rule);
    width: 100%;
    box-sizing: border-box;
  }
  #sales .data-table tr:last-child {
    border-bottom: 0;
  }
  #sales .data-table td {
    display: block !important;
    border: 0 !important;
    padding: 0 !important;
    /* Hard reset: kill the .data-table base's nowrap so addresses can
       wrap onto multiple lines instead of overflowing. */
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
    overflow-wrap: anywhere;
    word-break: normal;
    line-height: 1.4;
  }

  /* td:nth-child(1) — Date: small caps, first item on row 1 */
  #sales .data-table td:nth-child(1) {
    flex: 0 0 auto;
    order: 1;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  /* td:nth-child(5) — Price: bold, fills rest of row 1, right-aligned */
  #sales .data-table td:nth-child(5) {
    flex: 1 1 auto;
    order: 2;
    font-size: 15px;
    font-weight: 600;
    text-align: right;
    color: var(--ink);
    font-feature-settings: "tnum", "lnum";
  }
  /* td:nth-child(2) — Address: forced to row 2 by flex-basis 100% */
  #sales .data-table td:nth-child(2) {
    flex: 0 0 100%;
    order: 3;
    width: 100%;
    margin-top: 6px;
    font-family: var(--font-sans);
    font-size: 14px;
    font-weight: 400;
    color: var(--ink);
  }
  /* td:nth-child(3) — Property type tag, row 3 */
  #sales .data-table td:nth-child(3) {
    flex: 0 0 100%;
    order: 4;
    width: 100%;
    margin-top: 4px;
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  /* td:nth-child(4) — Tenure: hidden on mobile (desktop keeps it) */
  #sales .data-table td:nth-child(4) {
    display: none !important;
  }
  /* Empty / loading rows (colspan="5") — single cell, full width */
  #sales .data-table tr td[colspan] {
    flex: 0 0 100% !important;
    order: 0 !important;
    width: 100%;
    margin-top: 0;
    text-align: center;
    padding: 20px 8px !important;
  }
}

/* ============================================================
   "Build with Truely" — homepage API promotion section.
   Lives between the coverage map and the consumer CTA tail.
   Editorial, professional, no marketing-speak. Reuses .section-head
   for headline rhythm; adds three use-case tiles and an inline code
   snippet that shows the proposition in two seconds.
   ============================================================ */
.build-with-truely {
  margin-top: 56px;
  padding-top: 36px;
  border-top: 1px solid var(--rule);
}
.bwt-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  margin-top: 28px;
}
@media (max-width: 860px) {
  .bwt-grid { grid-template-columns: 1fr; }
}
.bwt-tile {
  padding: 22px 22px 24px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--paper);
  transition: border-color .12s;
}
.bwt-tile:hover { border-color: var(--ink); }
.bwt-tile-icon {
  display: inline-flex;
  align-items: center; justify-content: center;
  width: 36px; height: 36px;
  border-radius: 8px;
  background: var(--paper-tinted);
  border: 1px solid var(--rule);
  color: var(--accent);
  margin-bottom: 14px;
}
.bwt-tile h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 19px;
  letter-spacing: -0.005em;
  margin: 0 0 8px;
  color: var(--ink);
}
.bwt-tile p {
  font-family: var(--font-sans);
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink-soft);
  margin: 0;
}

.bwt-code {
  margin: 26px 0 14px;
  border: 1px solid #2a2520;
  border-radius: 8px;
  background: #1c1814;
  overflow: hidden;
}
.bwt-code-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 16px;
  background: #15110d;
  border-bottom: 1px solid #2a2520;
  font-family: var(--font-sans);
  font-size: 12px; font-weight: 600;
  color: rgba(255,255,255,0.65);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.bwt-code-more {
  color: rgba(255,255,255,0.75);
  text-decoration: none;
  letter-spacing: 0.02em;
  text-transform: none;
  font-weight: 500;
}
.bwt-code-more:hover { color: #fff; }
.bwt-code pre {
  margin: 0;
  padding: 18px 20px;
  overflow-x: auto;
  font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.6;
  color: #ede5d8;
}
.bwt-cmt   { color: #847868; font-style: italic; }
.bwt-cmd   { color: #ffd9b0; }
.bwt-flag  { color: #f4a273; }
.bwt-str   { color: #c8e6b3; }

.bwt-footnote {
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--ink-soft);
  line-height: 1.6;
  margin: 16px 0 0;
  text-align: center;
}
.bwt-footnote a {
  color: var(--accent);
  border-bottom: 1px solid currentColor;
  text-decoration: none;
}
.bwt-footnote a:hover { color: var(--accent-deep); }

