/* ================================================================
   site-theme.css — COHO Analytics
   Professional financial intelligence theme.
   Single source of truth. All pages must link this CSS LAST.
   ================================================================ */

/* ── Self-hosted fonts (run scripts/download-fonts.sh to populate assets/fonts/) ──
   Removing the Google Fonts CDN import eliminates the external dependency,
   prevents GDPR-relevant third-party requests, and removes a render-blocking
   resource.  The @font-face blocks below load from assets/fonts/ with
   font-display:swap so pages remain readable even when the files are absent.
   ──────────────────────────────────────────────────────────────────────────── */

/* Plus Jakarta Sans — Regular 400 */
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: local('Plus Jakarta Sans Regular'),
       url('../assets/fonts/plus-jakarta-sans/PlusJakartaSans-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* Plus Jakarta Sans — Regular Italic 400 */
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: local('Plus Jakarta Sans Italic'),
       url('../assets/fonts/plus-jakarta-sans/PlusJakartaSans-Italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
/* Plus Jakarta Sans — Medium 500 */
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: local('Plus Jakarta Sans Medium'),
       url('../assets/fonts/plus-jakarta-sans/PlusJakartaSans-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
/* Plus Jakarta Sans — SemiBold 600 */
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: local('Plus Jakarta Sans SemiBold'),
       url('../assets/fonts/plus-jakarta-sans/PlusJakartaSans-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
/* Plus Jakarta Sans — Bold 700 */
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: local('Plus Jakarta Sans Bold'),
       url('../assets/fonts/plus-jakarta-sans/PlusJakartaSans-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
/* Plus Jakarta Sans — ExtraBold 800 */
@font-face {
  font-family: 'Plus Jakarta Sans';
  src: local('Plus Jakarta Sans ExtraBold'),
       url('../assets/fonts/plus-jakarta-sans/PlusJakartaSans-ExtraBold.woff2') format('woff2');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}
/* DM Mono — Regular 400 */
@font-face {
  font-family: 'DM Mono';
  src: local('DM Mono Regular'),
       url('../assets/fonts/dm-mono/DMMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* DM Mono — Medium 500 */
@font-face {
  font-family: 'DM Mono';
  src: local('DM Mono Medium'),
       url('../assets/fonts/dm-mono/DMMono-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ================================================================
   1. DESIGN TOKENS — light mode default
   ================================================================ */
:root {
  /* Semantic color tokens — light mode */
  --bg:           #eef2f7;
  --bg2:          #e4ecf4;
  --bg3:          #dae4f0;
  --card:         #ffffff;
  --card2:        #f7fafd;
  --surface:      #ffffff;
  /* F252 — --surface-2 was being invented 8 times across F236/F237/F238/F250
     without ever being defined. Each invented callsite fell back to a hardcoded
     light grey hex (#f7f7f9) that was invisible against --muted text in dark mode.
     Now defined in both modes so panels using it render correctly without changing
     any HTML/JS. Lint rule scripts/audit/no-phantom-css-vars.mjs prevents
     re-introduction. */
  --surface-2:    #f3f4f6;
  --text:         #0d1f35;
  --text-strong:  #060f1d;
  --muted:        #374151;
  --faint:        #4b5563;
  --border:       rgba(13,31,53,.11);
  --border-strong:rgba(13,31,53,.20);
  --link:         #005a9c;
  --link-hover:   #004880;

  /* Accent colors */
  --accent:       #096e65;
  --accent-dim:   rgba(9,110,101,.10);
  --accent-weak:  rgba(9,110,101,.06);   /* faintest accent tint (search.html result rows) */
  /* F122 — Paired "on-accent" token. The dark-mode `--accent` is bright
     cyan (#0fd4cf), and pure white text on it scores only 1.7:1 — a hard
     WCAG fail. Every button that does `background:var(--accent); color:var(--on-accent,#fff)`
     was therefore unreadable after the dark-mode toggle. The fix is a
     paired token: in light mode the on-accent color is white; in dark mode
     it switches to the deep navy used for body text. Both pairings score
     >5:1 against their respective accent backgrounds. Use `var(--on-accent)`
     instead of hardcoded `#fff` on any accent-backed surface. */
  --on-accent:    #ffffff;
  --accent2:      #c86f0d;
  --accent2-dim:  rgba(200,111,13,.10);
  --good:         #047857;
  --good-dim:     rgba(4,120,87,.10);
  --warn:         #a84608;
  --warn-dim:     rgba(168,70,8,.10);
  --bad:          #991b1b;
  /* F133 — Paired "on-{good,warn,bad,info}" tokens. Each semantic color
     pairs a dark-bg/light-bg flip across themes; the on-token gives the
     readable text color for chips/badges/buttons whose background uses
     that semantic color. Light mode bgs are dark (so on-* = white), dark
     mode bgs are bright (so on-* = deep navy). Use these instead of
     hardcoded `#fff` or `#0a0f1d` for any color: declared next to
     background:var(--good)/--warn/--bad/--info. */
  --on-good:      #ffffff;
  --on-warn:      #ffffff;
  --on-bad:       #ffffff;
  --on-info:      #ffffff;
  /* F120 — `--bad-dim`, `--info`, `--info-dim` were declared twice in this
     :root block (originally here and again in the F252 contrast-guard
     section below at ~L207). The F252 values were winning at runtime via
     later-declaration precedence; removing the originals here keeps the
     resolved values identical and satisfies stylelint's
     declaration-block-no-duplicate-custom-properties rule. */

  /* Shadows */
  --shadow-sm:    0 1px 3px rgba(13,31,53,.07),0 1px 2px rgba(13,31,53,.04);
  --shadow:       0 4px 16px rgba(13,31,53,.08),0 1px 4px rgba(13,31,53,.05);
  --shadow-lg:    0 12px 40px rgba(13,31,53,.12),0 4px 12px rgba(13,31,53,.07);
  --shadow-card:  0 2px 8px rgba(13,31,53,.06),0 0 0 1px rgba(13,31,53,.07);

  /* Layout */
  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 16px;
  --radius-xl: 22px;

  /* Spacing */
  --sp1: 0.35rem; --sp2: 0.6rem; --sp3: 0.9rem;
  --sp4: 1.25rem; --sp5: 2rem;   --sp6: 3rem;

  /* Typography */
  --font-sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
  --font-mono: 'DM Mono', 'Fira Code', monospace;
  /* Typography scale — Compare Jurisdictions page H1 sizing is the
     site-wide standard for major page titles (clamp 1.4rem → 1.9rem).
     Previously --h1 went up to 2.6rem which made article + landing
     headings tower over other pages. Calmer scale = less "AI-busy". */
  --h1:  clamp(1.4rem, 3vw, 1.9rem);
  --h2:  clamp(1.05rem, 1.6vw, 1.25rem);
  --h3:  1rem;
  /* F94 (2026-05-31) — H4 was previously unset and inherited from <body>
     (0.938rem). Pages drifted between 0.85, 0.92, 0.95, 1.0, 1.05, 1.25
     for H4 across the site. Lock at 0.92rem (median of audited values)
     so card sub-titles / detail-row labels stay consistent. */
  --h4:  0.92rem;
  --body: 0.938rem;
  --small: 0.875rem;
  --tiny: 0.75rem;
  --caption: 0.688rem;

  --focus-ring: 0 0 0 3px rgba(9,110,101,.30);

  /* Map boundary styling — light mode */
  --map-boundary-stroke: rgba(15, 23, 42, 0.55);
  --map-boundary-stroke-light: rgba(13,31,53,.45);
  --map-boundary-stroke-dark: rgba(130,180,240,.55);
  --map-boundary-weight: 1.5;

  /* Legacy aliases */
  --color-background:     var(--bg);
  --color-background-alt: var(--bg2);
  --color-surface:        var(--card);
  --color-text:           var(--text);
  --color-text-light:     var(--muted);
  --color-text-muted:     var(--faint);
  --color-border:         var(--border);
  --color-primary:        var(--accent);
  --color-accent:         var(--accent2);
  --bg-primary:           var(--bg);
  --bg-secondary:         var(--bg2);
  --text-primary:         var(--text);
  --text-secondary:       var(--muted);
  --accent-gold:          var(--accent2);
  --border-color:         var(--border);
  /* F252 — Aliases for tokens the codebase references but that weren't
     defined. Each phantom was a silent breakage falling back to a
     hardcoded hex (often a light-mode value invisible in dark mode
     against --muted text). Defined here once so every callsite heals
     without code edits. CI lint at scripts/audit/no-phantom-css-vars.mjs
     catches any new undefined var(--xxx) reference at build time. */
  --border-soft:          var(--border);
  --text2:                var(--muted);
  --text-muted:           var(--muted);
  --brand:                var(--accent);
  --warning:              var(--warn);
  --color-info:           var(--accent);
  --card-alt:             var(--card2);
  --accent-light:         var(--accent-dim);
  --bg-alt:               var(--bg2);
  --card-bg:              var(--card);
  --bg-card:              var(--card);
  --warn-text:            var(--warn);
  --warning-bg:           var(--warn-dim);
  --warning-accent:       var(--warn);
  --info-dim:             rgba(96,165,250,.11);
  --bad-dim:              rgba(185,28,28,.12);
  --info:                 #1d4ed8;
  /* F252 — contrast-guard.js + a handful of OF/HNA panels reference these */
  --text-l:               #0f172a;              /* light-mode text on dark surfaces */
  --text-d:               #e5e7eb;              /* dark-mode text on light surfaces */
  --card-l:               #ffffff;              /* explicit light surface */
  --card-d:               #0f172a;              /* explicit dark surface */
  --bg-rgb:               255,255,255;          /* rgba() callers fall back to this */
  --card-rgb:             255,255,255;
  --warn-rgb:             251,191,36;
  /* css/pages/analysis.css legacy aliases */
  --panel:                rgba(255,255,255,0.04);
  --input:                var(--bg2);
  --button:               var(--bg2);
  /* Chart color tokens — WCAG AA ≥ 4.5:1 on white */
  --chart-1: #1e5799;
  --chart-2: #0369a1;
  --chart-3: #096e65;
  --chart-4: #7c3d00;
  --chart-5: #166534;
  --chart-6: #92400e;
  --chart-7: #991b1b;
}

/* ================================================================
   2. DARK MODE
   ================================================================ */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:           #08121e;
    --bg2:          #0c1928;
    --bg3:          #0f1e30;
    --card:         #0d1e30;
    --card2:        #102234;
    --surface:      #112438;
    --surface-2:    #152a40;
    --text:         rgba(215,232,248,.93);
    --text-strong:  #e4f0fc;
    --muted:        rgba(210,225,245,.95);
    --faint:        rgba(190,210,235,.90);
    --border:       rgba(90,150,210,.11);
    --border-strong:rgba(90,150,210,.22);
    --link:         #5ecbcc;
    --link-hover:   #7dd8d9;

    --accent:       #0fd4cf;
    --accent-dim:   rgba(15,212,207,.12);
    --accent-weak:  rgba(15,212,207,.08);   /* faintest accent tint (search.html result rows) */
    /* F122 — dark-mode on-accent: deep navy text on bright cyan accent
       gives 11:1 contrast (passes WCAG AAA). See light-mode definition
       at the top of this file for the rationale. */
    --on-accent:    #0a0f1d;
    --accent2:      #fbbf24;
    --accent2-dim:  rgba(251,191,36,.12);
    --good:         #34d399;
    --good-dim:     rgba(52,211,153,.12);
    --warn:         #fbbf24;
    --warn-dim:     rgba(251,191,36,.12);
    --bad:          #f87171;
    --bad-dim:      rgba(248,113,113,.12);
    --info:         #60a5fa;
    --info-dim:     rgba(96,165,250,.11);
    /* F133 — dark-mode on-{good,warn,bad,info}: deep navy text on the
       brighter dark-mode semantic colors. White on #34d399 / #fbbf24 /
       #f87171 / #60a5fa scores between 1.4:1 and 2.5:1 (all WCAG fail);
       dark text scores 5–11:1 (passes AAA). */
    --on-good:      #0a0f1d;
    --on-warn:      #0a0f1d;
    --on-bad:       #0a0f1d;
    --on-info:      #0a0f1d;
    --bg-rgb:       8,18,30;
    --card-rgb:     13,30,48;

    --shadow-sm:    0 1px 3px rgba(0,0,0,.30);
    --shadow:       0 4px 20px rgba(0,0,0,.40),0 1px 4px rgba(0,0,0,.25);
    --shadow-lg:    0 16px 48px rgba(0,0,0,.55),0 4px 16px rgba(0,0,0,.35);
    --shadow-card:  0 2px 8px rgba(0,0,0,.35),0 0 0 1px rgba(90,150,210,.10);

    /* Legacy dark */
    --color-background:     var(--bg);
    --color-background-alt: var(--bg2);
    --color-surface:        var(--card);
    --color-text:           var(--text);
    --color-text-light:     var(--muted);
    --color-text-muted:     var(--faint);
    --color-border:         var(--border);
    --bg-primary:           var(--bg);
    --bg-secondary:         var(--bg2);
    --text-primary:         var(--text);
    --text-secondary:       var(--muted);
    --accent-gold:          var(--accent2);
    --border-color:         var(--border);

    /* Map boundary styling — dark mode */
    --map-boundary-stroke: rgba(130,180,240,.55);
    --map-boundary-stroke-light: rgba(13,31,53,.45);
    --map-boundary-stroke-dark: rgba(130,180,240,.55);
    --map-boundary-weight: 1.5;
  /* Chart color tokens — dark mode (lighter on dark bg) */
  --chart-1: #5b9bd5;
  --chart-2: #38bdf8;
  --chart-3: #0fd4cf;
  --chart-4: #fbbf24;
  --chart-5: #4ade80;
  --chart-6: #fb923c;
  --chart-7: #f87171;
  }
  /* Selection: dark text on bright-cyan accent keeps contrast ≥ 6.2:1 */
  ::selection      { background: var(--accent); color: #0d1f35; }
  ::-moz-selection { background: var(--accent); color: #0d1f35; }
}

/* ================================================================
   3. RESET & BASE
   ================================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { color-scheme: light dark; scroll-behavior: smooth; font-size: 16px; }

html, body {
  background: var(--bg) !important;
  color: var(--text) !important;
  font-family: var(--font-sans);
  font-size: var(--body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

/* ================================================================
   4. TYPOGRAPHY
   ================================================================ */
/* Canonical type scale — enforced with !important so per-page inline
   styles (legacy drift on ~16 pages) get overridden into one consistent
   visual system. Page-specific overrides should adjust the CSS variables
   (--h1, --h2, --h3, --h4) or scope a more-specific selector; do NOT
   re-introduce hardcoded sizes on individual heading tags.
   F94 (2026-05-31): consolidates the 5+ distinct H1 sizes, 4 H2 sizes,
   3 H3 sizes, and 4 H4 sizes the type-scale audit flagged. */
/* F124 — Heading scale standardized across the site:
   - All h1-h4 use --text-strong color (was --text on h2/h3/h4) so headings
     read as a coherent visual hierarchy from a single contrast level.
     Previously the inline-style sweep found 128 h1-h4 tags carrying
     `color:var(--text-strong)` as a one-off override; promoting it to the
     base style eliminates the per-page drift and is what every existing
     override wanted anyway.
   - text-wrap: balance prevents the orphan-word-on-last-line problem on
     fluid h1-h2 widths. Falls back to normal wrapping in browsers without
     support (95%+ coverage as of 2026; pure progressive enhancement). */
h1 {
  font-size: var(--h1) !important;
  font-weight: 800;
  color: var(--text-strong, var(--text)) !important;
  line-height: 1.12;
  letter-spacing: -0.025em;
  text-wrap: balance;
  margin: 0 0 var(--sp3);
}
h2 {
  font-size: var(--h2) !important;
  font-weight: 700;
  color: var(--text-strong, var(--text)) !important;
  line-height: 1.25;
  letter-spacing: -0.015em;
  text-wrap: balance;
  margin: 0 0 var(--sp2);
}
h3 {
  font-size: var(--h3) !important;
  font-weight: 700;
  color: var(--text-strong, var(--text)) !important;
  line-height: 1.3;
  text-wrap: balance;
  margin: 0 0 var(--sp2);
}
h4 {
  font-size: var(--h4) !important;
  font-weight: 700;
  color: var(--text-strong, var(--text)) !important;
  line-height: 1.35;
  text-wrap: balance;
  margin: 0 0 var(--sp1);
}
h5, h6 {
  font-size: var(--small);
  color: var(--text-strong, var(--text)) !important;
  font-weight: 600;
  text-wrap: balance;
  margin: 0 0 var(--sp1);
}
/* F124 — intro sub-paragraphs (.page-sub, .intro-card p, .article-body
   lead) get text-wrap: pretty for cleaner long-paragraph wrap. Different
   from balance — pretty optimizes the last 1-2 lines for readability,
   balance evens every line. Headlines → balance, body lead → pretty. */
.page-sub, .intro-card p:first-of-type, .article-body > p:first-of-type {
  text-wrap: pretty;
}
/* Escape hatch for headings that intentionally want to override (e.g.
   chart-card titles that are visually subordinate). Apply class instead
   of inline style. */
.h-as-h2 { font-size: var(--h2) !important; }
.h-as-h3 { font-size: var(--h3) !important; }
.h-as-h4 { font-size: var(--h4) !important; }
/* F133 — on-accent escape hatch for headings inside a var(--accent) hero
   panel. The global `h1–h6 { color: var(--text-strong) !important }` rule
   prevents inline color overrides; this class is the canonical replacement
   for the inline `color: var(--on-accent) !important` pattern so headings
   sitting on a colored hero (orange, teal, etc.) render with their paired
   on-token in both modes. */
.h-on-accent { color: var(--on-accent, #fff) !important; }

p  { color: var(--muted) !important; line-height: 1.65; }
li { color: var(--muted) !important; }
td, th { color: var(--muted) !important; }
strong, b { color: var(--text) !important; font-weight: 700; }
small { font-size: var(--small); color: var(--faint) !important; }
/* F133 — `code/kbd` had `background: var(--bg2)` but inherited body color
   (--text). Runtime scanner caught 4+4 = 8 instances where the inherited
   color resolved as light-mode --muted (#64748b) against the bg2 surface
   (~3.5:1 borderline). Add explicit `color: var(--text-strong)` so code
   inline-snippets stay readable in both themes. */
code, kbd { font-family: var(--font-mono); font-size: 0.85em; background: var(--bg2); color: var(--text-strong); padding: 1px 5px; border-radius: 4px; border: 1px solid var(--border); }
hr { border: none; border-top: 1px solid var(--border); margin: var(--sp4) 0; }

.card h1, .card h2, .card h3, .card h4,
.panel h1, .panel h2, .panel h3,
.metric h3, .stat-card h3, summary,
.kpi-card h3, .kpi-card h4 { color: var(--text) !important; }

/* F127 — Anchor color cascade, now with proper specificity tiers.
   PRIOR STATE: `a:not([style*="background"]) { color: var(--link) !important }`
   from F122. Problem: the `!important` clobbered every CSS-class-based
   button-styled anchor too (e.g. `.lof-action-ctas a` with
   background:var(--accent) + color:var(--card)). The :not([style*="background"])
   exception only covered INLINE backgrounds; class-based backgrounds still
   matched, so cyan-on-cyan returned via a different route — the buttons in
   the Opportunity Finder detail panel were unreadable in dark mode.

   FIX: use `:where()` (zero specificity) for the global anchor styling.
   ANY class-based or inline rule wins automatically without needing
   `!important`. This is the right structural primitive: zero-specificity
   defaults that real selectors override transparently. The inline-style
   exception is still here so anchors that opt out via `<a style="background:…">`
   also drop the underline + transition. */
:where(a) { color: var(--link); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; transition: color 0.15s; }
:where(a:hover) { color: var(--link-hover); }
a[style*="background"] { text-decoration: none; }
a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }
*:focus-visible { outline-color: var(--accent); outline-offset: 2px; }

/* ================================================================
   5. LAYOUT
   ================================================================ */
main { background: transparent !important; min-height: calc(100vh - 200px); }

header.site-header, footer.site-footer, section, .section,
.page, .content, .container, .wrap { background: transparent !important; }

.container, .wrap { max-width: 1240px; margin: 0 auto; padding: 0 var(--sp4); }
.container-wide { max-width: 100%; padding: 0 var(--sp4); }
.page-container { max-width: 1240px; margin: 0 auto; padding: var(--sp4) var(--sp4) var(--sp6); }

/* navigation.js main override (site content shell) */
main { 
  max-width: 1240px; 
  margin: var(--sp3) auto; 
  padding: var(--sp4); 
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow);
}
@media (max-width: 760px){
  main{ margin: var(--sp2) auto; padding: var(--sp3); border-radius: var(--radius-lg); }
}

/* ================================================================
   6. NAVIGATION (injected by navigation.js)
   ================================================================ */
header.site-header {
  position: sticky !important; top: 0; z-index: 9000 !important;
  background: color-mix(in srgb, var(--card) 85%, transparent) !important;
  border-bottom: 1px solid var(--border);
  backdrop-filter: saturate(1.4) blur(18px);
  -webkit-backdrop-filter: saturate(1.4) blur(18px);
}
.nav-wrap {
  max-width: 1240px; margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 20px; gap: 16px;
}
/* F29: keep the header a constant height on every page so headings below it
   don't shift during navigation. Root cause: responsive.css applies a 44px
   WCAG touch-target min-height to ALL <a>, which inflated the brand logo link
   to 44px → 62px brand block → 87px header on pages that load it, vs 65px
   elsewhere. The brand block IS the tap target; its inner link doesn't need
   the 44px floor. Override it + keep the tagline on one line. */
.brand { display: flex; flex-direction: column; gap: 1px; flex-shrink: 0; }
.brand a {
  font-weight: 800; font-size: 0.92rem; letter-spacing: -0.01em;
  color: var(--text) !important; text-decoration: none !important;
  white-space: nowrap;
  min-height: 0 !important; min-width: 0 !important;
  display: inline !important; line-height: 1.2;
}
.brand small { font-size: 0.7rem; color: var(--muted) !important; font-weight: 500; letter-spacing: 0.04em; white-space: nowrap; }
/* Reserve the header's resting height so the JS-injected nav doesn't shove
   content down on first paint (the static placeholder is otherwise ~0px).
   At desktop widths the inline nav is a single row, so pin a fixed height —
   this makes the header identical on every page regardless of which CSS a
   page loads or whether the jurisdiction pill is present. Dropdowns open via
   position:absolute, so a fixed height doesn't clip them. */
header.site-header .nav-wrap { min-height: 52px; }
@media (min-width: 769px) {
  header.site-header .nav-wrap { height: 60px; }
}
/* Mobile: keep brand + jurisdiction pill + hamburger on ONE row (don't let
   the pill wrap to a second row, which made the header 118px on pages with a
   selected jurisdiction vs ~52px without). Pill name truncates if needed. */
@media (max-width: 768px) {
  header.site-header .nav-wrap { flex-wrap: nowrap; min-height: 52px; }
  header.site-header .jurisdiction-pill__name { max-width: 92px; }
}
/* F29: the WCAG 44px touch-target min-heights (responsive.css) inflate the
   desktop header — but only on pages that load responsive.css (index.html
   doesn't), so the header was 69px on most pages and 60px on the landing,
   shifting every heading when navigating. Neutralize the inflation for the
   header's own controls; this is the DESKTOP nav context — mobile uses the
   slide-in drawer, which keeps its own 44px targets. Result: one constant
   header height across every page. */
header.site-header .nav-group-btn,
header.site-header nav.site-nav > a,
header.site-header .jurisdiction-pill,
header.site-header .mobile-menu-btn {
  min-height: 0;
}
/* nav.site-nav defaults to align-items:stretch, so the touch-target rule's
   inline-flex children stretched the .nav-group wrappers to 44px (taller than
   their 34px buttons) on responsive.css pages. Center them so the nav row
   tracks the button height → header is the same on every page. */
header.site-header nav.site-nav { align-items: center; }
header.site-header .nav-group { display: flex; align-items: center; }
/* The pill WRAP (not the pill) was stretching to 44px on responsive.css pages.
   Pin it to its content height so it never drives the header taller. */
header.site-header .jurisdiction-pill-wrap { min-height: 0; align-self: center; }
nav.site-nav { display: flex; gap: 3px; flex-wrap: wrap; justify-content: flex-end; }
nav.site-nav a {
  padding: 6px 11px; border-radius: var(--radius-sm); border: 1px solid transparent;
  color: var(--muted) !important; text-decoration: none !important;
  font-weight: 600; font-size: 0.82rem; letter-spacing: 0.01em;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
nav.site-nav a:hover { background: var(--accent-dim); color: var(--link) !important; border-color: color-mix(in srgb, var(--link) 25%, transparent); }
nav.site-nav a.is-active { background: var(--accent-dim); color: var(--link) !important; border-color: color-mix(in srgb, var(--link) 35%, transparent); }

/* Mobile navigation — hamburger menu */
@media (max-width: 768px) {
  nav.site-nav { display: none; }
  nav.site-nav.nav-expanded { display: flex !important; flex-direction: column; position: fixed; top: 58px; left: 0; right: 0; bottom: 0; background: var(--card) !important; z-index: 8999; padding: 1.25rem; gap: 4px; overflow-y: auto; box-shadow: 0 12px 40px rgba(0,0,0,.25); }
  nav.site-nav.nav-expanded a { padding: 14px 16px !important; border-radius: 10px !important; font-size: 1rem !important; border: 1px solid transparent; }
  nav.site-nav.nav-expanded a:hover, nav.site-nav.nav-expanded a.is-active { background: var(--bg2) !important; border-color: var(--border) !important; }
}

footer.site-footer { margin-top: var(--sp6); border-top: 1px solid var(--border); background: var(--bg2) !important; }
.footer-wrap {
  max-width: 1240px; margin: 0 auto; padding: 18px 20px;
  color: var(--muted) !important; display: flex; flex-wrap: wrap;
  gap: 12px; justify-content: space-between; align-items: center;
  font-size: var(--small);
}
.footer-wrap a { color: var(--muted) !important; text-decoration: none; }
.footer-wrap a:hover { color: var(--text) !important; }

/* ================================================================
   7. CARDS & SURFACES
   ================================================================ */
.card, .kpi-card, .panel, .tile, .box, .callout, .content-card,
.stat-card, .chart-card, .section-card, .data-card {
  background: var(--card) !important;
  border: 1px solid var(--border) !important;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
.card p, .panel p, .tile p, .callout p, .card li, .panel li { color: var(--muted) !important; }
.surface-tint { background: var(--card2) !important; border: 1px solid var(--border) !important; border-radius: var(--radius); }

/* ================================================================
   8. STAT / KPI CARDS
   ================================================================ */
.stat, .kpi { padding: var(--sp3) var(--sp4); }
.stat .num, .kpi-value, .stat-value { font-size: 1.6rem; font-weight: 800; color: var(--text) !important; letter-spacing: -0.02em; line-height: 1.1; }
.stat .lbl, .kpi-label, .stat-label { color: var(--muted) !important; font-size: var(--small); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; margin-top: 4px; }
/* F133 — .kpi-source/.stat .src anchors were var(--faint) which dropped
   to 3:1 in dark mode on var(--card) bg. The runtime scanner caught the
   FRED/HUD source links specifically. Override to var(--muted) for the
   needed contrast headroom in both modes. */
.stat .src, .kpi-source { color: var(--muted) !important; font-size: 0.72rem; margin-top: 5px; }
.stat .src a, .kpi-source a { color: var(--muted) !important; }
html.dark-mode .stat .src, html.dark-mode .kpi-source { color: rgba(210, 225, 245, 0.95) !important; }
html.dark-mode .stat .src a, html.dark-mode .kpi-source a { color: rgba(210, 225, 245, 0.95) !important; }
.kpi-unit, .stat-unit { color: var(--muted) !important; font-size: 0.72rem; margin-top: 2px; }
.kpi-delta, .stat-delta { font-size: 0.78rem; font-weight: 700; margin-top: 4px; color: var(--muted); }
.kpi-delta.positive, .stat-delta.positive { color: var(--good) !important; }
.kpi-delta.negative, .stat-delta.negative { color: var(--bad) !important; }
.kpi-delta.flat, .stat-delta.flat { color: var(--faint) !important; }
/* Standardized source attribution for KPI / stat cards */
.kpi-source, .stat-source { font-size: 0.65rem; color: var(--faint, #999); margin-top: 3px; font-style: italic; }
.kpi-source a, .stat-source a { color: var(--faint, #999); text-decoration: none; }
.kpi-source a:hover, .stat-source a:hover { text-decoration: underline; }

/* `.kpi-verify` extends `.kpi-source` for values the user should
   sanity-check against an external authoritative source before using
   in a decision. Slightly darker than plain source-attribution so
   the warn glyph is readable, but still quiet enough to avoid
   overwhelming the value itself. Links stay underlined for clear
   affordance on an external-action element. */
.kpi-source.kpi-verify, .stat-source.kpi-verify { color: var(--muted, #666); font-style: normal; line-height: 1.35; }
.kpi-source.kpi-verify a, .stat-source.kpi-verify a { color: var(--link, #054a42) !important; text-decoration: underline; text-underline-offset: 2px; }
.kpi-source.kpi-verify a:hover, .stat-source.kpi-verify a:hover { text-decoration-thickness: 2px; }
.chart-source { font-size: 0.72rem; color: var(--faint); margin-top: var(--sp2); padding: 0 var(--sp4) var(--sp3); }
/* Source-attribution links need a non-color cue to satisfy WCAG 1.4.1
   (link-in-text-block). The subdued faint color is reinforced with
   a dotted underline that darkens on hover — stays visually quiet
   in the normal state while still providing the required affordance. */
.chart-source a { color: var(--faint); text-decoration: underline dotted; text-underline-offset: 2px; }
.chart-source a:hover { text-decoration: underline; }
/* Vintage inline span — separates the source-vintage from the link text */
.chart-source__vintage { font-style: italic; }
/* Source-type pill — raw / transformed / modeled */
.chart-source__type {
  display: inline-block;
  padding: 0 0.38em;
  border-radius: 2px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  vertical-align: baseline;
}
.chart-source__type--raw         { background: color-mix(in srgb, var(--info,#1d4ed8) 12%, transparent); color: var(--info,#1d4ed8); }
.chart-source__type--transformed { background: color-mix(in srgb, var(--accent,#096e65) 12%, transparent); color: var(--accent,#096e65); }
.chart-source__type--modeled     { background: color-mix(in srgb, var(--warn,#a8460a) 12%, transparent); color: var(--warn,#a8460a); }

/* Data vintage badge — renders age + optional stale-data warning.
   Auto-attached by js/components/data-vintage-badge.js to any element
   marked with data-vintage-source="path/to/file.json". Signal agrees
   with the CI freshness check (same timestamp-probe order). */
.data-vintage-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  box-sizing: border-box;
  min-width: 0;
  max-width: 100%;
  padding: 0.32rem 0.65rem;
  margin: 0.35rem 0;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--muted, #64748b);
  background: var(--bg2, #f8fafc);
  border: 1px solid var(--border, #cbd5e1);
  border-radius: 999px;
  white-space: normal;
  overflow-wrap: anywhere;
}
/* F76: dropped opacity:.75 on the icon — the parent already uses muted text,
   and stacking opacity on top dropped it below AA in dark mode. */
.data-vintage-badge .data-vintage-icon { font-size: 0.65rem; }
.data-vintage-badge .data-vintage-text,
.data-vintage-badge .data-vintage-source { min-width: 0; }
.data-vintage-badge .data-vintage-age { color: var(--faint, #94a3b8); }
@media (max-width: 480px) {
  .data-vintage-badge {
    max-width: calc(100vw - 24px);
  }
}
/* F76: stale badge — was hardcoded amber pair (#92400e text on rgba(217,
   119,6,...) bg) with no dark-mode override, so dark-mode users saw dark
   amber text on a dark-amber-tinted background ≈ 2:1. Use the live --warn
   token so the color flips automatically with the mode. */
.data-vintage-badge--stale {
  color: var(--warn);
  background: var(--warn-dim);
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  white-space: normal;
}
.data-vintage-badge--stale .data-vintage-icon { color: var(--warn); }
.data-vintage-badge--stale .data-vintage-age   { color: var(--warn); }

/* Place-approximation hint on HNA stats — a compact pill that sits next
   to a stat header to warn that the number is downscaled from a county
   source. Twin of the comparative-analysis panel disclaimer shipped in
   #647; applied to housing-needs-assessment.html's CHAS / AMI tier
   stats when a place/CDP is selected. */
.data-approx-hint {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.1rem 0.45rem;
  font-size: 0.72rem;
  font-weight: 500;
  /* F76: was hardcoded amber, invisible in dark mode. Use the live --warn
     token so the color flips automatically. */
  color: var(--warn);
  background: var(--warn-dim);
  border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent);
  border-radius: 999px;
  vertical-align: middle;
  cursor: help;
}

/* Explainer tooltip — zero-JS inline methodology popover.
   Used for stat cards where a one-line label can't fully describe the
   methodology, data scope, or limitations. Built on native <details>
   so it's keyboard-accessible and needs no framework. */
.info-tooltip { display: inline-block; margin-left: 0.35rem; vertical-align: middle; }
.info-tooltip > summary {
  list-style: none;            /* hide default disclosure triangle */
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.05rem; height: 1.05rem;
  border-radius: 50%;
  background: var(--bg2, #f1f5f9);
  color: var(--muted, #64748b);
  font-size: 0.75rem;
  font-weight: 700;
  border: 1px solid var(--border, #cbd5e1);
  transition: background .15s, color .15s;
}
.info-tooltip > summary::-webkit-details-marker { display: none; }
.info-tooltip > summary:hover,
.info-tooltip > summary:focus-visible { background: var(--accent, #1e40af); color: var(--on-accent, #fff); outline: none; }
.info-tooltip[open] > summary { background: var(--accent, #1e40af); color: var(--on-accent, #fff); }
.info-tooltip-body {
  display: block;
  margin-top: 0.5rem;
  padding: 0.7rem 0.85rem;
  border-radius: var(--radius, 6px);
  background: var(--bg2, #f8fafc);
  border: 1px solid var(--border, #cbd5e1);
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--text, #0f172a);
  max-width: 42rem;
}
.info-tooltip-body p { margin: 0 0 0.5rem; }
.info-tooltip-body p:last-child { margin-bottom: 0; }
.info-tooltip-body dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 0.75rem; margin: 0.25rem 0; font-size: 0.78rem; }
.info-tooltip-body dt { color: var(--muted, #64748b); font-weight: 600; }
.info-tooltip-body dd { margin: 0; }
/* Leaflet map legend swatches (used by co-lihtc-map.js Leaflet legend control) */
.map-legend .swatch-dda { width:14px;height:10px;border-radius:3px;display:inline-block;flex-shrink:0;background:rgba(255,200,90,.22);border:1px solid rgba(255,200,90,.55); }
.map-legend .swatch-qct { width:14px;height:10px;border-radius:3px;display:inline-block;flex-shrink:0;background:rgba(120,255,170,.24);border:1px solid rgba(120,255,170,.55); }
.map-legend .marker-lihtc { width:10px;height:10px;border-radius:50%;background:#5fa8ff;display:inline-block;flex-shrink:0; }
.map-legend .marker-county { width:10px;height:10px;border-radius:2px;background:rgba(200,200,255,.35);border:1px solid rgba(130,130,220,.5);display:inline-block;flex-shrink:0; }
/* F176 — collapsible map legend (CO LIHTC + similar). Tap header to toggle;
   mobile default-collapsed via JS so the legend doesn't bury small maps. */
.map-legend .map-legend-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; cursor: pointer; user-select: none; }
.map-legend .map-legend-head:focus-visible { outline: 2px solid var(--accent, #096e65); outline-offset: 2px; border-radius: 3px; }
.map-legend .map-legend-caret { display: inline-block; transition: transform .15s ease; font-size: 11px; opacity: .7; margin-left: auto; }
.map-legend.is-collapsed .map-legend-caret { transform: rotate(-90deg); }
.map-legend .map-legend-body { transition: max-height .15s ease, opacity .15s ease; overflow: hidden; }
.map-legend.is-collapsed .map-legend-body { max-height: 0 !important; opacity: 0; margin-top: 0; }
.map-legend.is-collapsed .map-legend-head { margin-bottom: 0; }
/* Actual on-map LIHTC marker dot — built by the divIcon in hna-renderers
   renderLihtcLayer(). Without explicit styling the inner <span> renders
   as a zero-width inline element and markers are effectively invisible
   even though the layer is on the map. Match the legend swatch so the
   legend explains what's on the map. */
.lihtc-marker { display:block; }
.lihtc-marker .lihtc-dot {
  display:block;
  width:12px;height:12px;
  border-radius:50%;
  background:#5fa8ff;
  border:2px solid #fff;
  box-shadow:0 0 0 1px rgba(0,0,0,.35), 0 1px 3px rgba(0,0,0,.4);
  box-sizing:border-box;
}

/* Methodology & Data Sources panel (HNA). The renderer injects
   <ul class="method-list"> inside a card; default UA bullet indent
   pushes the markers outside the card padding. Constrain the indent
   so bullets sit inside the box. */
.method-section { margin: 0 0 1rem; }
.method-section h4 { font-size: .92rem; margin: 0 0 .35rem; color: var(--text); }
.method-section p { margin: 0 0 .35rem; font-size: .85rem; color: var(--muted); line-height: 1.5; }
.method-section .method-list {
  margin: 0 0 .35rem;
  padding-left: 1.25rem;
  font-size: .85rem;
  color: var(--muted);
  line-height: 1.5;
}
.method-section .method-list li { margin: .15rem 0; }
.method-section .method-list strong { color: var(--text); }

/* Plain-language methodology popover — attached next to chart / stat
   headings via js/components/methodology-popover.js. Closed state is
   a small inline ℹ pill; open state expands a 5-row dl with What /
   Source / Method / Drift / Caveats. Matches the existing
   info-tooltip aesthetic so it doesn't read as a foreign component. */
.methodology-popover {
  display: inline-block;
  margin-left: .4rem;
  vertical-align: middle;
  font-weight: 400;
  font-size: .72rem;
}
.methodology-popover__summary {
  list-style: none;
  cursor: pointer;
  padding: .1rem .5rem;
  border-radius: 999px;
  background: var(--bg2);
  color: var(--muted);
  border: 1px solid var(--border);
  user-select: none;
  white-space: nowrap;
}
.methodology-popover__summary::-webkit-details-marker { display: none; }
.methodology-popover__summary:hover {
  background: var(--card);
  color: var(--text);
  border-color: var(--accent);
}
.methodology-popover[open] .methodology-popover__summary {
  background: var(--accent);
  color: var(--on-accent, #fff);
  border-color: var(--accent);
}
.methodology-popover__body {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: .35rem .85rem;
  margin: .55rem 0 .25rem;
  padding: .65rem .85rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: .8rem;
  line-height: 1.45;
  font-weight: 400;
  max-width: 38rem;
}
.methodology-popover__body dt {
  color: var(--muted);
  font-weight: 700;
  font-size: .74rem;
  letter-spacing: .02em;
}
.methodology-popover__body dd {
  margin: 0;
  color: var(--text);
}

/* "County-level data" disclosure on HNA sections whose data only exists
   at county granularity (LEHD, DOLA SYA, BLS QCEW). The renderer
   injects a <div class="hna-county-scope-note" role="note"> after the
   section's h2 when the user has picked a place/cdp. Visual matches
   the chartChasGap proxy-note pattern: amber left-border, muted
   background, role=note for screen readers. */
.hna-county-scope-note {
  margin: 0 0 .75rem;
  padding: .55rem .85rem;
  border-left: 3px solid var(--warn, #d97706);
  border-radius: 0 4px 4px 0;
  background: var(--warn-dim, rgba(217,119,6,.10));
  font-size: .82rem;
  line-height: 1.45;
  color: var(--text);
}
.hna-county-scope-note strong { color: var(--text); }

/* Place-apportioned variant — same shape, green palette to match the
   chartChasGap "✓ TIGER 2024 place-level" badge. Used when place-LEHD
   replaces a raw county blob via population-weighted apportionment. */
.hna-county-scope-note--place {
  border-left-color: var(--good, #16a34a);
  background: rgba(34,197,94,.08);
}

/* LIHTC info panel on the HNA map — same pattern as method-list:
   the renderer injects <ul class="lihtc-list"><li class="lihtc-item">
   and without padding the default UA indent pushes bullets outside
   the card. */
.lihtc-list {
  margin: .35rem 0 0;
  padding-left: 1.25rem;
  font-size: .85rem;
  color: var(--muted);
  line-height: 1.5;
}
.lihtc-list .lihtc-item { margin: .15rem 0; }
.lihtc-list .lihtc-item strong { color: var(--text); }
.lihtc-source { margin: 0 0 .35rem; font-size: .82rem; color: var(--muted); }
.lihtc-empty  { margin: .35rem 0; font-size: .85rem; color: var(--muted); font-style: italic; }
/* F133 — was bg:#fff with inherited color. In dark mode the body color
   inherited as light, leaving white-on-white. Use theme tokens. */
.map-reset-btn { padding:8px 12px;font-size:13px;cursor:pointer;background:var(--card);color:var(--text-strong);border:1px solid var(--border);border-radius:4px;box-shadow:0 1px 3px rgba(0,0,0,0.2);font-family:inherit; }
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px,1fr)); gap: 12px; margin: var(--sp3) 0; }
.delta { font-weight: 700; font-size: 0.82rem; }
.delta.up { color: var(--good) !important; }
.delta.down { color: var(--bad) !important; }
.yoy { font-size: 0.76rem; font-weight: 700; margin-top: 2px; color: var(--muted); min-height: 1em; }
.yoy.pos { color: var(--good) !important; }
.yoy.neg { color: var(--bad) !important; }
.delta.flat { color: var(--faint) !important; }

/* ================================================================
   9. METRIC CHART CARDS
   ================================================================ */
.metric-grid { display: grid; grid-template-columns: repeat(12,1fr); gap: 12px; }
.metric { grid-column: span 12; padding: var(--sp3); }
@media (min-width: 860px) { .metric { grid-column: span 4; } }
.metric h3 { font-size: 0.9rem; color: var(--text) !important; margin: 0 0 6px; font-weight: 700; }
.metric .value { font-size: 1.5rem; font-weight: 800; color: var(--text) !important; letter-spacing: -0.02em; }
.metric .meta { color: var(--muted) !important; font-size: var(--tiny); display: flex; gap: 10px; margin-top: 4px; }
/* F221b — Revert F220's flex-column on .chart-wrap. F220 tried to fix
   the source-badge appearing as a side sibling of the canvas by
   switching .chart-wrap to a vertical stack — but that put Chart.js
   into an infinite resize loop on every responsive: true +
   maintainAspectRatio: false chart (anything inside .chart-wrap with
   the legend, source badge, or any extra child grew the wrap, which
   Chart.js re-fit the canvas to, which grew the wrap, ad infinitum —
   FRED dashboard cards ballooned to >20,000px tall).
   The real fix lives elsewhere: keep extra content OUT of .chart-wrap
   entirely. source-badge.js now hoists the .chart-source badge to
   the wrap's parent (mirroring its existing .chart-box treatment),
   and the F221 event legend is inserted as a SIBLING of .chart-wrap
   on the FRED dashboard, not inside it. With nothing else inside,
   the original simple flex layout works again. */
.chart-wrap { margin-top: 10px; border-top: 1px solid var(--border); padding-top: 10px; display: flex; align-items: center; justify-content: center; }
canvas { width: 100% !important; }

/* Chart container centering inside cards */
.card .chart-box,
.chart-card .chart-box,
.panel .chart-box {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.card canvas,
.chart-card canvas,
.panel canvas {
  max-width: 100%;
}

/* ----------------------------------------------------------------
   Chart loading / empty / error states (Recommendation 3.1)
   ---------------------------------------------------------------- */
.chart-state-wrap {
  position: relative;
}

/* Loading overlay */
.chart-loading {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp2);
  background: var(--card);
  border-radius: var(--radius);
  z-index: 2;
  transition: opacity 0.25s;
}

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

/* Spinner */
.chart-spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: chart-spin 0.75s linear infinite;
}

@keyframes chart-spin {
  to { transform: rotate(360deg); }
}

.chart-loading-label {
  font-size: var(--small);
  color: var(--muted);
}

/* Empty state */
.chart-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp2);
  min-height: 140px;
  padding: var(--sp4);
  color: var(--muted);
  font-size: var(--small);
  text-align: center;
}

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

.chart-empty-icon {
  font-size: 1.8rem;
  opacity: 0.45;
  line-height: 1;
}

/* Error state */
.chart-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp2);
  min-height: 140px;
  padding: var(--sp4);
  color: var(--bad);
  font-size: var(--small);
  text-align: center;
  background: var(--bad-dim);
  border-radius: var(--radius);
  border: 1px solid var(--bad);
}

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

/* ================================================================
   10. TABLES
   ================================================================ */
table { background: transparent !important; color: var(--text) !important; width: 100%; border-collapse: collapse; }
table th { color: var(--text) !important; font-weight: 700; font-size: var(--small); text-transform: uppercase; letter-spacing: 0.05em; background: var(--bg2) !important; padding: 10px 14px; border-bottom: 2px solid var(--border-strong); }
table td { color: var(--muted) !important; padding: 9px 14px; border-bottom: 1px solid var(--border); font-size: var(--body); }
table tr:last-child td { border-bottom: none; }
table tr:hover td { background: var(--accent-dim); }
.table-container { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--border); }

/* ================================================================
   11. FORM CONTROLS
   ================================================================ */
select, input:not([type="checkbox"]):not([type="radio"]), textarea {
  background: var(--bg2) !important; color: var(--text) !important;
  border: 1px solid var(--border-strong) !important;
  border-radius: var(--radius-sm); padding: 8px 12px;
  font-family: var(--font-sans); font-size: var(--body); width: 100%;
  transition: border-color 0.15s, box-shadow 0.15s;
}
/* R1: checkboxes and radios get a fixed 1rem hit-target with no padding
   so they don't inherit the form-control 100%-width / 8px-padding rule
   that was making them render as wide bars on flex layouts. */
input[type="checkbox"], input[type="radio"] {
  width: 1rem;
  height: 1rem;
  padding: 0;
  margin: 0;
  flex-shrink: 0;
  vertical-align: middle;
  cursor: pointer;
  accent-color: var(--accent);
}
select:focus, input:focus, textarea:focus { outline: none; border-color: var(--accent) !important; box-shadow: var(--focus-ring); }
/* Default label styling — NO !important so more-specific rules
   (e.g. .proj-view-label:has(input:checked) { color: #fff }) can
   win. Prior !important caused a color-contrast violation where the
   checked projection-view label's muted color ran against the accent
   background at 1.68:1. */
label { color: var(--muted); font-size: var(--small); font-weight: 600; display: block; margin-bottom: 5px; }

/* ================================================================
   12. BUTTONS
   ================================================================ */
/* F131 — removed `color: var(--text) !important` from base `.btn`. The
   `!important` clobbered every inline `color: var(--on-accent)` set on
   accent-backed buttons (the "Start Here" home CTA, every CTA across the
   site that uses class="btn" plus inline background:var(--accent)).
   Result was dark text on cyan/teal accent = 1.48:1 contrast — caught
   only by the runtime scanner, never by the static gates. Use :where()
   for the same default-but-overridable behavior without specificity.
   The child-selector rule below ensures inner spans of a styled .btn
   inherit the button's color (defeats the body !important color rule
   for nested SPAN children that the scanner showed as broken). */
:where(.btn) { cursor: pointer; border-radius: var(--radius-sm); border: 1px solid var(--border-strong); background: var(--card); color: var(--text); padding: 8px 16px; font-family: var(--font-sans); font-size: var(--small); font-weight: 700; transition: all 0.15s; display: inline-flex; align-items: center; gap: 6px; }
:where(.btn) { text-decoration: none; }
.btn > * { color: inherit; }   /* F131 — inner spans pick up the button's color */
.btn:hover { background: var(--bg2); box-shadow: var(--shadow-sm); }
.btn.primary, .btn-primary { background: var(--accent) !important; border-color: transparent; color: var(--on-accent, #fff); }
.btn.primary:hover { opacity: 0.9; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ================================================================
   12b. SYSTEMIC DARK-MODE CONTRAST OVERRIDES (F76)
   "White text on accent background" works in light (#fff on dark
   teal #096e65 ≈ 7:1) but fails in dark mode (#fff on bright cyan
   #0fd4cf ≈ 1.7:1). Override the canonical class-based primary
   buttons + onboarding circle. .kicker hardcoded a fixed dark teal
   that disappears on dark accent-dim; use the live --accent instead.

   F85b: attribute-selector pattern (catching ANY inline
   "background:var(--accent)" element) was reverted on user feedback —
   it flipped a handful of inline buttons to dark text on bright cyan,
   which read as visually jarring even though contrast was AAA. We
   accept the lower contrast on those rare inline buttons and lean on
   the class-based overrides below for the canonical .btn.primary /
   .btn-primary cases.
   ================================================================ */
@media (prefers-color-scheme: dark) {
  .btn.primary, .btn-primary {
    color: #0d1f35 !important;
  }
  .kicker { color: var(--accent) !important; }
}
html.dark-mode .btn.primary,
html.dark-mode .btn-primary { color: #0d1f35 !important; }
html.dark-mode .kicker { color: var(--accent) !important; }
/* F155 — Explicit light-mode override for the same rules. The
   `@media (prefers-color-scheme: dark)` block above fires off the OS
   preference, so when an OS-dark user opens the page and dark-mode-toggle
   adds `html.light-mode`, the @media block was still painting the primary
   button text dark navy against the light-mode bright-teal accent
   (rgb(13,31,53) on rgb(9,110,101) = 2.71:1). Class-based selector beats
   the @media block via specificity, restoring white text in light mode. */
html.light-mode .btn.primary,
html.light-mode .btn-primary { color: var(--on-accent, #fff) !important; }
html.light-mode .kicker { color: var(--accent) !important; }

/* ================================================================
   13. PILLS, BADGES
   ================================================================ */
.pill, .badge, .tag { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg2); color: var(--muted) !important; font-weight: 600; font-size: var(--small); }
.pill strong, .badge strong { color: var(--text) !important; }
.pill.accent { background: var(--accent-dim); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent) !important; }
.pill.warn   { background: var(--warn-dim);   border-color: color-mix(in srgb, var(--warn) 30%, transparent);  color: var(--warn) !important; }
.pill.good   { background: var(--good-dim);   border-color: color-mix(in srgb, var(--good) 30%, transparent);  color: var(--good) !important; }
.pill.bad    { background: var(--bad-dim);    border-color: color-mix(in srgb, var(--bad) 30%, transparent);   color: var(--bad) !important; }
.tag.tag-green { background: var(--good-dim); color: var(--good) !important; }
html.light-mode .tag.tag-green { background: #e6f4ee; color: #065f46 !important; }

/* ================================================================
   14. TOOLBAR
   ================================================================ */
.toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: var(--sp3) 0; }
.controls-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px,1fr)); gap: 12px; margin: var(--sp3) 0; }

/* ================================================================
   15. DETAILS / ACCORDIONS
   ================================================================ */
details { border: 1px solid var(--border); border-radius: var(--radius); margin-top: var(--sp3); overflow: hidden; }
/* F133 — was `background: var(--card)` shorthand. Chrome's cascade for the
   `background` shorthand with a var() value doesn't always propagate to
   background-color when --card flips via class-based theme toggle (the value
   stayed at light-mode #ffffff even with html.dark-mode set). Using the
   background-color longhand resolves the var() correctly at apply time. */
details > summary { list-style: none; cursor: pointer; padding: 13px 18px; display: flex; align-items: center; justify-content: space-between; gap: 12px; font-weight: 700; font-size: 0.9rem; color: var(--text) !important; background-color: var(--card); transition: background-color 0.15s; }
details[open] > summary { background-color: var(--bg2); border-bottom: 1px solid var(--border); }
details > summary::-webkit-details-marker { display: none; }
details > summary::after { content: "›"; font-size: 1.2em; color: var(--faint) !important; transition: transform 0.2s; }
details[open] > summary::after { transform: rotate(90deg); }
.section-body { padding: var(--sp3) var(--sp4); }
.section-desc { color: var(--muted) !important; margin: 0 0 12px; }

/* ================================================================
   16. KICKER / UTILITY TEXT
   ================================================================ */
/* Kicker text on .accent-dim backgrounds — needs the DARKER accent shade
   to clear 4.5:1 at this small uppercase size. var(--accent) alone is
   #096e65 which measured 4.46:1 against the 10%-accent tinted bg
   (axe-core reported in #675). #054a42 gives ~7:1 with headroom. */
.kicker { font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: #054a42 !important; font-size: 0.71rem; margin-bottom: 6px; display: block; }
html.dark-mode .kicker { color: var(--accent) !important; }
html.light-mode .kicker { color: #054a42 !important; background: #e6f3f1; }
/* F129 — intro `<p class="sub">` was capped at 72ch (≈ 720px wide)
   which created a narrow text band with conspicuous empty space on the
   right at common viewport widths (1280/1440/1600px). Bumped to 95ch
   for breathing room while staying within the readable-line-length
   guideline; on narrower viewports the container's width takes over. */
.sub  { color: var(--muted) !important; max-width: 95ch; }
.card-meta { color: var(--faint) !important; font-size: var(--tiny); }
.note { color: var(--faint) !important; font-size: var(--caption); margin-top: var(--sp3); }
.disclaimer { color: var(--faint) !important; font-size: var(--caption); }
.err  { color: var(--bad) !important; font-weight: 700; }
.ok   { color: var(--good) !important; font-weight: 700; }
.text-muted { color: var(--muted) !important; }
.text-faint { color: var(--faint) !important; }
.text-accent { color: var(--accent) !important; }
.text-good  { color: var(--good) !important; }
.text-warn  { color: var(--warn) !important; }
.text-bad   { color: var(--bad) !important; }
.text-upper { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.74rem; font-weight: 700; }
.text-mono  { font-family: var(--font-mono); }

/* ================================================================
   17. CENSUS DASHBOARD
   ================================================================ */
#census-stats { margin: var(--sp4) 0; padding: var(--sp4); }
.census-controls { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.census-controls label { display: flex; flex-direction: column; gap: 6px; }
.census-controls select { min-width: 220px; }
.census-vintage { color: var(--muted) !important; font-weight: 700; }
.census-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px,1fr)); gap: 12px; margin-top: 12px; }
.census-grid .card { padding: var(--sp3); }
.census-grid .card .num { font-size: 1.5rem; font-weight: 800; color: var(--text) !important; letter-spacing: -0.02em; line-height: 1.1; margin: 0 0 4px; }
.census-grid .card .lbl { color: var(--muted) !important; font-size: var(--small); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; margin: 0; }

/* ================================================================
   18. LEAFLET
   ================================================================ */
/* Keep map panes below navigation (z-index: 9000) */
.leaflet-container { background: var(--bg2) !important; font-family: var(--font-sans) !important; position: relative; z-index: 10; }

/* Prevent map from trapping touch scroll on mobile */
@media (max-width: 768px) {
  .leaflet-container { pointer-events: none; }
  .leaflet-container.leaflet-grab,
  .leaflet-container.leaflet-crosshair { pointer-events: auto; }
}
.leaflet-control-zoom a, .leaflet-control-zoom a:hover { background: var(--card) !important; color: var(--text-strong) !important; border-color: var(--border) !important; }
/* F128 + F131 — Leaflet's CSS hardcodes background:#fff and color:#333 on
   every control surface AND every popup / tooltip wrapper. In dark mode
   our `strong { color: var(--text) !important }` (and any --accent /
   --bad / --info / --warn colored text in popup contents) paints light
   text on that white bg → catastrophic contrast. Runtime scanner found
   915 of these on housing-needs-assessment alone. The F128 fix covered
   controls; F131 expands to popups, tooltips, and the popup-content
   wrapper which Leaflet styles independently. */
.leaflet-control-layers,
.leaflet-control-layers-expanded,
.leaflet-control-attribution,
.leaflet-control-scale-line,
.leaflet-popup-content-wrapper,
.leaflet-popup-tip,
.leaflet-tooltip,
.leaflet-bar a {
  background: var(--card) !important;
  color: var(--text-strong) !important;
  border-color: var(--border) !important;
}
.leaflet-control-layers label,
.leaflet-control-layers strong,
.leaflet-control-attribution a,
.leaflet-popup-content,
.leaflet-popup-content *,
.leaflet-tooltip,
.leaflet-tooltip * {
  color: var(--text-strong) !important;
}
.leaflet-control-attribution a:hover { color: var(--link-hover) !important; }
/* F173/F175 — overflow-wrap: anywhere shrinks Leaflet tooltips to ~11px
   (their min-content becomes 1 character). Use break-word so the tooltip
   keeps word boundaries + grows to the natural content width up to max. */
.leaflet-tooltip { background: var(--surface) !important; color: var(--text) !important; border: 1px solid var(--border-strong) !important; box-shadow: var(--shadow) !important; border-radius: var(--radius-sm); font-family: var(--font-sans); font-size: 0.8rem; max-width: 300px; width: max-content; white-space: normal; word-break: normal; overflow-wrap: break-word; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--card) !important; color: var(--text) !important; box-shadow: var(--shadow-lg) !important; border: 1px solid var(--border) !important; }
/* F173 — popup content needs explicit overflow-wrap so long addresses,
   credit-type tags, and HUD/USDA program-type strings wrap inside the
   bounded popup box instead of spilling outside it. Table cells with
   `text-align:right` previously rendered single-line strings outside
   the popup boundary; min-width:0 + overflow-wrap fixes it. */
.leaflet-popup-content { color: var(--muted) !important; max-width: 320px; word-break: normal; overflow-wrap: anywhere; }
.leaflet-popup-content table { width: 100%; table-layout: fixed; }
.leaflet-popup-content table td { min-width: 0; word-break: normal; overflow-wrap: anywhere; }
.leaflet-popup-content strong, .leaflet-popup-content b { color: var(--text) !important; }
/* F173/F175 — AHL sticky marker tooltip (hover state). Its inner divs set
   max-width:260px inline; reinforce the wrap behavior so long
   property names + program descriptions wrap rather than overflow.
   Use break-word (not anywhere) so the tooltip doesn't collapse to 1 char. */
.leaflet-tooltip.ahl-marker-tip { max-width: 280px; width: max-content; white-space: normal; word-break: normal; overflow-wrap: break-word; }
.leaflet-tooltip.ahl-marker-tip > div { word-break: normal; overflow-wrap: break-word; }
.leaflet-bar { border: 1px solid var(--border) !important; border-radius: var(--radius-sm) !important; box-shadow: var(--shadow-sm) !important; }

/* Leaflet tile sizing can break if a global `img { max-width: 100%; }` rule exists elsewhere.
   Enforce Leaflet's expected behavior so basemaps always render and tooltips remain usable. */
.leaflet-container img.leaflet-tile,
.leaflet-container img.leaflet-marker-icon,
.leaflet-container img.leaflet-marker-shadow {
  max-width: none !important;
  max-height: none !important;
}

/* Optional per-page override: remove the centered "shell" styling when a page needs full-bleed layout. */
body.no-shell main {
  max-width: none !important;
  margin: 0 !important;
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  background: transparent !important;
  padding-left: var(--sp4) !important;
  padding-right: var(--sp4) !important;
}

/* Reusable minimum border shell for pages that need a subtle frame even with wide layouts. */
.content-shell {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
  box-shadow: var(--shadow-sm);
}

/* ================================================================
   19. SCROLLBAR
   ================================================================ */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg2); }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--faint); }

/* Light-mode selection: white text on dark teal accent (#096e65) → ~7:1.
   Scoped so it doesn't override the dark-mode selection rule that lives
   further up in this file (the previous global rule re-introduced white-on-
   bright-cyan in dark mode — ~2.3:1 — making selections invisible). */
@media (prefers-color-scheme: light) {
  ::selection      { background: var(--accent); color: var(--on-accent, #fff); }
  ::-moz-selection { background: var(--accent); color: var(--on-accent, #fff); }
}
html:not(.dark-mode) ::selection      { background: var(--accent); color: var(--on-accent, #fff); }
html:not(.dark-mode) ::-moz-selection { background: var(--accent); color: var(--on-accent, #fff); }

@media print {
  html, body { background: #fff !important; color: #000 !important; }
  header.site-header, footer.site-footer, nav { display: none; }
}

/* ---- Census Housing Construction sub-section ---- */
.census-construction-header {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin: 24px 0 10px;
  padding-top: 20px;
  border-top: 1px solid var(--border, rgba(255,255,255,0.1));
}
.census-construction-title {
  font-size: var(--small, 0.85rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text);
}
.census-construction-note {
  font-size: 0.75rem;
  color: var(--muted);
  font-weight: 500;
}
.census-construction-card .sub-lbl {
  font-size: 0.72rem;
  color: var(--muted);
  font-weight: 500;
  margin: 2px 0 0;
  line-height: 1.3;
}
.census-construction-card .census-date {
  font-size: 0.72rem;
  color: var(--faint, var(--muted));
  margin: 6px 0 0;
  font-weight: 500;
}
.census-construction-card .census-yoy {
  font-size: var(--tiny);
  font-weight: 700;
  margin: 3px 0 0;
}
.census-construction-card .delta-up   { color: var(--good, #34d399); }
.census-construction-card .delta-down { color: var(--bad,  #f87171); }
.census-construction-card .census-src {
  font-size: 0.68rem;
  margin: 6px 0 0;
}
.census-construction-card .census-src a {
  color: var(--accent, var(--muted));
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  /* Drop the 0.7 opacity — when blended on the card background it
     pushed the effective contrast below 4.5:1 for this small link
     text. Full opacity on the accent token is ~6:1 on card surfaces. */
}
.census-construction-card .census-src a:hover { text-decoration: underline; text-decoration-thickness: 2px; }

/* ================================================================
   20. VERTICAL SPACING — minimal logical gaps
   ================================================================ */
/* Tighten heading top-margin when preceded by other content */
main h2 { margin-top: var(--sp4); }
main h2:first-child, main > *:first-child > h2:first-child { margin-top: 0; }
main h3 { margin-top: var(--sp3); }
main h3:first-child { margin-top: 0; }

/* Hero-stat grids and KPI rows: consistent moderate gap */
/* F141 — was just `margin`. The .hero-stats wrapper on cra-expansion-analysis
   was rendering its 4 stat-cards vertically stacked (display:block) at every
   width — the page looked like a long column of "40% / 35% / 20% / 5%" pills.
   Now it's a responsive grid mirroring .stats-strip's behavior: 4 columns at
   ≥1024px (so all 4 fit side-by-side), 2 columns at 640–1024px, 1 column
   below. Other consumers of .hero-stats (only ~3 pages) already had similar
   inline grid styles, so this is purely additive. */
.hero-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp3);
  margin: var(--sp4) 0 !important;
}

/* Section-level divs that use margin: 2rem 0 pattern — normalize */
.section-block { margin: var(--sp4) 0; }

/* Reduce default paragraph-to-paragraph gap inside cards */
.card p + p, .panel p + p { margin-top: var(--sp2); }

/* ================================================================
   Card-safe containment utilities (audit N4)
   ----------------------------------------------------------------
   Cards that contain user-generated or upstream-shaped text (long
   URLs, RSS-derived titles, dataset names) need defensive containment
   so children don't push past the card right edge. Opt-in by adding
   `card-safe` to the card element; `card-safe-strict` cascades to all
   descendants for situations like the Housing News article rows where
   third-party data shape varies widely.
   ================================================================ */
.card-safe { min-width: 0; overflow-wrap: anywhere; word-break: normal; }
.card-safe-strict,
.card-safe-strict * { min-width: 0; overflow-wrap: anywhere; word-break: normal; }
.wrap-anywhere { overflow-wrap: anywhere; word-break: normal; }

/* "What this page does / not / how to use" callout — used on reference
   pages (Housing News, Economic Dashboard, etc.) to disclose tool
   scope to non-technical readers. */
.page-disclosure {
  /* F139 — was max-width: 80ch which created wide blank space on the
     right at desktop widths. Drop the cap; the multi-paragraph variants
     (e.g. economic-dashboard's What does / What is not / How to use)
     now flow into 3 columns at wider viewports so the text fills width
     instead of wrapping single-column with empty gutters. Single-
     paragraph disclosures still cap at a comfortable reading measure
     via the per-paragraph max-width below. */
  margin: var(--sp3) 0;
  padding: var(--sp2) var(--sp3);
  background: var(--bg2);
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  font-size: .9rem;
  line-height: 1.55;
  color: var(--text);
}
/* F139 — when the disclosure has 2+ siblings, render them as a
   responsive grid (3 columns ≥1024px, 2 columns ≥640px, 1 column
   below). Single paragraph keeps reading measure via 80ch. */
.page-disclosure { display: grid; gap: var(--sp3); grid-template-columns: 1fr; }
@media (min-width: 640px)  { .page-disclosure { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .page-disclosure { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.page-disclosure:has(> p:only-child) {
  grid-template-columns: minmax(0, 80ch);
}
.page-disclosure p { margin: 0; max-width: 60ch; }
.page-disclosure p:last-child { margin-bottom: 0; }
.page-disclosure strong { color: var(--text); }
.page-disclosure ul,
.page-disclosure ol { padding-left: 1.5rem; margin: .4rem 0 .35rem; }
.page-disclosure li { margin-bottom: .25rem; line-height: 1.55; }

/* ================================================================
   P4 — Container-edge bullet/list standardization
   ----------------------------------------------------------------
   Cards and panels were inheriting browser default `padding-left: 40px`
   on ul/ol, which on narrow containers pushed bullet markers up to (or
   past) the right side of the indent — looked cramped and unprofessional
   against the card's left edge. Standardize indent to a consistent 1.5rem
   so bullet markers always sit a clear gap inside the container, and
   give list items a unified vertical rhythm.

   Applies to: .card, .panel, .callout, .tile, .box, .briefs-disclosure,
   .lof-methodology, .lof-meth-note (the cards that hold body lists).
   Page-specific overrides win via specificity.
   ================================================================ */
.card ul,
.card ol,
.panel ul,
.panel ol,
.callout ul,
.callout ol,
.tile ul,
.tile ol,
.box ul,
.box ol,
.briefs-disclosure ul,
.briefs-disclosure ol,
.lof-methodology ul,
.lof-methodology ol,
.lof-meth-note ul,
.lof-meth-note ol {
  padding-left: 1.5rem;     /* consistent left indent inside containers */
  margin: 0.5rem 0 0.75rem; /* tight vertical rhythm */
}
.card li,
.panel li,
.callout li,
.tile li,
.box li,
.briefs-disclosure li,
.lof-methodology li,
.lof-meth-note li {
  margin-bottom: 0.35rem;   /* breathing room between items */
  line-height: 1.55;
}
.card li:last-child,
.panel li:last-child,
.callout li:last-child,
.tile li:last-child,
.box li:last-child,
.briefs-disclosure li:last-child,
.lof-methodology li:last-child,
.lof-meth-note li:last-child {
  margin-bottom: 0;
}

/* Headers in main containers should span the full container width.
   Some pages used inline max-width that constrained H1/H2 to ~640px
   even when the container was 1100px+; that left awkward whitespace
   on the right. Default headers to full width; intro copy can still
   constrain itself via .page-disclosure / max-width:80ch. */
main .card > h1,
main .card > h2,
main .card > h3,
main > h1,
main > h2 { max-width: 100%; }

/* ================================================================
   DARK MODE TOGGLE (class-based override + toggle button styles)
   ================================================================
   MANUAL TOGGLE support (.dark-mode / .light-mode class on <html>)
   Allows js/dark-mode-toggle.js to force a scheme regardless of OS.
   ---------------------------------------------------------------- */

/* Force light mode even when OS prefers dark */
html.light-mode {
  color-scheme: light;

  --bg:            #eef2f7;
  --bg2:           #e4ecf4;
  --bg3:           #dae4f0;
  --card:          #ffffff;
  --card2:         #f7fafd;
  --surface:       #ffffff;
  --text:          #0d1f35;
  --text-strong:   #060f1d;
  --muted:         #374151;
  --faint:         #4b5563;
  --border:        rgba(13,31,53,.11);
  --border-strong: rgba(13,31,53,.20);
  --link:          #005a9c;
  --link-hover:    #004880;
  --accent:        #096e65;
  --accent-dim:    rgba(9,110,101,.10);
  --on-accent:     #ffffff; /* F122 — see top of file for paired-token rationale */
  --accent2:       #c86f0d;
  --accent2-dim:   rgba(200,111,13,.10);
  --good:          #047857;
  --good-dim:      rgba(4,120,87,.10);
  --warn:          #a84608;
  --warn-dim:      rgba(168,70,8,.10);
  --bad:           #991b1b;
  --bad-dim:       rgba(153,27,27,.10);
  --info:          #1d4ed8;
  --info-dim:      rgba(29,78,216,.09);
  --on-good:       #ffffff; /* F133 — see top-of-file paired-token rationale */
  --on-warn:       #ffffff;
  --on-bad:        #ffffff;
  --on-info:       #ffffff;
  --shadow-sm:     0 1px 3px rgba(13,31,53,.07),0 1px 2px rgba(13,31,53,.04);
  --shadow:        0 4px 16px rgba(13,31,53,.08),0 1px 4px rgba(13,31,53,.05);
  --shadow-lg:     0 12px 40px rgba(13,31,53,.12),0 4px 12px rgba(13,31,53,.07);
  --shadow-card:   0 2px 8px rgba(13,31,53,.06),0 0 0 1px rgba(13,31,53,.07);
  --color-background:     var(--bg);
  --color-background-alt: var(--bg2);
  --color-surface:        var(--card);
  --color-text:           var(--text);
  --color-text-light:     var(--muted);
  --color-text-muted:     var(--faint);
  --color-border:         var(--border);
  --bg-primary:           var(--bg);
  --bg-secondary:         var(--bg2);
  --text-primary:         var(--text);
  --text-secondary:       var(--muted);
  --accent-gold:          var(--accent2);
  --border-color:         var(--border);
  --map-boundary-stroke:  rgba(15, 23, 42, 0.55);
  --map-boundary-weight:  2.25;
}

html.light-mode body,
html.light-mode html {
  background: var(--bg) !important;
  color: var(--text) !important;
}

html.dark-mode {
  color-scheme: dark;

  /* Mirror dark-mode variable values when forced via JS toggle */
  --bg:            #08121e;
  --bg2:           #0c1928;
  --bg3:           #0f1e30;
  --card:          #0d1e30;
  --card2:         #102234;
  --surface:       #112438;
  --surface-2:     #152a40;
  --text:          rgba(215,232,248,.93);
  --text-strong:   #e4f0fc;
  --muted:         rgba(210,225,245,.95);
  --faint:         rgba(190,210,235,.90);
  --border:        rgba(90,150,210,.11);
  --border-strong: rgba(90,150,210,.22);
  --link:          #5ecbcc;
  --link-hover:    #7dd8d9;
  --accent:        #0fd4cf;
  --accent-dim:    rgba(15,212,207,.12);
  --on-accent:     #0a0f1d; /* F122 — see top of file for paired-token rationale */
  --accent2:       #fbbf24;
  --accent2-dim:   rgba(251,191,36,.12);
  --good:          #34d399;
  --good-dim:      rgba(52,211,153,.12);
  --warn:          #fbbf24;
  --warn-dim:      rgba(251,191,36,.12);
  --bad:           #f87171;
  --bad-dim:       rgba(248,113,113,.12);
  --info:          #60a5fa;
  --info-dim:      rgba(96,165,250,.11);
  --on-good:       #0a0f1d; /* F133 — see top-of-file paired-token rationale */
  --on-warn:       #0a0f1d;
  --on-bad:        #0a0f1d;
  --on-info:       #0a0f1d;
  --bg-rgb:        8,18,30;
  --card-rgb:      13,30,48;
  --shadow-sm:     0 1px 3px rgba(0,0,0,.30);
  --shadow:        0 4px 20px rgba(0,0,0,.40),0 1px 4px rgba(0,0,0,.25);
  --shadow-lg:     0 16px 48px rgba(0,0,0,.55),0 4px 16px rgba(0,0,0,.35);
  --shadow-card:   0 2px 8px rgba(0,0,0,.35),0 0 0 1px rgba(90,150,210,.10);
  --color-background:     var(--bg);
  --color-background-alt: var(--bg2);
  --color-surface:        var(--card);
  --color-text:           var(--text);
  --color-text-light:     var(--muted);
  --color-text-muted:     var(--faint);
  --color-border:         var(--border);
  --bg-primary:           var(--bg);
  --bg-secondary:         var(--bg2);
  --text-primary:         var(--text);
  --text-secondary:       var(--muted);
  --accent-gold:          var(--accent2);
  --border-color:         var(--border);
  --map-boundary-stroke:  rgba(248, 250, 252, 0.50);
  --map-boundary-weight:  2.25;
}

html.dark-mode body,
html.dark-mode html {
  background: var(--bg) !important;
  color: var(--text) !important;
}

html.dark-mode ::selection      { background: var(--accent); color: #0d1f35; }
html.dark-mode ::-moz-selection { background: var(--accent); color: #0d1f35; }

/* ----------------------------------------------------------------
   4. DARK MODE TOGGLE BUTTON (optional, injected by dark-mode-toggle.js)
   ---------------------------------------------------------------- */
.dark-mode-toggle {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  z-index: 9999;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--card);
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  box-shadow: var(--shadow);
  transition: background 0.2s, box-shadow 0.2s;
}

.dark-mode-toggle:hover {
  background: var(--bg2);
  box-shadow: var(--shadow-lg);
}

.dark-mode-toggle:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring), var(--shadow);
}

/* ----------------------------------------------------------------
   DATA FRESHNESS TIMESTAMP — shown on all major data pages
   ---------------------------------------------------------------- */
.data-timestamp {
  display: block;
  font-size: var(--tiny);
  color: var(--faint);
  margin-top: var(--sp1);
  font-family: var(--font-mono);
  letter-spacing: 0.01em;
}

/* === COHO Analytics — workflow step component === */
.workflow-steps{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 0;margin-bottom:16px;border-bottom:1px solid var(--border)}
.workflow-step{padding:6px 12px;border-radius:999px;font-size:.82rem;font-weight:600;color:var(--muted);background:transparent;text-decoration:none;border:1px solid transparent;transition:background .15s,color .15s}
.workflow-step:hover{color:var(--text);background:color-mix(in oklab,var(--card) 80%,var(--accent) 20%)}
.workflow-step--active{color:var(--accent);border-color:var(--accent);background:color-mix(in oklab,var(--card) 85%,var(--accent) 15%)}
.workflow-step-arrow{color:var(--muted);font-size:.82rem}

/* === Scoping-a-Project workflow progress bar — sticky below site header ===
   F130 — Consolidated ALL stepper CSS into one rule block. Previously each
   of the 6 stepper pages (Opportunity Finder, Jurisdiction, HNA, Market
   Analysis, Scenarios, Deal Calc) maintained its own inline CSS for
   .wf-step, .wf-step__num, .wf-step__label, .wf-step--active, .wf-step--done,
   .wf-step-connector — and over time those copies drifted (different
   min-widths, different colors, missing rules). The user reported "submenu
   circles move around between pages" — that's the drift. Now every property
   that controls circle position / size / color / connector layout lives
   HERE; the inline copies in each page are still tolerated but no longer
   authoritative (the !important flags below win). */
.wf-progress-wrap,
.workflow-progress {
  position: sticky;
  top: 58px;       /* height of the sticky site header */
  z-index: 8500;   /* below header (9000) but above page content */
  background: color-mix(in oklab, var(--card) 85%, transparent);
  backdrop-filter: saturate(1.2) blur(10px);
  -webkit-backdrop-filter: saturate(1.2) blur(10px);
  border-bottom: 1px solid var(--border);
  /* Override inline script-injected stylesheets (appended to <head> after site-theme.css loads) */
  max-width: none !important;
  margin: 0 !important;
  padding: 6px 0 !important;
}
/* Keep the inner step list centered at 1200 px on wide viewports */
.wf-progress-steps,
.workflow-progress__steps {
  display: flex !important;
  align-items: center !important;
  gap: 0 !important;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--sp4);
}
/* INDIVIDUAL STEP — the column with circle + label. min-width pins each
   column to the same width across pages so the circles align identically
   regardless of label text length. Uses `!important` to defeat any stale
   inline copies still living in page CSS. */
.wf-step,
.workflow-progress__step {
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  text-align: center !important;
  text-decoration: none !important;
  color: var(--muted) !important;
  flex: 1 1 0 !important;
  min-width: 80px !important;
}
/* THE CIRCLE — fixed 30px diameter, identical default state across pages. */
.wf-step__num,
.workflow-progress__step__num {
  width: 30px !important;
  height: 30px !important;
  border-radius: 50% !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  font-size: .78rem !important;
  font-weight: 700 !important;
  background: var(--bg2) !important;
  color: var(--muted) !important;
  border: 2px solid var(--border) !important;
  z-index: 1 !important;
  flex-shrink: 0 !important;
}
.wf-step__label,
.workflow-progress__step__label {
  font-size: .68rem !important;
  color: var(--muted) !important;
  margin-top: 4px !important;
  line-height: 1.2 !important;
  font-weight: 600 !important;
}
.wf-step-connector {
  flex: 1 !important;
  height: 2px !important;
  background: var(--border) !important;
  min-width: 20px !important;
  margin-bottom: 18px !important;
}
/* ACTIVE state — current page's step. Cyan accent fill, on-accent text.
   F127 baseline; F130 promotes to global. */
.wf-step--active .wf-step__num,
.workflow-progress__step--active .workflow-progress__step__num {
  background: var(--accent) !important;
  color: var(--on-accent, #fff) !important;
  border-color: var(--accent) !important;
}
.wf-step--active .wf-step__label,
.workflow-progress__step--active .workflow-progress__step__label {
  color: var(--accent) !important;
}
/* DONE state — completed step. Green fill. */
.wf-step--done .wf-step__num,
.workflow-progress__step--done .workflow-progress__step__num {
  background: var(--good, #047857) !important;
  color: var(--on-accent, #fff) !important;
  border-color: var(--good, #047857) !important;
}
.wf-step--done .wf-step__label,
.workflow-progress__step--done .workflow-progress__step__label {
  color: var(--good, #047857) !important;
}

@media (max-width: 480px) {
  .wf-progress-wrap,
  .workflow-progress {
    box-sizing: border-box !important;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 0 !important;
    overflow: hidden !important;
  }

  .wf-progress-steps,
  .workflow-progress__steps {
    box-sizing: border-box !important;
    width: 100% !important;
    max-width: 100% !important;
    min-width: 0 !important;
    padding: 0 10px !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }

  .wf-progress-steps::-webkit-scrollbar,
  .workflow-progress__steps::-webkit-scrollbar {
    display: none;
  }

  .wf-progress-steps .wf-step,
  .workflow-progress__steps .wf-step,
  .workflow-progress__steps .workflow-progress__step {
    flex: 0 0 58px !important;
    min-width: 58px !important;
  }

  .wf-progress-steps .wf-step-connector,
  .workflow-progress__steps .wf-step-connector {
    flex: 0 0 12px !important;
    min-width: 12px !important;
  }
}

/* === Workflow "Recommended Next Action" banner === */
.wf-next-action {
  max-width: 1200px;
  margin: 12px auto 0;
  padding: 0 18px;
}
.wf-next-action__inner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  flex-wrap: wrap;
}
.wf-next-action__icon {
  font-size: 1.25rem;
  flex-shrink: 0;
}
.wf-next-action__text {
  flex: 1;
  min-width: 200px;
}
.wf-next-action__heading {
  display: block;
  font-size: .85rem;
  font-weight: 700;
  color: var(--text-strong);
  margin-bottom: 2px;
}
.wf-next-action__body {
  display: block;
  font-size: .82rem;
  color: var(--muted);
  line-height: 1.45;
}
.wf-next-action__cta {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 7px 16px;
  font-size: .82rem;
  font-weight: 700;
  border-radius: var(--radius-sm);
  text-decoration: none !important;
  white-space: nowrap;
  transition: background .15s, color .15s;
  flex-shrink: 0;
}
/* Variant: next (green accent — step complete, continue) */
.wf-next-action--next .wf-next-action__inner {
  background: color-mix(in oklab, var(--card) 60%, var(--good) 8%);
  border-color: color-mix(in oklab, var(--border) 40%, var(--good) 30%);
}
.wf-next-action--next .wf-next-action__cta {
  background: var(--good);
  /* F132 — was `color: #fff !important`. White on dark-mode --good (#34d399)
     scores ~2:1; dark text scores ~9:1. Use a paired-token approach: dark
     navy text in dark mode where --good is brighter, white in light mode
     where --good is darker. */
  color: #fff;
}
html.dark-mode .wf-next-action--next .wf-next-action__cta { color: #0a0f1d; }
.wf-next-action--next .wf-next-action__cta:hover {
  background: color-mix(in oklab, var(--good) 85%, #000);
}
/* Variant: current (neutral) */
.wf-next-action--current .wf-next-action__inner {
  background: color-mix(in oklab, var(--card) 80%, var(--accent) 8%);
  border-color: color-mix(in oklab, var(--border) 50%, var(--accent) 20%);
}
/* Variant: skipped (warning) */
.wf-next-action--skipped .wf-next-action__inner {
  background: color-mix(in oklab, var(--card) 60%, var(--warn) 8%);
  border-color: color-mix(in oklab, var(--border) 40%, var(--warn) 30%);
}
.wf-next-action--skipped .wf-next-action__cta {
  /* F132 — was `color: #fff !important` on var(--warn). White on amber
     #d97706 light = 2.5:1; on yellow #fbbf24 dark = 1.4:1. Now use a
     theme-aware pairing: lighten the bg in light mode (so dark text wins)
     and keep the bright yellow in dark mode with the same dark text. */
  background: #fbbf24;
  color: #1a0a00;
}
html.dark-mode .wf-next-action--skipped .wf-next-action__cta {
  background: var(--warn);
}
.wf-next-action--skipped .wf-next-action__cta:hover {
  background: color-mix(in oklab, var(--warn) 85%, #000);
  color: #fff;
}
/* Variant: complete (celebration) */
.wf-next-action--complete .wf-next-action__inner {
  background: color-mix(in oklab, var(--card) 55%, var(--accent) 12%);
  border-color: color-mix(in oklab, var(--border) 30%, var(--accent) 40%);
}

/* === Housing Outcome Score panel === */
.hos-panel {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
}
.hos-dim {
  background: var(--bg2);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
}
.hos-dim__label {
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  margin-bottom: 4px;
}
.hos-dim__score {
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--text-strong);
}
.hos-dim__bar {
  height: 6px;
  background: var(--bg3);
  border-radius: 3px;
  margin-top: 6px;
  overflow: hidden;
}
.hos-dim__fill {
  height: 100%;
  border-radius: 3px;
  transition: width 0.4s ease;
}
.hos-dim--unavailable .hos-dim__score { color: var(--faint); }

/* === Platform disclaimer === */
.platform-disclaimer{padding:10px 16px;background:color-mix(in oklab,var(--card) 90%,var(--accent) 10%);border-left:3px solid var(--accent);border-radius:4px;font-size:.85rem;color:var(--muted);margin-bottom:16px}

/* === Kicker spans === */
span.kicker{display:block;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--accent);margin-bottom:4px}

/* === Deal calculator === */
.deal-calc-panel{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:24px;margin-top:24px}
.deal-calc-panel h2{font-size:1.1rem;font-weight:700;margin-bottom:4px}
.deal-calc-disclaimer{font-size:.8rem;color:var(--muted);margin-bottom:16px}
.deal-calc-inputs{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:16px}
.deal-calc-field label{display:block;font-size:.82rem;font-weight:600;margin-bottom:4px;color:var(--muted)}
.deal-calc-field input[type=number],.deal-calc-field input[type=range]{width:100%}
.deal-calc-results{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;background:var(--bg2);border-radius:6px;padding:16px}
.deal-calc-result-item .label{font-size:.78rem;color:var(--muted);font-weight:600}
.deal-calc-result-item .value{font-size:1.15rem;font-weight:800;color:var(--text)}

/* ── Data-reliability badge (DataQuality module) ─────────────────────────── */
.data-reliability-badge,.freshness-badge{
  display:inline-flex;align-items:center;
  font-size:.78rem;font-weight:600;line-height:1.2;
  padding:.25rem .6rem;border-radius:2rem;
  border:1px solid transparent;
  white-space:nowrap;cursor:default;
  transition:background .15s,color .15s;
}
.data-reliability-badge.drb--ok,.freshness-badge[data-freshness="now"]{
  background:var(--good-dim);color:var(--good);border-color:rgba(4,120,87,.25);
}
.data-reliability-badge.drb--warn{
  background:var(--warn-dim);color:var(--warn);border-color:rgba(180,83,9,.25);
}
.data-reliability-badge.drb--error{
  background:var(--bad-dim);color:var(--bad);border-color:rgba(153,27,27,.25);
}
/* Default state before DataQuality runs */
.data-reliability-badge:not(.drb--ok):not(.drb--warn):not(.drb--error){
  background:var(--bg2);color:var(--muted);border-color:var(--border);
}
/* === Data Quality Summary panel (js/components/data-quality-summary.js) === */
.dqs-panel { margin: 0 var(--sp4) var(--sp3); border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); font-size: .82rem; }
.dqs-summary { padding: .5rem .8rem; cursor: pointer; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; user-select: none; }
.dqs-summary::-webkit-details-marker { display: none; }
.dqs-summary::marker { content: ''; }
.dqs-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.dqs-status-dot.dqs-ok { background: var(--good); }
.dqs-status-dot.dqs-warn { background: var(--warn); }
.dqs-status-dot.dqs-error { background: var(--bad); }
.dqs-source-count { color: var(--muted); font-size: .75rem; }
.dqs-updated { margin-left: auto; color: var(--faint); font-size: .72rem; }
.dqs-body { padding: .5rem .8rem .8rem; border-top: 1px solid var(--border); }
.dqs-table { width: 100%; border-collapse: collapse; font-size: .78rem; }
.dqs-table th { text-align: left; font-weight: 600; color: var(--muted); padding: 4px 8px; border-bottom: 1px solid var(--border); font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; }
.dqs-table td { padding: 5px 8px; border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); }
.dqs-icon { font-size: .65rem; }
tr.dqs-ok .dqs-icon { color: var(--good); }
tr.dqs-warn .dqs-icon { color: var(--warn); }
tr.dqs-error .dqs-icon { color: var(--bad); }
.dqs-fresh { color: var(--good); font-size: .72rem; font-weight: 600; }
.dqs-recent { color: var(--muted); font-size: .72rem; }
.dqs-stale { color: var(--warn); font-size: .72rem; font-weight: 600; }
.dqs-note { color: var(--muted); font-size: .72rem; font-style: italic; }
.dqs-coverage { color: var(--muted); font-size: .72rem; }
.dqs-limitations { margin-top: .6rem; padding: .5rem .6rem; background: color-mix(in srgb, var(--warn) 8%, transparent); border: 1px solid color-mix(in srgb, var(--warn) 20%, transparent); border-radius: 6px; font-size: .78rem; line-height: 1.5; color: var(--text); }
@media(max-width:600px) { .dqs-table th:nth-child(n+4), .dqs-table td:nth-child(n+4) { display: none; } }

/* === Page Context panel (js/components/page-context.js) === */
.pctx-panel { margin: 0 var(--sp4) var(--sp3); border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); font-size: .84rem; }
.pctx-summary { padding: .55rem .8rem; cursor: pointer; font-weight: 600; font-size: .82rem; color: var(--text); user-select: none; }
.pctx-summary::-webkit-details-marker { display: none; }
.pctx-summary::marker { content: ''; }
.pctx-summary::before { content: '▸ '; color: var(--muted); }
details[open].pctx-panel > .pctx-summary::before { content: '▾ '; }
.pctx-body { padding: 0 .8rem .8rem; display: grid; grid-template-columns: 1fr 1fr; gap: .5rem .8rem; }
.pctx-section { min-width: 0; }
.pctx-label { font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: .2rem; }
.pctx-text { margin: 0; font-size: .82rem; line-height: 1.55; color: var(--text); }
.pctx-not { grid-column: 1 / -1; background: color-mix(in srgb, var(--warn) 6%, transparent); border: 1px solid color-mix(in srgb, var(--warn) 18%, transparent); border-radius: 6px; padding: .5rem .6rem; }
.pctx-not .pctx-label { color: var(--warn); }
.pctx-next { grid-column: 1 / -1; }
.pctx-next-links { display: flex; flex-wrap: wrap; gap: .4rem; }
.pctx-next-link { display: inline-flex; align-items: baseline; gap: .25rem; padding: .3rem .6rem; background: var(--card); border: 1px solid var(--border); border-radius: 6px; text-decoration: none; font-size: .78rem; color: var(--text); transition: border-color .15s; }
.pctx-next-link:hover { border-color: var(--accent); }
.pctx-next-desc { color: var(--muted); font-size: .72rem; }
@media(max-width:600px) { .pctx-body { grid-template-columns: 1fr; } }

/* === API Health badge (js/components/api-health.js) === */
.apih-panel { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); margin: 0 var(--sp4) var(--sp2); }
.apih-body { padding: .4rem .8rem .6rem; display: flex; flex-wrap: wrap; gap: .3rem .8rem; border-top: 1px solid var(--border); }
.apih-row { font-size: .75rem; display: flex; align-items: center; gap: .25rem; }
.apih-icon { font-weight: 700; font-size: .7rem; }
.apih-row.dqs-ok .apih-icon { color: var(--good); }
.apih-row.dqs-warn .apih-icon { color: var(--warn); }
.apih-row.dqs-error .apih-icon { color: var(--bad); }

/* === Map Layer Status (js/components/map-layer-status.js) === */
.mls-panel { font-size: .75rem; padding: .4rem .6rem; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); }
.mls-row { display: flex; align-items: center; gap: .4rem; padding: 2px 0; }
.mls-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.mls-dot--full { background: var(--good); }
.mls-dot--partial { background: var(--warn); }
.mls-dot--map { background: var(--accent); }
.mls-dot--off { background: var(--faint); opacity: .5; }
.mls-label { flex: 1; }
.mls-scope { color: var(--faint); font-size: .68rem; margin-left: auto; }

/* === Development Realism panels (js/components/development-realism.js) === */
.devr-panel { margin: var(--sp3) var(--sp4); border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); }
.devr-summary { padding: .6rem .8rem; cursor: pointer; font-weight: 700; font-size: .88rem; color: var(--text); user-select: none; }
.devr-body { padding: 0 .8rem 1rem; }
.devr-intro { font-size: .82rem; color: var(--muted); line-height: 1.55; margin: 0 0 .8rem; }
.devr-subhead { font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); margin: 1rem 0 .4rem; border-bottom: 1px solid var(--border); padding-bottom: .25rem; }
.devr-checklist { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .3rem; }
.devr-item { font-size: .82rem; line-height: 1.45; padding: .35rem .5rem; border-radius: 6px; background: var(--card); border: 1px solid var(--border); }
.devr-check-label { display: flex; align-items: flex-start; gap: .4rem; cursor: pointer; }
.devr-checkbox { margin-top: .15rem; flex-shrink: 0; accent-color: var(--accent); }
.devr-item-note { font-size: .75rem; color: var(--muted); margin-top: .2rem; padding-left: 1.4rem; line-height: 1.45; font-style: italic; }
.devr-flag { display: inline-flex; font-size: .65rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; padding: 1px 6px; border-radius: 999px; margin-left: .4rem; vertical-align: middle; }
.devr-flag--fatal { background: var(--bad-dim, #fde8e8); color: var(--bad); border: 1px solid rgba(153,27,27,.25); }
.devr-flag--verify { background: var(--warn-dim, #fef3c7); color: var(--warn); border: 1px solid rgba(180,83,9,.25); }
/* F255 — Strong Signal contrast fix. Previous tokens computed to ~4.3:1
   in light mode (--good #047857 on rgba(4,120,87,.10) over white card).
   Light mode now pairs a saturated tint with green-900 text (~9:1);
   dark mode keeps the bright --good text on a slightly stronger tint
   to match the same ~9:1 target so both modes are well above WCAG AA. */
.devr-flag--strong { background: rgba(4,120,87,.15); color: #022c1f; border: 1px solid rgba(4,120,87,.30); }
html.dark-mode .devr-flag--strong { background: rgba(52,211,153,.18); color: #d4f5e6; border: 1px solid rgba(52,211,153,.40); }
.devr-type-card { padding: .5rem .6rem; background: var(--card); border: 1px solid var(--border); border-radius: 6px; margin-bottom: .4rem; }
.devr-type-specs { font-size: .75rem; font-weight: 600; color: var(--accent); margin-bottom: .25rem; }
.devr-type-notes { font-size: .78rem; color: var(--muted); line-height: 1.5; }

/* === Land Value Tool (js/components/land-value-tool.js) === */
.lvt-tool { max-width: 900px; margin: 0 auto; }
.lvt-header { margin-bottom: 1rem; }
.lvt-title { font-size: 1.2rem; margin: 0 0 .3rem; }
.lvt-subtitle { font-size: .82rem; color: var(--muted); margin: 0; }
.lvt-section { margin-bottom: 1.2rem; padding: .8rem; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); }
.lvt-section-title { font-size: .88rem; font-weight: 700; margin: 0 0 .4rem; color: var(--accent); }
.lvt-note { font-size: .78rem; color: var(--muted); margin: 0 0 .6rem; line-height: 1.45; }
.lvt-row { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: .4rem; }
.lvt-label { display: flex; flex-direction: column; gap: .15rem; font-size: .78rem; font-weight: 600; color: var(--muted); flex: 1; min-width: 180px; }
.lvt-input { padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: .85rem; }
.lvt-input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent); }
.lvt-help { font-size: .72rem; color: var(--faint); line-height: 1.4; flex: 1; min-width: 180px; align-self: center; }
.lvt-comp-row { display: flex; gap: .4rem; margin-bottom: .3rem; }
.lvt-btn-primary { padding: .6rem 1.5rem; background: var(--accent); color: var(--on-accent, #fff); border: none; border-radius: 8px; font-size: .88rem; font-weight: 700; cursor: pointer; }
.lvt-btn-primary:hover { opacity: .9; }
.lvt-btn-secondary { padding: .3rem .8rem; background: var(--bg2); color: var(--muted); border: 1px solid var(--border); border-radius: 6px; font-size: .78rem; cursor: pointer; }
.lvt-results-header { margin-bottom: .8rem; padding: .6rem .8rem; background: var(--bg2); border-radius: var(--radius); }
.lvt-conf { font-size: .85rem; display: flex; align-items: center; gap: .4rem; }
.lvt-conf-note { font-size: .75rem; color: var(--muted); margin-top: .2rem; }
.lvt-result-card { margin-bottom: .8rem; padding: .8rem; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); }
.lvt-result-title { font-size: .85rem; font-weight: 700; margin: 0 0 .5rem; }
.lvt-result-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: .5rem; }
.lvt-metric { text-align: center; }
.lvt-metric-val { font-size: 1.1rem; font-weight: 700; color: var(--text); }
.lvt-metric-lbl { font-size: .7rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.lvt-warning { margin-top: .5rem; padding: .4rem .6rem; background: var(--bad-dim, #fde8e8); border: 1px solid rgba(153,27,27,.2); border-radius: 6px; font-size: .78rem; color: var(--bad); }
.lvt-band { display: flex; flex-direction: column; gap: .35rem; }
.lvt-band-row { display: flex; align-items: center; gap: .5rem; }
.lvt-band-label { font-size: .72rem; color: var(--muted); min-width: 140px; text-align: right; }
.lvt-band-bar-bg { flex: 1; height: 26px; background: var(--bg2); border-radius: 4px; overflow: hidden; }
.lvt-band-bar { height: 100%; display: flex; align-items: center; padding: 0 8px; font-size: .72rem; font-weight: 700; color: #fff; border-radius: 4px; min-width: fit-content; white-space: nowrap; }
.lvt-band-note { font-size: .78rem; color: var(--muted); margin-top: .5rem; font-style: italic; }
.lvt-disclaimer { margin-top: 1rem; padding: .6rem .8rem; background: color-mix(in srgb, var(--warn) 8%, transparent); border: 1px solid color-mix(in srgb, var(--warn) 20%, transparent); border-radius: 6px; font-size: .75rem; line-height: 1.5; color: var(--text); }
@media(max-width:600px) { .lvt-comp-row { flex-direction: column; } .lvt-band-label { min-width: 100px; font-size: .65rem; } }

/* === iframe loading-spinner overlay (used by js/iframe-spinner.js) === */
.map-iframe-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  background: var(--bg2, #f5f7fa);
  border-radius: inherit;
  z-index: 2;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.map-iframe-overlay.loaded { opacity: 0; }
.map-iframe-spinner {
  width: 2rem;
  height: 2rem;
  border: 3px solid var(--border, rgba(13,31,53,.12));
  border-top-color: var(--accent, #096e65);
  border-radius: 50%;
  animation: _map-spin 0.7s linear infinite;
}
@keyframes _map-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .map-iframe-spinner { animation: none; } }
.map-iframe-overlay-text { font-size: 0.85rem; color: var(--muted, #476080); }

/* === Pipeline status warning banner === */
.pipeline-status-banner {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.65rem 1.1rem;
  background: var(--warn-dim, #fef3c7);
  color: var(--warn, #b45309);
  border-left: 4px solid var(--warn, #b45309);
  border-radius: var(--radius-sm, 4px);
  font-size: 0.875rem;
  font-weight: 500;
  margin: 0.75rem 0;
}
.pipeline-status-banner[hidden] { display: none; }

@media(max-width:480px){
  .data-reliability-badge,.freshness-badge{font-size:.72rem;padding:.2rem .5rem;}
}
