﻿:root {
  --navy: #0d1f35;
  --navy-2: #1a2a3a;
  --navy-3: #1e3d5a;
  --teal: #00b2a9;
  --teal-dark: #0d5a54;
  /* Muted sub-text on the dark surfaces (labels, provenance, as-of dates). Lifted from #7a9ab5
     (~5.6:1 on --navy) to ~6.8:1 so the many 9–11px muted labels read more comfortably, while
     staying clearly secondary to white/body text. Only used on dark backgrounds (the light
     Overview uses its own greys), so brightening it is safe. */
  --slate: #8ea9c2;
  --paper: #f4f6f8;
  --ink: #1a2a3a;
  --warn: #f59e0b;
  --white: #ffffff;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  background: var(--paper);
  color: var(--ink);
  min-height: 100vh;
}

.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
}

/* ===== Top bar: brand (logo + title) · page-tab nav (horizontal soft-scroll) · Guide/logo ===== */
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Hug the brand text at every width. flex-shrink:0 stops the box shrinking below its text (which
     would spill "Housing Dashboard" onto the tabs); and NO min-width means the box doesn't reserve
     empty space between the title and the first tab (a 220px min-width used to leave a big gap before
     "Overview" at medium widths, while the text is only ~160px wide). */
  flex-shrink: 0;
}

.logo-sq {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  background: var(--teal);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
}

.logo-sq:hover {
  filter: brightness(1.05);
}

.logo-sq:focus-visible {
  outline: 2px solid #c2d7e8;
  outline-offset: 2px;
}

.logo-sq svg {
  width: 16px;
  height: 16px;
  fill: var(--white);
}

.brand-text {
  line-height: 1.1;
}

.brand-top {
  display: block;
  font-size: 10px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--teal);
  font-weight: 600;
}

.brand-main {
  display: block;
  font-size: 13px;
  color: var(--white);
  font-weight: 600;
  white-space: nowrap;
}

/* The nav is a horizontal soft-scroll frame: the four tabs stay on ONE line and, when the window is
   too narrow for them to fit (the ~801–894px band — wider they fit; ≤800px the nav gets its own full
   row), the inner .nav-scroll scrolls horizontally with NO visible scrollbar. Left/right fade shadows
   + a chevron cue give the "there's more →" feedback instead (mirrors the vertical .softscroll
   grammar). .nav-can-left / .nav-can-right are toggled from JS (wireNavSoftScroll) by scroll pos. */
.nav {
  position: relative;
  display: flex;
  min-width: 0;       /* allow the frame to shrink below its content in the topbar so the view scrolls */
  overflow: hidden;   /* clip; the inner .nav-scroll does the scrolling, .nav is the fixed fade/cue frame */
}
.nav-scroll {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: stretch;
  gap: 0;
  overflow-x: auto;
  scrollbar-width: none;        /* Firefox */
  -ms-overflow-style: none;     /* legacy Edge */
}
.nav-scroll::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;                /* Chrome / Edge / Safari */
}
/* Left/right fade shadows over the navy topbar — a scrolled tab dissolves cleanly BEFORE it reaches
   the brand (left) or the guide/logo (right), so it never reads as tucking under them. The first
   ~55% of the band is near-solid navy (fully hides the tab) then fades, so the visible tab text stops
   well clear of the neighbour rather than crowding right up against it. */
.nav::before,
.nav::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 40px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
  z-index: 2;
}
.nav::before {
  left: 0;
  background: linear-gradient(to right, var(--navy) 55%, rgba(13, 31, 53, 0));
}
.nav::after {
  right: 0;
  background: linear-gradient(to left, var(--navy) 55%, rgba(13, 31, 53, 0));
}
.nav.nav-can-left::before {
  opacity: 1;
}
.nav.nav-can-right::after {
  opacity: 1;
}
/* Chevron cue pinned to the right edge while more tabs remain to the right (gentle sideways bob). */
.nav-cue {
  position: absolute;
  right: 3px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 16px;
  line-height: 1;
  font-weight: 700;
  color: var(--teal);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 3;
}
.nav.nav-can-right .nav-cue {
  opacity: 0.9;
  animation: nav-cue-bob 1.6s ease-in-out infinite;
}
@keyframes nav-cue-bob {
  0%, 100% { transform: translateY(-50%) translateX(0); }
  50% { transform: translateY(-50%) translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .nav.nav-can-right .nav-cue { animation: none; }
}

.nav-tab {
  height: 56px;
  border: none;
  background: transparent;
  color: var(--slate);
  padding: 0 16px;
  font-size: 12px;
  font-weight: 600;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}

.nav-tab:hover {
  color: #c2d7e8;
}

.nav-tab.active {
  color: var(--white);
  border-bottom-color: var(--teal);
}

.nav-tab:focus,
.nav-tab:focus-visible {
  outline: none;
  box-shadow: none;
}


.topbar-right {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

/* Fullscreen toggle (replaced the Datasing logo). Mirrors the .guide-btn pill so the two
   topbar controls read as a matched pair. Wired by js/presentation/fullscreen.js. */
.fs-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px 0 10px;
  border: 1px solid var(--navy-3);
  border-radius: 14px;
  background: transparent;
  color: #c7d8e8;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.fs-btn:hover {
  border-color: var(--teal);
  color: #fff;
  background: rgba(0, 178, 169, 0.12);
}
.fs-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.topbar {
  height: 56px;
  background: var(--navy);
  color: var(--white);
  border-bottom: 1px solid var(--navy-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
  gap: 16px;   /* a little air around the nav so the tabs don't sit tight against the brand / logo */
  position: sticky;
  top: 0;
  z-index: 10;
}

/* ===== Loading feedback (header spinner, sidebar skeleton, map overlay) ===== */
@keyframes gshd-spin { to { transform: rotate(360deg); } }
@keyframes gshd-shimmer { 0% { background-position: -180px 0; } 100% { background-position: 180px 0; } }

/* Header status spinner: hidden until setMapStatus reports a loading state. */
.head-spinner {
  display: none;
  width: 12px;
  height: 12px;
  border: 2px solid #cbd5e1;
  border-top-color: #0f766e;
  border-radius: 50%;
  flex-shrink: 0;
}
.head-spinner.is-active {
  display: inline-block;
  animation: gshd-spin 0.7s linear infinite;
}

/* Map loading overlay (Neighbourhood): a scrim + spinner over the map until the
   choropleth has data and paints. Shown/hidden via .is-active from the page controller. */
.map-loading-overlay {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 600;
  align-items: center;
  justify-content: center;
  background: rgba(11, 22, 35, 0.45);
}
.map-loading-overlay.is-active { display: flex; }
.map-loading-box {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;
  border-radius: 8px;
  background: rgba(15, 28, 44, 0.92);
  color: #e6f2f0;
  font-size: 13px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
}
.map-spinner {
  width: 18px;
  height: 18px;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: #2dd4bf;
  border-radius: 50%;
  animation: gshd-spin 0.7s linear infinite;
  flex-shrink: 0;
}

.page-error-notice {
  background: #fff1f2;
  color: #9f1239;
  border: 1px solid #fecdd3;
  border-radius: 6px;
  padding: 10px 12px;
  font-size: 12px;
  line-height: 1.5;
  margin: 12px 0 0;
}

/* ===== Page shell + page-head (title · centred "Mapped at…" label · geography stats) ===== */
main {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.page {
  display: none;
  flex: 1;
  min-height: calc(100vh - 93px);
}

.page.active {
  display: flex;
  flex-direction: column;
}

.page-head {
  position: relative;
  padding: 3px 18px;
  min-height: 33px;
  border-bottom: 1px solid #e2e8f0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
}

/* Dead-centred in the bar regardless of the left/right widths, so it never
   shifts as the right-hand stats change. Names the geography the map is drawn at. */
.page-head-center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 13px;
  color: #66798b;
  white-space: nowrap;
  pointer-events: none;
}

.page-head-left {
  /* Content-sized (the page title), not flex:1 grown — so G.fitPageHeadStats can measure the real
     title width against the stats to decide when the stats no longer fit. space-between still holds
     the title at the left and the stats at the right. */
  flex: 0 0 auto;
}

.page-head-right {
  display: flex;
  gap: 12px;
  align-items: center;
  white-space: nowrap;
  flex-wrap: nowrap;
  justify-content: flex-end;
  text-align: right;
  flex-shrink: 0;
}
/* The geography stats stay on ONE line and are HIDDEN outright (by G.fitPageHeadStats) when they
   can't fit alongside the page title — rather than wrapping to a taller second line. Mirrors the way
   the centred "Mapped at … level" label disappears when space runs out. */
.page-head-right.gshd-stats-hidden {
  display: none;
}

.page-head-right span {
  font-size: 12px;
  color: #66798b;
}

.page-head-right .geo-divider {
  width: 1px;
  height: 16px;
  background: #cbd5e1;
  flex-shrink: 0;
}

.page-head-right span.geo-vintage {
  font-size: 12px;
  font-weight: 600;
  color: #1e293b;
  flex-shrink: 0;
}

.page-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 0;
}

/* ===== Frame: sidebar (statistic selector + map controls) + map area ===== */
.frame {
  flex: 1;
  min-height: 0;
  display: flex;
  background: var(--navy-2);
}

.sidebar {
  /* 240px: wide enough that the longest SA3 name ("Turnbull Thompson Park-Hawthorndale") stays on one
     line (it needs ~235px including padding). */
  width: 240px;
  background: var(--navy);
  border-right: 1px solid var(--navy-3);
  padding: 14px;
  color: var(--slate);
  display: flex;
  flex-direction: column;
  /* Scroll the sidebar internally so a long metric list can't stretch the whole
     layout past the viewport (which would push the map + legend below the fold). */
  min-height: 0;
  max-height: calc(100vh - 93px);
  overflow-y: auto;
  /* Never show an ugly scrollbar: hide it but keep scrolling (the soft-scroll regions inside add
     their own fade/cue feedback). */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.sidebar::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

.sidebar h3 {
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  font-size: 10px;
  margin-bottom: 10px;
}

.sidebar p {
  font-size: 12px;
  line-height: 1.35;
}

.zoom-dropdown {
  width: 100%;
  padding: 8px 6px;
  border: 1px solid #36506d;
  background: rgba(26, 42, 58, 0.8);
  color: #d7e6f2;
  font-size: 11px;
  border-radius: 3px;
  margin-bottom: 10px;
  cursor: pointer;
}

.zoom-dropdown:hover {
  background: rgba(26, 42, 58, 0.95);
  border-color: #00b2a9;
}

.zoom-dropdown:focus {
  outline: 2px solid #00b2a9;
  outline-offset: -1px;
}

.zoom-dropdown optgroup {
  font-style: normal;
}

/* ===== Soft-scroll: shared "scroll region with no visible scrollbar" utility ===== */
/* ---- Soft-scroll: a scroll region with NO visible scrollbar. Top/bottom fade shadows + a chevron
   cue give the "there's more to scroll" feedback instead, so a windowed user never sees an ugly bar.
   .can-up / .can-down are toggled from JS (wireSoftScroll) by scroll position. Used by the SA3 zoom
   lists, the Neighbourhood metric list, and every right-hand stats panel. ---- */
.softscroll {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* In a sidebar the soft-scroll region grows to fill the free vertical space above Map Boundaries. */
.sidebar .softscroll {
  flex: 1 1 auto;
}
.softscroll-view {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Hide the (ugly, default-light) scrollbar but keep wheel/trackpad scrolling. */
  scrollbar-width: none;        /* Firefox */
  -ms-overflow-style: none;     /* legacy Edge */
}
.softscroll-view::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;                /* Chrome / Edge / Safari */
}
/* Top/bottom fade shadows: content dissolves into the background at whichever edge has more beyond
   it, so the region reads as scrollable without a bar. */
.softscroll::before,
.softscroll::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 24px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
  z-index: 2;
}
.softscroll::before {
  top: 0;
  background: linear-gradient(to bottom, var(--navy), rgba(13, 31, 53, 0));
}
.softscroll::after {
  bottom: 0;
  background: linear-gradient(to top, var(--navy), rgba(13, 31, 53, 0));
}
.softscroll.can-up::before {
  opacity: 1;
}
.softscroll.can-down::after {
  opacity: 1;
}
/* Scroll affordance: a chevron fades in (gentle bob) at the bottom edge while there's more below. */
.softscroll-cue {
  position: absolute;
  bottom: 1px;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 13px;
  line-height: 1;
  color: var(--teal);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 3;
}
.softscroll.can-down .softscroll-cue {
  opacity: 0.85;
  animation: softscroll-bob 1.6s ease-in-out infinite;
}
@keyframes softscroll-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(2px); }
}

/* ===== "Map zoom to SA3 area" click list (sidebar) ===== */
/* SA3 "Map Zoom" click list (Housing + Building). The block flex-grows to fill the sidebar; the list
   (a .softscroll-view) scrolls internally so Map Boundaries stays pinned at the bottom. A top border
   separates it from the "Select statistic" section above (matching the Map Boundaries divider). */
.sa3-zoom {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  margin-bottom: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--navy-3);
}
.sa3-list {
  display: flex;
  flex-direction: column;
}
.sa3-zoom-group {
  display: flex;
  flex-direction: column;
  margin-bottom: 6px;
}
.sa3-zoom-ta {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--slate);
  margin: 4px 0 2px;
}
.sa3-zoom-item {
  text-align: left;
  background: transparent;
  border: none;
  border-left: 2px solid transparent;
  color: #c7d8e8;
  font-size: 11px;
  padding: 3px 8px;
  cursor: pointer;
  border-radius: 3px;
  transition: background 0.12s ease, color 0.12s ease;
}
.sa3-zoom-item:hover {
  background: rgba(0, 178, 169, 0.10);
  border-left-color: var(--teal);
  color: var(--white);
}

/* ===== Map area + Leaflet controls (reset · zoom · scale · choropleth legend) + hover tooltip ===== */
.map-area {
  flex: 1;
  min-width: 0;
  position: relative;
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  background: #1e3d5a;
  border-right: 1px solid var(--navy-3);
  position: relative;
}

.map-canvas {
  width: 100%;
  height: 100%;
  min-height: 400px;
  background: #0b1828;
}
/* Leaflet makes the map container keyboard-focusable (tabindex=0) for arrow-key panning,
   so clicking the map focuses it and a window-refocus re-asserts the browser's focus ring
   around the whole map — which reads as a stray border. Suppress that ring; panning still
   works once the map has focus. */
.map-canvas:focus,
.map-canvas:focus-visible {
  outline: none;
}

.map-legend {
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid var(--navy-3);
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 5px 0;
  font-size: 11px;
  color: #c7d8e8;
}

.legend-line {
  display: inline-block;
  width: 22px;
  height: 0;
  flex-shrink: 0;
  border-top-width: 2px;
  border-top-style: solid;
}

.legend-line.dashed {
  border-top-style: dashed;
}

.legend-item-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 1;
}

.legend-toggle {
  display: inline-flex;
  align-items: center;
  width: 32px;
  height: 16px;
  background: #36506d;
  border-radius: 8px;
  cursor: pointer;
  position: relative;
  transition: background 0.2s;
}

.legend-toggle.active {
  background: #00b2a9;
}

.legend-toggle::after {
  content: '';
  position: absolute;
  width: 12px;
  height: 12px;
  background: white;
  border-radius: 50%;
  left: 2px;
  transition: left 0.2s;
}

.legend-toggle.active::after {
  left: 18px;
}

.leaflet-control-attribution {
  font-size: 10px;
}

.map-reset-control {
  border: 1px solid #28435c;
  background: rgba(11, 24, 40, 0.85);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
}

.map-reset-btn {
  display: block;
  width: 54px;
  height: 28px;
  border: none;
  padding: 0;
  background: transparent;
  color: #d7e6f2;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
}

.map-reset-btn:hover {
  background: rgba(255, 255, 255, 0.08);
}

.map-reset-btn:focus-visible {
  outline: 2px solid #7fb8de;
  outline-offset: -2px;
}

/* Map-layers control (top-right) — a small dark panel of two switch toggles ("Satellite", "Roads"),
   reusing the .legend-toggle switch from the boundary legend. Not leaflet-bar (that would style the
   toggle <button>s as control buttons), so the border/shadow are set here directly. */
.map-layers-control {
  border: 1px solid #28435c;
  background: rgba(11, 24, 40, 0.85);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
  border-radius: 4px;
  padding: 7px 9px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.map-layer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.map-layer-label {
  color: #d7e6f2;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.map-layers-control .legend-toggle:focus-visible {
  outline: 2px solid #7fb8de;
  outline-offset: 2px;
}

.leaflet-top.leaflet-left .map-reset-control+.leaflet-control-zoom {
  margin-left: 26px;
}

.leaflet-touch .leaflet-top.leaflet-left .map-reset-control+.leaflet-control-zoom {
  margin-left: 22px;
}

.map-scale {
  margin-left: 10px;
  margin-bottom: 10px;
}

/* Scale bar — snaps to a round distance (1/2/5×10ⁿ) near a target width; rendering.js sets the exact
   pixel width so the bar spans the labelled distance (the 92px here is just the pre-refresh initial).
   box-sizing:border-box so the set width is the tick-to-tick span. */
.map-scale-bar {
  box-sizing: border-box;
  width: 92px;
  border: 2px solid #7fa0be;
  border-top: none;
  color: #d7e6f2;
  background: rgba(11, 24, 40, 0.8);
  font-size: 11px;
  font-weight: 400;
  text-align: center;
  padding: 2px 4px;
  line-height: 1.2;
  border-radius: 0 0 3px 3px;
  white-space: nowrap;
}

.leaflet-tooltip.hier-tooltip {
  background: #0d2742;
  border: 1px solid #1f527a;
  border-radius: 12px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
  color: #d9e9f7;
  padding: 12px;
  min-width: 240px;
  max-width: 280px;
}

.leaflet-tooltip.hier-tooltip::before {
  display: none;
}

.tip-eyebrow {
  font-size: 9px;
  color: #98b6cf;
  margin-bottom: 2px;
}

.tip-title {
  font-size: 14px;
  color: #00b2a9;
  font-weight: 700;
  margin-bottom: 8px;
  line-height: 1.25;
  letter-spacing: 0.2px;
  white-space: normal;
}

/* Active level (the bold-outlined shape) — teal tint over the row, content kept
   exactly in line with every other row (no padding/margin shift, no border). */
.tip-row-active {
  background: rgba(0, 178, 169, 0.13);
}

.tip-row-active .tip-val {
  color: #5feae0;
}

.tip-sep {
  height: 1px;
  background: rgba(255, 255, 255, 0.14);
  margin: 7px 0;
}

.tip-row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  align-items: baseline;
  gap: 10px;
  margin: 4px 0;
}

.tip-key {
  font-size: 9px;
  color: #98b6cf;
  text-transform: none;
}

.tip-val {
  font-size: 9px;
  color: #ffffff;
  font-weight: 600;
  text-align: right;
  max-width: none;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.25;
}

/* Transient "Loading…" cell for population/density rows that depend on the Census view
   (loaded after the map). Muted + italic so it reads as a pending state, not a value. */
.tip-loading {
  color: #98b6cf;
  font-weight: 500;
  font-style: italic;
}

/* ===== Right panel (shared: Housing / Building / Neighbourhood click-to-select stats) ===== */
.right-panel {
  /* 340px keeps the trend-chart note lines (e.g. "…▲ 27.2% vs a year ago") on one line without the
     last word orphan-wrapping (the note needs ~324px including padding). */
  width: 340px;
  background: var(--navy);
  color: #c7d8e8;
  /* The inner .softscroll-view scrolls internally (no visible bar) so a long stats list + the
     charts can't stretch the layout past the viewport; the panel itself is the clipped frame that
     carries the fade shadows. position/flex-column come from the .softscroll class. */
  min-height: 0;
  max-height: calc(100vh - 93px);
  overflow: hidden;
}
.right-panel .softscroll-view {
  padding: 14px;
}

.right-panel h3 {
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  font-size: 10px;
  margin-bottom: 10px;
}

.right-panel p {
  font-size: 12px;
  line-height: 1.4;
}

/* ===== Overview: Region × TLA comparison grid ===== */
/* The shell is the soft-scroll FRAME (also carries .softscroll = position/flex-column). It's CAPPED
   to the viewport so a tall grid on a short/windowed screen scrolls INSIDE it instead of growing the
   page and triggering the document's ugly default scrollbar. 89px = header (56) + page-head (33).
   The second cap (1066px) stops the fill-to-height rows (see .overview-grid) from stretching past
   ~110px each on a very tall monitor: 1066 ≈ 16×2 padding + 44 header + 9×110 rows (at the current
   9 body rows). Below that the viewport cap wins and the rows share whatever height is available
   (down to their 52px floor). If the row count changes, nudge 1066 so max row stays ~110px. */
.overview-shell {
  flex: 1;
  padding: 16px;
  background: var(--paper);
  max-height: min(calc(100vh - 89px), 1066px);
  overflow: hidden;
}
/* Inner: the grid itself AND the scroll-view (.softscroll-view hides the bar + adds the fade/cue).
   First column sizes to the widest row label; data columns never shrink below their content —
   combined with nowrap this guarantees NO word wrapping (it scrolls horizontally if ever too wide).
   Rows FILL the shell height: the grid is a flex child of the (flex-column) shell with flex:1, so it
   flex-fills the shell's real height. The body rows then each take an equal 1fr slice of that height
   (floored at 52px). So the table fills a tall window (no dead space below) and compresses on a short
   one (all rows stay visible down to ~600px tall, then it scrolls) — instead of sitting at a fixed
   height that clipped when short / left a gap when tall. The equal 1fr tracks also give every row an
   identical height, which is why the old 64px cell floor is no longer needed (see below). Phones opt
   out of this in the ≤560 block. */
.overview-grid {
  display: grid;
  flex: 1;          /* fill the flex-column shell's height (min-height:0 lets it also shrink) */
  min-height: 0;
  grid-template-columns: max-content repeat(4, minmax(max-content, 1fr));
  /* Header row sizes to its content; EVERY body row (however many) shares the remaining height
     equally via grid-auto-rows (floored at 52px). Using grid-auto-rows rather than an explicit
     repeat(N) keeps this correct if a metric row is ever added or removed. */
  grid-template-rows: auto;
  grid-auto-rows: minmax(52px, 1fr);
  gap: 0;
  overflow-x: auto;
}
/* Soft-scroll fades tuned to the light --paper background (the default .softscroll fades to navy). */
.overview-shell.softscroll::before {
  background: linear-gradient(to bottom, var(--paper), rgba(244, 246, 248, 0));
}
.overview-shell.softscroll::after {
  background: linear-gradient(to top, var(--paper), rgba(244, 246, 248, 0));
}
.overview-shell .softscroll-cue {
  color: var(--teal-dark);
  font-size: 16px;
  font-weight: 700;
  bottom: 7px;
}

.cell {
  min-height: 44px;
  border: 1px solid #e2e8f0;
  background: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #93a2b1;
  font-size: 13px;
  text-align: center;
  padding: 6px 14px;
  white-space: nowrap;   /* never wrap any header, label, or value */
}

/* Every body row is the SAME height — now guaranteed by the grid's equal 1fr row tracks (see
   .overview-grid), so no per-cell floor is needed to keep them uniform. min-height:0 lets the rows
   compress on a short window (the 52px track floor, not the cell, sets the minimum); a floor here
   would block that compression and reintroduce clipping. Excludes the (shorter) header row. */
.overview-shell .cell:not(.hdr) {
  min-height: 0;
}

.cell.hdr {
  background: #f8fafc;
  color: #334155;
  font-weight: 700;
  font-size: 14px;
}

/* Overview column headers: distinct colors for each TA column */
.cell.hdr.col-1 {
  background: #0d5a54;
  color: #ffffff;
}

.cell.hdr.col-2 {
  background: #1a56a0;
  color: #ffffff;
}

.cell.hdr.col-3 {
  background: #5b21b6;
  color: #ffffff;
}

.cell.hdr.col-4 {
  background: #b45309;
  color: #ffffff;
}

.cell.left {
  background: #fcfdff;
  color: #475569;
  font-weight: 600;
  font-size: 14px;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  padding-left: 14px;
  padding-right: 18px;
}

/* Metric name + muted provenance sub-line (source · recency) in the left label column. */
.ov-row-main {
  font-size: 14px;
  font-weight: 600;
  color: #475569;
}

.ov-row-sub {
  margin-top: 3px;
  font-size: 10px;
  font-weight: 500;
  /* Provenance line (source · recency) on the near-white left cell. Was #8aa0b5 (~2.6:1 — fails
     WCAG AA); darkened to ~5:1 so this small text is legible while staying muted under the metric
     name (#475569). This is the light Overview page, so it needs a DARKER grey (not the lifted
     --slate, which is tuned for the dark panels). */
  color: #5a6f83;
  white-space: nowrap;
}

/* Real metric values stand out; "Metric pending" placeholders stay muted/italic. The value cell
   stacks the figure over an optional year-on-year change chip. */
.cell.value:not(.hdr):not(.left) {
  flex-direction: column;
  color: #0f2535;
  /* Gentle fade for the row-hover highlight (see .row-hot / .cell-hot below). */
  transition: background-color 0.13s ease;
}

.ov-val {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.3px;
  line-height: 1.1;
}

/* Year-on-year change chip. The arrow (▲/▼) shows DIRECTION; colour is intentionally neutral
   for now — "up" doesn't reliably mean "good" across these metrics (e.g. a rising social-housing
   waitlist or rent is bad), so green/red would mislead. The .ov-up/.ov-dn classes are still set
   in overview.js, so a per-metric sentiment colour can be reinstated by giving them colours again. */
.ov-chg {
  margin-top: 3px;
  font-size: 10px;
  font-weight: 600;
  color: #5b7184;
}

.ov-up { color: #5b7184; }
.ov-dn { color: #5b7184; }
.ov-flat { color: #5b7184; }

.cell.pending:not(.hdr):not(.left) {
  color: #aebccb;
  font-style: italic;
}

/* Sourced metric whose value is still loading — pulses so it reads as "arriving", clearly
   distinct from the static italic "Metric pending" (a metric we haven't wired up at all). */
.cell.loading:not(.hdr):not(.left) {
  color: #6b8299;
  font-style: italic;
  animation: gshd-pulse 1.1s ease-in-out infinite;
}

@keyframes gshd-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }

/* Overview body cells: subtle column tint + accent bar */
.cell.col-1:not(.hdr):not(.left) {
  background: #eefaf9;
  border-bottom: 3px solid #00b2a9;
}

.cell.col-2:not(.hdr):not(.left) {
  background: #eef5ff;
  border-bottom: 3px solid #4a90c4;
}

.cell.col-3:not(.hdr):not(.left) {
  background: #f4efff;
  border-bottom: 3px solid #7c3aed;
}

.cell.col-4:not(.hdr):not(.left) {
  background: #fff6ec;
  border-bottom: 3px solid #f59e0b;
}

/* Row hover (reading aid, mouse only — wired in overview.js, hover-capable devices). Pointing at a
   VALUE cell lifts its whole row (each cell deepens its own column tint, gently), and the single
   hovered cell goes a shade darker still, so the eye finds both the row and the exact figure. The
   `.overview-grid` ancestor makes these out-specify the base `.cell.col-N:not(.hdr):not(.left)` tints. */
.overview-grid .cell.value.row-hot.col-1 { background: #d8f3f0; }
.overview-grid .cell.value.row-hot.col-2 { background: #dcebfc; }
.overview-grid .cell.value.row-hot.col-3 { background: #e9ddfc; }
.overview-grid .cell.value.row-hot.col-4 { background: #ffecd6; }
.overview-grid .cell.value.cell-hot.col-1 { background: #bfebe6; }
.overview-grid .cell.value.cell-hot.col-2 { background: #c6def8; }
.overview-grid .cell.value.cell-hot.col-3 { background: #d8c6f7; }
.overview-grid .cell.value.cell-hot.col-4 { background: #ffddb4; }

/* ============================================================================================
   RESPONSIVE / MEDIA QUERIES — reader's map
   --------------------------------------------------------------------------------------------
   Mobile support is a CSS-only override layer on top of the desktop base above. Rules key on
   WIDTH for layout (space-driven — a shrunk desktop window benefits too) and on TOUCH
   (`hover: none`) ONLY for input affordances (the nav). Breakpoints:
     • ≤1000px — tablet: narrower sidebar/panel; hide the centred "Mapped at…" label.
     • ≤800px  — the substantive block: stack the 3 columns, map 60vh, page scrolls as one.
     • ≤560px  — narrow-phone tuning: Overview fit-to-width grid, bigger tap targets.
   ⚠️ The blocks are SPLIT across the file ON PURPOSE — an @media query adds NO specificity, so an
   override only wins by coming LATER in source order than the base rule it overrides. So each
   component's responsive tweak sits just after that component's base rules:
     • ≤1000 / ≤800 / ≤560 core layout ... here (right below the base component styles).
     • ≤800 map-attribution wrap ......... after the .map-attrib base rules (search "map-attrib").
     • ≤800 guide drawer z-index ......... after the .guide-* base rules (end of file).
     • ≤800 & ≤560 + (hover:none) nav .... end of file (touch-only; must win over the base nav).
   Jump between them with a "@media" search. Don't merge them blindly — the ordering is load-bearing.
   ============================================================================================ */
@media (max-width: 1000px) {
  .sidebar {
    width: 180px;
  }

  .right-panel {
    width: 260px;
  }

  /* The dead-centred "Mapped at SA2 level" label is absolutely positioned (so it never shifts as
     the right-hand stats change), which means it doesn't reserve layout space — below ~966px it
     starts overlapping the right-aligned geography stats. Hide it through that band (it's a plain-
     English restatement of the stats, which already show "64 SA2s" etc.); it reappears above 1000px
     where there's room for both. (It's also hidden in the ≤800 block, where the stats wrap.) */
  .page-head-center {
    display: none;
  }
}

@media (max-width: 800px) {
  /* The header stays a SINGLE 56px row at EVERY width — the tabs chevron-scroll inline (see the base
     .nav rules) instead of wrapping onto a taller second row. z-index 1100 keeps this sticky bar
     above Leaflet's controls (z-index 1000): on a phone the page scrolls, so the map slides up UNDER
     the bar and its zoom/scale controls would otherwise bleed over the header. */
  .topbar {
    z-index: 1100;
  }
  /* NOTE: the guide drawer/backdrop z-index bumps that keep them above this raised bar
     live in a media block at the END of the file — they must come after the base
     .guide-* rules (which are defined later) to win, since @media adds no specificity. */

  /* (No header rules here on purpose — the base .topbar/.nav own the single-row layout at every
     width, and the touch-only blocks at the end of the file own the phone override.) */

  .page {
    min-height: calc(100vh - 125px);
  }

  /* Stack the three columns. On a narrow screen the map is the star, so it comes
     first; the click-to-select detail panel sits directly beneath it (a tap's result
     is then visible without scrolling past the controls); the sidebar controls come
     last. Reordered with flex `order` only — the DOM is untouched, so desktop is
     unaffected and every JS hook still targets the same nodes. */
  .frame {
    flex-direction: column;
  }
  .map-area { order: 1; }
  .right-panel { order: 2; }
  .sidebar { order: 3; }

  /* The page-head stays a SINGLE line at every width. The centred "Mapped at … level" label is
     already hidden (≤1000 block), and the geography stats are hidden by G.fitPageHeadStats when they
     can't fit beside the title — so nothing here wraps to a second line. */

  /* Full-width, natural-height stacked panels: drop the desktop viewport-height caps
     (which turn each panel into its own inner scroll region) so the whole page scrolls
     as one on a phone instead of nesting scroll areas. */
  .sidebar,
  .right-panel {
    width: 100%;
    max-height: none;
    overflow: visible;
  }

  /* Give the map a solid, usable slice of the viewport rather than a letterbox, and
     size the canvas to match so it can't overflow onto the panel below on short screens. */
  .map-area {
    flex: 0 0 auto;
    height: 60vh;
    min-height: 340px;
    border-right: none;
    border-bottom: 1px solid var(--navy-3);
  }
  .map-canvas {
    min-height: 340px;
  }

  /* NOTE: the map tooltip is kept clear of the map edges by clamping its POSITION (see
     G.clampTooltipToMap in rendering.js) rather than by shrinking it here — shrinking forced
     the box to stack tall and then clipped the top/bottom instead. So it keeps its natural,
     readable width and just slides back in-bounds on a small (touch) screen. */

  /* NOTE: the mobile map-attribution wrap fix lives in a media block AFTER the base
     .map-attrib rules (further down) — those set white-space:nowrap later in the file, so an
     override here would lose (an @media query adds no specificity). */

  /* Overview: engage the FIT-TO-WIDTH grid here (≤800px) — BEFORE the last column would clip
     (~760px in the content-sized + horizontal-scroll layout that applies 801–1000px). The four
     districts share the width (minmax(0,1fr)) so nothing scrolls off the edge; a MODERATE font step
     keeps them readable (the ≤560 block tightens further for phones). The label column flexes (1.3fr)
     so it yields room to the data columns instead of hogging ~205px. */
  .overview-grid {
    grid-template-columns: minmax(96px, 1.3fr) repeat(4, minmax(0, 1fr));
  }
  .cell {
    padding: 6px 8px;
    white-space: normal;   /* let headers/labels wrap rather than force a wider column */
    line-height: 1.18;
  }
  .cell.hdr {
    font-size: 11.5px;
    padding: 6px 6px;
  }
  .cell.left {
    padding: 8px 10px;
  }
  .ov-row-main { font-size: 12px; }
  .ov-row-sub { font-size: 8.5px; white-space: normal; }
  .ov-val { font-size: 17px; }
  .ov-chg { font-size: 9px; white-space: normal; }   /* wrap the "vs prior yr" chip — the narrow mobile cells can't hold it on one line */
  /* Keep the label column pinned (harmless once it fits; a safety net if a label is ever wider). */
  .overview-grid > .cell:first-child,
  .overview-grid .cell.left {
    position: sticky;
    left: 0;
    z-index: 3;
  }
}

/* ===== Phone tuning: tighter spacing + touch-friendlier controls ===== */
@media (max-width: 560px) {
  /* (No nav rules here on purpose — tabs keep their base 12px and chevron-scroll; the touch-phone
     block at the end of the file is the only place they shrink.) */

  /* Overview on a phone: instead of an ugly horizontal scroll, fit all four geography
     columns on screen at once. The comparison matrix stays intact (region + 3 districts
     side by side) with the YoY chips; columns share the width evenly and headers wrap. */
  .overview-shell {
    padding: 8px;
    overflow: visible;
    max-height: none;
  }
  .overview-grid {
    grid-template-columns: minmax(78px, 1.1fr) repeat(4, minmax(0, 1fr));
    /* Opt out of the desktop fill-to-height rows: on a phone the page scrolls as one region, so
       rows size to their content (no shell cap, no equal-height stretch, no flex-fill). */
    grid-template-rows: none;
    flex: none;
    min-height: 0;
  }
  .overview-shell .softscroll-cue {
    display: none;
  }
  .cell {
    padding: 5px 3px;
    font-size: 11px;
    white-space: normal;   /* let long headers wrap rather than force a scroll */
    line-height: 1.15;
  }
  /* Drop the desktop 64px row floor so rows size to their (now smaller) content. */
  .overview-shell .cell:not(.hdr) {
    min-height: 0;
  }
  .cell.left {
    padding: 6px 5px;
  }
  .cell.hdr {
    font-size: 9px;
    padding: 6px 2px;
  }
  .ov-row-main {
    font-size: 10.5px;
  }
  .ov-row-sub {
    font-size: 8px;
    white-space: normal;
    margin-top: 2px;
  }
  .ov-val {
    font-size: 12.5px;
  }

  /* Bigger tap targets for the SA3 zoom list on touch screens. */
  .sa3-zoom-item {
    padding: 7px 8px;
    font-size: 12px;
  }

}

/* ===== Neighbourhood Insights: metric selector ===== */
.metric-selector {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 12px;
}
/* Sidebar skeleton shown while the metric catalogue/data loads (replaced by the real
   selector once data arrives). Translucent-white bars shimmer on the dark sidebar. */
.metric-skeleton {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 2px 0;
}
.metric-loading-text {
  font-size: 11px;
  color: var(--slate);
}
.metric-skeleton .skel-row {
  width: 100%;
  height: 12px;
  border-radius: 4px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.04) 25%, rgba(255, 255, 255, 0.13) 37%, rgba(255, 255, 255, 0.04) 63%);
  background-size: 360px 100%;
  animation: gshd-shimmer 1.2s ease-in-out infinite;
}
.metric-group {
  display: flex;
  flex-direction: column;
}
.metric-group-label {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--slate);
  margin-bottom: 4px;
}
.metric-item {
  text-align: left;
  background: transparent;
  border: none;
  border-left: 2px solid transparent;
  color: #c7d8e8;
  font-size: 11px;
  padding: 4px 8px;
  cursor: pointer;
  border-radius: 3px;
  transition: background 0.12s ease, color 0.12s ease;
}
.metric-item:hover {
  background: rgba(0, 178, 169, 0.10);
  color: var(--white);
}
/* Sub-items (the dwelling-type breakdown under "All new dwellings"): a slight indent so they read
   as children of the total above them. */
.metric-item-sub {
  padding-left: 20px;
}
.metric-item.active {
  background: rgba(0, 178, 169, 0.16);
  border-left-color: var(--teal);
  color: var(--white);
  font-weight: 600;
}
/* A View whose data source isn't loaded yet: shown but disabled. */
.metric-item.pending {
  color: #7a8fa0;
  cursor: not-allowed;
  font-style: italic;
}
.metric-item.pending:hover {
  background: transparent;
  color: #7a8fa0;
}

/* ===== Map attribution: compact, collapsible (full credits kept) ===== */
.map-attrib {
  display: flex;
  align-items: flex-start;   /* chevron aligns with the first line of the wrapped credit */
  gap: 6px;
  background: rgba(13, 31, 53, 0.8);
  border-radius: 6px;
  padding: 1px 5px;
  font-size: 10px;
  line-height: 1.45;
  color: #c7d8e8;
  max-width: calc(100vw - 916px);   /* the open room to the LEFT of this control, between it and the scale bar (viewport minus sidebar + panel + this control's right offset + scale clearance). With white-space:normal the credit stays ONE line while it fits and only WRAPS when the map narrows — never clipping or reaching the scale. */
}

/* Sits just to the LEFT of the bottom-right legend and opens leftward. margin-right clears the legend
   (right:12px + ~174px wide + ~13px gap); the two-class selector beats Leaflet's default 10px control
   margin. margin-bottom:12px lines its baseline up with the legend. The legend itself is unchanged. */
.leaflet-bottom.leaflet-right .map-attrib {
  margin-right: 201px;
  margin-bottom: 12px;
}
.map-attrib a {
  color: #8fd3cd;
  text-decoration: none;
}
.map-attrib a:hover {
  text-decoration: underline;
}
.map-attrib-text {
  white-space: normal;   /* wrap into the width-capped block (was nowrap) so it never clips off the map edge */
}
.map-attrib-collapsed .map-attrib-text {
  display: none;
}
.map-attrib-toggle {
  border: none;
  background: transparent;
  color: #c7d8e8;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0 2px;
}
.map-attrib-toggle:hover {
  color: var(--white);
}

/* Below 1000px there's no room for the credit to expand sideways between the bottom-left scale and the
   bottom-right legend, so instead of opening inline it opens UPWARD as a small popover above the chevron
   — clear of both. The collapsed chevron itself never collides and stays put, so the attribution is
   always one tap away (Esri ToU satisfied). Above 1000px the inline open-left behaviour (the base rules)
   is untouched. Placed after the base rules on purpose — an @media query adds no specificity, so it only
   overrides by coming later in source order. */
@media (max-width: 1000px) {
  .map-attrib {
    position: relative;
    max-width: none;   /* the base calc() capped the INLINE text; the text is now an absolute popover, so the control only needs to fit the chevron */
  }
  .map-attrib:not(.map-attrib-collapsed) .map-attrib-text {
    position: absolute;
    right: 0;                                     /* right edge aligns with the chevron (already left of the legend) */
    bottom: calc(100% + 4px);                     /* floats just above the control */
    max-width: min(260px, calc(100vw - 193px));   /* capped so it never overflows the map's left edge */
    background: rgba(13, 31, 53, 0.92);
    border: 1px solid #28435c;
    border-radius: 6px;
    padding: 6px 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  }
}

/* ===== Neighbourhood Insights: choropleth legend (map overlay) ===== */
.choropleth-legend {
  position: absolute;
  right: 12px;
  bottom: 12px;   /* sits in the bottom-right corner (was raised to 34px to clear the attribution, which now tucks in to the LEFT of the legend instead of below it) */
  z-index: 500;
  background: rgba(13, 31, 53, 0.88);
  border: 1px solid var(--navy-3);
  border-radius: 8px;
  padding: 10px 12px;
  min-width: 174px;
  color: #c7d8e8;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
  transition: opacity 0.12s ease;
}
/* The legend and the map controls (Reset, zoom, scale, attribution) all live OUTSIDE the
   transformed map-pane, so an open tooltip — trapped inside that pane — can't be layered above
   them via z-index. Instead they YIELD: any one of them fades out only while the tooltip actually
   overlaps it, and fades back the moment it moves off. Toggled by G.wireTooltipYield (rendering.js). */
.leaflet-control {
  transition: opacity 0.12s ease;
}
.choropleth-legend.gshd-tooltip-yield,
.leaflet-control.gshd-tooltip-yield {
  opacity: 0;
  pointer-events: none;
}
.choropleth-legend .legend-title {
  font-size: 12px;
  color: var(--white);
  font-weight: 600;
  margin-bottom: 7px;
}
.choropleth-legend .legend-ramp {
  display: flex;
  height: 12px;
  border-radius: 3px;
  overflow: hidden;
}
.choropleth-legend .legend-ramp span {
  flex: 1;
}
.choropleth-legend .legend-ends {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--slate);
  margin-top: 5px;
}
/* Small "?" in the legend title (Building map) — opens the shading note popup. */
.choropleth-legend .legend-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}
.choropleth-legend .legend-help {
  flex: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid var(--navy-3);
  background: rgba(255, 255, 255, 0.08);
  color: #c7d8e8;
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.choropleth-legend .legend-help:hover {
  background: rgba(0, 178, 169, 0.25);
  color: var(--white);
}
.choropleth-legend .legend-help-pop {
  position: absolute;
  bottom: calc(100% + 6px);
  right: 0;
  width: 200px;
  background: rgba(13, 31, 53, 0.97);
  border: 1px solid var(--navy-3);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 10px;
  line-height: 1.35;
  color: #c7d8e8;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
.choropleth-legend .legend-nodata {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--slate);
  margin-top: 7px;
}
.legend-nodata-swatch {
  width: 13px;
  height: 13px;
  border-radius: 3px;
  background: #5b6770;
  display: inline-block;
}

/* ===== Neighbourhood Insights: right-panel SA1 profile ===== */
.np-heading-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  /* Tightened from 10px to reclaim vertical room in the panel (the red selection box below added a
     little height); keeps the STATISTICS heading close to the first geo row. */
  margin-bottom: 5px;
}

/* The selected area's identity rows (SA2 Name + SA2 Code on Housing/Building; SA1 Code on
   Neighbourhood) carry a teal left-accent bar (see .np-geo-sel below) — the map keeps its red
   selection outline, but the panel marks the selection in teal to stay in the navy/teal palette.
   The bar bleeds slightly into the panel padding (negative side margin) so the label/value columns
   stay aligned with the un-highlighted rows above. */
.np-geo-sel {
  /* Selected-area marker on the identity rows: a teal left-accent bar (reuses the app's "active"
     idiom — nav tabs, the metric list). Teal keeps it in the navy/teal palette (the map keeps its
     red selection outline; the panel no longer echoes that red). ZERO vertical footprint so the
     marked rows sit at exactly the same position as un-marked rows — otherwise the row inside the
     marker (SA2 Name is marked on Housing/Building but a plain row on Neighbourhood) jumps a few px
     when switching pages. `border-left` adds only horizontal width, no height; the extra -4px left
     margin + 1px left padding exactly offsets the 3px bar so the label text stays column-aligned. */
  margin: 0 -8px 0 -12px;
  padding: 0 8px 0 9px;
  border-left: 3px solid var(--teal);
  border-radius: 0 4px 4px 0;
}
.np-heading {
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  font-size: 10px;
  margin-bottom: 0;
}
.np-source {
  font-size: 9px;
  color: var(--slate);
}
.np-header {
  margin-bottom: 0;
}
.np-geo-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 2px 0;
}
.np-geo-label {
  font-size: 11px;
  color: var(--slate);
}
.np-geo-val {
  font-size: 12px;
  color: var(--white);
  font-weight: 400;
  text-align: right;
}
.np-geo-val-strong {
  font-weight: 700;
}
.np-empty {
  font-size: 12px;
  color: var(--slate);
  line-height: 1.5;
}

/* Shared empty-state for the three map right-panels (nothing selected yet) — one look across
   Housing / Building / Neighbourhood, built by G.buildEmptyPanelHtml. A centred map-pin glyph
   with a soft pulse ring, a primary prompt and a page-specific hint. The two <p> rules are
   scoped under .right-panel so they out-specify the base `.right-panel p` (which would otherwise
   force a 12px size). Centres within a min-height so it reads as intentional, not top-crammed. */
.np-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 10px;
  min-height: 55vh;
  padding: 24px 14px;
}
/* Mobile: the desktop 55vh centres the prompt in the tall fixed side panel, but on a phone the panel
   stacks ABOVE the sidebar, so that height just opens a big void that pushes the sidebar far down.
   Collapse it to content height. Placed AFTER the base rule (deliberately, not in the ≤800px block
   earlier in the file) so it actually wins — @media adds no specificity, so source order decides. */
@media (max-width: 800px) {
  .np-empty-state {
    min-height: 0;
    padding: 30px 14px 34px;
  }
}
.np-empty-icon {
  position: relative;
  width: 42px;
  height: 42px;
  color: var(--teal);
}
.np-empty-icon svg {
  position: relative;
  width: 100%;
  height: 100%;
  display: block;
  filter: drop-shadow(0 2px 6px rgba(0, 178, 169, 0.35));
}
/* Soft pulse ring behind the pin head — echoes the map's SA3 red-pulse motif in teal. */
.np-empty-icon::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 46%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  border: 1.5px solid var(--teal);
  opacity: 0;
  animation: gshd-empty-pulse 2.6s ease-out infinite;
}
@keyframes gshd-empty-pulse {
  0% { transform: scale(0.55); opacity: 0.5; }
  70% { opacity: 0; }
  100% { transform: scale(2); opacity: 0; }
}
.right-panel .np-empty-primary {
  font-size: 13px;
  font-weight: 600;
  color: #dbe7f2;
  margin: 0;
}
.right-panel .np-empty-hint {
  font-size: 11px;
  line-height: 1.5;
  color: var(--slate);
  margin: 0;
  max-width: 210px;
  /* Reserve 3 lines (3 × 1.5em) so the whole empty-state block is the SAME height on every page
     regardless of how many lines a page's hint wraps to — the block is vertically centred, so an
     equal height keeps the pin at an identical position across Housing / Building / Neighbourhood
     (Neighbourhood's hint is only 2 lines and would otherwise sit the pin ~8px lower). If a hint
     ever needs 4 lines, bump this to 6em. */
  min-height: 4.5em;
}
/* Respect reduced-motion: hold the ring static (no infinite pulse). */
@media (prefers-reduced-motion: reduce) {
  .np-empty-icon::before {
    animation: none;
  }
}
.np-group-label {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--teal);
  margin: 12px 0 4px;
}
/* A section header that carries a right-aligned grain+source tag (Housing/Building, where a panel
   mixes sources at different grains). The label sits left, the tag right — same baseline. The label
   drops its own vertical margin here because the row provides it. Neighbourhood is single-source and
   keeps the plain .np-group-label heading instead. */
.np-group-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin: 12px 0 4px;
}
.np-group-head .np-group-label {
  margin: 0;
}
/* The grain + source tag, e.g. "SA2 · CENSUS 2023 & REINZ". Muted so it informs without competing
   with the teal section label. */
.np-group-src {
  font-size: 9px;
  letter-spacing: 0.3px;
  color: var(--slate);
  text-align: right;
  white-space: nowrap;
}
/* Separator above a stats-section header. Used on the Housing AND Neighbourhood panels so every
   section bar has an identical 14px-above / 12px-below gap. The geo header no longer carries its own
   divider (see .np-header) — the first section's .np-group-sep provides that bar instead. Applies to
   both a plain .np-group-label (Neighbourhood) and a .np-group-head row (Housing/Building). */
.np-group-sep {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--navy-3);
}
.np-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding: 3px 0;
}
.np-label {
  font-size: 11px;
  color: var(--slate);
}
.np-val {
  font-size: 11px;
  color: var(--white);
  font-weight: 600;
  text-align: right;
}
/* A panel value whose source isn't loaded yet (Housing: Median Rent SA2, the TLA affordability stats). */
.np-pending {
  color: #7a8fa0;
  font-weight: 500;
  font-style: italic;
}
/* The row matching the left-selected statistic (set in renderPanel). */
.np-row-active {
  background: rgba(0, 178, 169, 0.16);
  margin: 0 -8px;
  padding: 3px 8px;
  border-radius: 4px;
}
.np-row-active .np-label {
  color: var(--white);
}
.np-row-active .np-val {
  color: var(--teal);
}
/* "Where this SA1 sits" distribution. Flows after the metric groups with a clear gap above its
   divider (a fixed margin, not auto: the right panel scrolls now, so margin-top:auto would collapse
   to 0 and crowd the separator against the last metric row). */
.np-dist {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--navy-3);
}
.np-dist-title {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: var(--slate);
  margin-bottom: 8px;
}
.np-dist-svg {
  display: block;
  width: 100%;
  height: 44px;
  overflow: visible;
}
.np-dist-ends {
  display: flex;
  justify-content: space-between;
  font-size: 9px;
  color: var(--slate);
  margin-top: 3px;
}
.np-dist-note {
  font-size: 11px;
  color: #c7d8e8;
  margin-top: 7px;
}
.np-dist-note strong {
  color: var(--white);
  font-weight: 700;
}

/* Housing right-panel trend sparklines (price line + benchmark, sales bars). */
.np-trend-block {
  margin-top: 16px;
}
.np-trend-block .np-dist {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.np-trend-block .np-dist + .np-dist {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px solid var(--navy-3);
}
.np-trend-svg {
  display: block;
  width: 100%;
  /* Trimmed 45→42px (and the top gap 8→6px) to reclaim a few px per chart — the viewBox is unchanged
     (preserveAspectRatio="none"), so the line just scales down. Applies to every trend chart. */
  height: 42px;
  margin-top: 6px;
  overflow: visible;
}
.np-trend-note {
  font-size: 11px;
  color: #c7d8e8;
  margin-top: 6px;
}
.np-trend-note strong {
  color: var(--white);
  font-weight: 700;
}
.np-trend-chg {
  color: #8aa0b5;
}
.np-trend-legend {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 9px;
  color: #7a8fa0;
  margin-top: 4px;
}
.np-trend-key {
  display: inline-block;
  width: 12px;
  height: 0;
  border-top: 2px solid transparent;
  margin-left: 8px;
}
.np-trend-key-sa2 {
  border-top-color: #00b2a9;
  margin-left: 0;
}
.np-trend-key-bench {
  border-top-style: dashed;
  border-top-color: #9fb3c4;
}
.np-trend-key-sales {
  border-top-color: #4a90c4;
}
.np-trend-loading {
  font-size: 10px;
  font-style: italic;
  color: #7a8fa0;
  margin-top: 10px;
}
.np-trend-foot {
  font-size: 9px;
  font-style: italic;
  color: #7a8fa0;
  margin-top: 8px;
}
/* Hover scaffolding: a transparent hit-rect captures the mouse; a guide line + dot(s) appear at the
   nearest quarter (the note line below shows that quarter's values). All hidden at rest. */
.np-trend-svg.np-tp-price,
.np-trend-svg.np-tp-sales,
.np-trend-svg.np-rt-svg {
  cursor: crosshair;
}
.np-rt-guide {
  stroke: #c7d8e8;
  stroke-width: 1;
  opacity: 0.5;
  pointer-events: none;
}
.np-rt-dot {
  fill: #00b2a9;
  pointer-events: none;
}
.np-tp-hit {
  pointer-events: all;
}
.np-tp-guide {
  stroke: #c7d8e8;
  stroke-width: 1;
  opacity: 0.5;
  pointer-events: none;
}
.np-tp-dot-sa2 {
  fill: #00b2a9;
  pointer-events: none;
}
.np-tp-dot-bench {
  fill: #9fb3c4;
  pointer-events: none;
}

/* Affordability chart: reuses the price-chart guide/dot treatment. */
.np-aff-guide {
  stroke: #c7d8e8;
  stroke-width: 1;
  opacity: 0.5;
  pointer-events: none;
}
.np-aff-dot-ta {
  fill: #00b2a9;
  pointer-events: none;
}
.np-aff-dot-bench {
  fill: #9fb3c4;
  pointer-events: none;
}

/* Affordability metric toggle (Housing / Rental / Serviceability). */
.np-aff-toggle {
  display: flex;
  gap: 4px;
  margin-bottom: 8px;
}
.np-aff-tab {
  flex: 1 1 auto;
  padding: 4px 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2px;
  color: #9fb3c4;
  background: var(--navy-2);
  border: 1px solid var(--navy-3);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.np-aff-tab:hover {
  color: var(--white);
  border-color: #2f4763;
}
.np-aff-tab.active {
  color: #04141f;
  background: #00b2a9;
  border-color: #00b2a9;
}

/* Each affordability stat's own as-of month, inline just left of the value (keeps the row to one line). */
.np-aff-asof {
  font-size: 10px;
  font-weight: 400;
  color: var(--slate);
  margin-right: 6px;
}
.np-val.np-loading {
  font-style: italic;
  color: #7a8fa0;
}

/* Inside a trend toggle the chart's own title is redundant (the toggle button labels it). */
.np-trend-block .np-dist-title {
  display: none;
}

/* Affordability chart sits right under its stat rows — no separator line (overrides .np-dist). */
.np-aff-chart {
  margin-top: 12px;
  padding-top: 0;
  border-top: 0;
}

/* ============================================================================================
   Public user guide — header "Guide" button + per-page "?" open one right-hand slide-over drawer
   (built in js/presentation/guide.js). Dark-navy themed to match the right panels; the body is a
   .softscroll region so it never shows an ugly bar. Progressive disclosure: plain text up top,
   "Data & methodology" + "Glossary" collapsed in <details> at the bottom.
   ============================================================================================ */

/* Header trigger — a teal-accented pill on the navy top bar. */
.guide-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px 0 8px;
  border: 1px solid var(--navy-3);
  border-radius: 14px;
  background: transparent;
  color: #c7d8e8;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.guide-btn:hover {
  border-color: var(--teal);
  color: #fff;
  background: rgba(0, 178, 169, 0.12);
}
.guide-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--teal);
  color: #04121f;
  font-size: 11px;
  font-weight: 800;
}

/* Dimmed backdrop behind the drawer. */
.guide-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(4, 12, 20, 0.5);
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 1000;
}
.guide-backdrop.is-open {
  opacity: 1;
}

/* The slide-over drawer (also carries .softscroll → flex column + fade shadows). */
.guide-drawer {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  width: 420px;
  max-width: 92vw;
  background: var(--navy);
  color: #c7d8e8;
  z-index: 1001;
  box-shadow: -8px 0 30px rgba(0, 0, 0, 0.35);
  transform: translateX(100%);
  transition: transform 0.22s ease;
}
.guide-drawer.is-open {
  transform: translateX(0);
}
.guide-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px 18px 12px;
  border-bottom: 1px solid var(--navy-3);
  flex-shrink: 0;
}
.guide-title {
  font-size: 15px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.3px;
}
.guide-close {
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: #8aa0b5;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.guide-close:hover {
  background: var(--navy-2);
  color: #fff;
}
.guide-body {
  padding: 16px 18px 28px;
}

/* --- content --- */
.guide-welcome {
  font-size: 18px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.2px;
  margin-bottom: 10px;
}
.guide-lead {
  font-size: 13.5px;
  line-height: 1.5;
  color: #d7e4f0;
}
.guide-note {
  margin-top: 8px;
  font-size: 11.5px;
  color: var(--slate);
  font-style: italic;
}
.guide-pagelist {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.guide-pageline {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.guide-pageline-name {
  font-size: 12px;
  font-weight: 700;
  color: var(--teal);
}
.guide-pageline-desc {
  font-size: 12px;
  line-height: 1.4;
  color: #c7d8e8;
}
.guide-sec {
  margin-top: 20px;
}
.guide-h {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--teal);
  font-weight: 700;
  margin-bottom: 8px;
}
.guide-p {
  font-size: 12.5px;
  line-height: 1.5;
  color: #c7d8e8;
  margin-bottom: 6px;
}
.guide-read {
  color: #a9c0d6;
}
.guide-read-tag {
  display: inline-block;
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  font-weight: 700;
  color: var(--navy);
  background: var(--teal);
  border-radius: 3px;
  padding: 1px 5px;
  margin-right: 4px;
  vertical-align: middle;
}
.guide-def {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-bottom: 9px;
}
.guide-def-term {
  font-size: 12px;
  font-weight: 700;
  color: #e4eef7;
}
.guide-def-desc {
  font-size: 12px;
  line-height: 1.4;
  color: #b6c8da;
}

/* Layer 3 — collapsed by default. */
.guide-details {
  margin-top: 18px;
  border-top: 1px solid var(--navy-3);
  padding-top: 4px;
}
.guide-details > summary {
  cursor: pointer;
  list-style: none;
  padding: 8px 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--teal);
  font-weight: 700;
}
.guide-details > summary::-webkit-details-marker {
  display: none;
}
.guide-details > summary::before {
  content: "▸ ";
  color: var(--slate);
}
.guide-details[open] > summary::before {
  content: "▾ ";
}
.guide-details-body {
  padding: 6px 0 4px;
}
.guide-sources {
  margin: 6px 0 0;
  padding-left: 18px;
}
.guide-sources li {
  font-size: 11.5px;
  line-height: 1.5;
  color: #b6c8da;
}

/* Lock the background while the drawer is open. */
body.guide-open {
  overflow: hidden;
}

/* On mobile the topbar is lifted to z-index 1100 (so the scrolling map can't bleed its
   Leaflet controls over the sticky header). The guide drawer + backdrop must sit above
   that raised bar. This block is placed AFTER the base .guide-* rules on purpose: an
   @media query adds no specificity, so it only wins by coming later in source order. */
@media (max-width: 800px) {
  .guide-backdrop {
    z-index: 1200;
  }
  .guide-drawer {
    z-index: 1210;
  }
}

/* ===== Mobile phones/tablets (touch, no hover): page tabs on their OWN full-width row =====
   A finger can't use the desktop horizontal chevron-scroll of the page tabs, so on a touch device
   at narrow widths the tabs drop onto their own full-width row below the brand + guide/logo, where
   all four fit comfortably (a touch device has the vertical room — the page already scrolls as one
   region here). Desktop (mouse) keeps the single-row chevron-scroll nav at EVERY width — this block
   is keyed on `hover: none` so it never fires there. CSS-only. Placed at the very end of the file so
   it wins over the base single-row `.topbar`/`.nav` rules (an @media query adds no specificity, so
   it only wins by coming later in source order). */
@media (max-width: 800px) and (hover: none) {
  .topbar {
    height: auto;
    /* A little vertical breathing room: with height:auto the wrapped rows would otherwise sit flush
       against the top edge (the desktop 56px row centres its content, so it doesn't need this). */
    padding-top: 8px;
    padding-bottom: 8px;
    flex-wrap: wrap;
  }
  .brand {
    order: 1;
  }
  .topbar-right {
    order: 2;
  }
  .nav {
    order: 3;
    flex-basis: 100%;
    margin-top: 4px;
  }
  /* All four tabs on ONE line on the own row (no wrap — wrapping to 2–3 lines reads as messy),
     centred. Compact height so the row isn't tall. On phones the font shrinks to keep them on one
     line (see the ≤560 block); tablets/landscape (561–800px) have room for the full 12px. */
  .nav-scroll {
    flex-wrap: nowrap;
    justify-content: center;
  }
  .nav-tab {
    height: auto;
    padding: 6px 10px;
  }
}

/* Phones (touch, ≤560px): shrink the tab font just enough that all four fit comfortably on the ONE
   own-row line — at the full 12px they'd need ~462px, more than a ~360px phone has. This is the ONLY
   place the tab font changes, and ONLY on a touch phone: desktop keeps 12px + chevron-scroll, and
   tablets/landscape (561–800px touch) keep 12px too (they have the room). */
@media (max-width: 560px) and (hover: none) {
  .nav-tab {
    font-size: 9px;
    padding: 4px 6px;
  }
}