/* Font families (Inter + Outfit) are loaded via <link> in index.html <head>
   for non-render-blocking delivery — do not re-add an @import here. */

:root {
    --font-heading: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    --font-body: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    
    /* Sleek Dark Palette */
    /* Phase AH (2026-08-01): Nick — "make the darkmode background a
       little more grey but dark." Same shape of fix as Phase AA's light-
       mode retune, just the opposite direction: #030712 (rgb 3,7,18) is a
       near-black BLUE — B is 6x R — not a neutral dark grey. Retuned to
       #15171b (rgb 21,23,27, B only ~1.3x R), and --surf-1/2/3 below
       re-derived as neutral translucent greys over it, keeping the same
       relative step sizes (panel lighter than base, inset darker) so the
       existing depth hierarchy is unchanged, only the hue is. See the
       matching --surf-0..3 block below for the composited values this
       was checked against. */
    --color-bg: #15171b;
    --color-surface: rgba(17, 24, 39, 0.7);
    --color-surface-hover: rgba(31, 41, 55, 0.85);
    --color-border: rgba(255, 255, 255, 0.08);
    --color-border-active: rgba(255, 255, 255, 0.2);
    --color-text-primary: #f9fafb;
    --color-text-secondary: #9ca3af;
    --color-text-muted: #6b7280;
    
    /* Accent gradients */
    --accent-gradient: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%);
    --accent-glow: 0 0 20px rgba(168, 85, 247, 0.4);

    /* Proportional phone scaling (RELEASE_PLAN.md Phase F5e, 2026-07-29).
       app.js's applyViewportMetrics() overwrites both of these via
       setProperty() on documentElement before first paint (called
       synchronously, not on DOMContentLoaded) — these are only the static
       fallback so the phone block is never unstyled if JS is slow to run. */
    --ui-scale: 1;
    --app-h: 100dvh;
    /* Touch-target floor (RELEASE_PLAN.md Phase F3) — never scale a tap
       target below 44px even on the smallest phone. Use this for anything
       a finger lands on; use --ui-scale directly only for type, spacing,
       and decorative chrome. */
    --tap: max(44px, calc(44px * var(--ui-scale)));

    /* Phase X token foundation (RELEASE_PLAN.md, 2026-07-31). A complete
       semantic set, both themes assign every token — see body.theme-light
       below for the light values, which are NOT simply lighter versions of
       these; --accent/--good/--bad get different literal values per theme
       on purpose (contrast — see the comment by the light block). */
    /* Phase AH (2026-08-01): retuned from the original blue-black ladder
       (#030712 / rgba(17,24,39,*) / rgba(15,23,42,*) / rgba(2,6,23,*)) to
       neutral dark greys — same method as Phase AA's light-mode retune:
       picked values, then pixel-sampled the actual COMPOSITED result
       (these are translucent, so the declared value isn't what lands on
       screen) against the new --surf-0 base. Composited over #15171b:
       --surf-1 ≈ rgb(30,31,36) — visibly lighter than base, panel reads
       as "raised"; --surf-2 ≈ rgb(23,25,29) — a touch lighter, nested
       card; --surf-3 ≈ rgb(18,20,23) — darker than base, inset/recessed.
       Same relative ordering (inset < base < card < panel) as the
       original ladder, hue neutralized (R≈G≈B, not R<G<B) rather than
       the hierarchy changed. */
    --surf-0: #15171b;                      /* app background / ocean */
    --surf-1: rgba(30, 32, 38, 0.85);       /* panel base (glass) */
    --thumb-ring: rgba(0, 0, 0, 0.55);      /* slider thumb edge (Phase BY) */
    --surf-2: rgba(24, 26, 31, 0.60);       /* nested card */
    --surf-3: rgba(16, 17, 20, 0.55);       /* inset / deepest */
    --bdr-1:  rgba(255, 255, 255, 0.08);
    --bdr-2:  rgba(255, 255, 255, 0.18);
    --txt-1:  #f9fafb;                      /* headings / primary */
    --txt-2:  #cbd5e1;                      /* body */
    --txt-3:  #94a3b8;                      /* secondary */
    --txt-4:  #64748b;                      /* muted / labels */
    --accent:        #fbbf24;               /* amber — active/selected/value */
    /* Phase AQ (RELEASE_PLAN.md, 2026-09-12): --accent-fill is IDENTICAL to
       --accent in dark mode (defined separately anyway so both themes have
       the same two names available) but DIVERGES in light mode — see that
       block for why. Use --accent for TEXT (anywhere accent renders as a
       glyph/label color on a background); use --accent-fill for anything
       that IS a background, border, outline, box-shadow glow, or native
       accent-color — i.e. a filled shape or indicator, not a character. */
    --accent-fill:   #fbbf24;
    --accent-soft:   rgba(245, 158, 11, 0.18);
    --accent-border: rgba(245, 158, 11, 0.40);
    --good:      #34d399;  --good-soft: rgba(16, 185, 129, 0.15);
    --bad:       #fb7185;  --bad-soft:  rgba(244, 63, 94, 0.15);
    --shadow-1: 0 8px 32px rgba(0, 0, 0, 0.50);

    /* Nick, 2026-08-01: "give match criteria priority levels their own
       colors" — a 5-stop warm-to-cool scale (P1 most urgent -> P5 least),
       anchored on the 3 semantic colors that already exist and are
       audited for both themes (P1 = --bad, P3 = --accent, P5 = --good)
       with two new intermediate stops filled in to match their family. */
    --priority-1: #fb7185;  --priority-1-soft: rgba(244, 63, 94, 0.15);   --priority-1-border: rgba(244, 63, 94, 0.40);
    --priority-2: #fb923c;  --priority-2-soft: rgba(249, 115, 22, 0.15);  --priority-2-border: rgba(249, 115, 22, 0.40);
    --priority-3: #fbbf24;  --priority-3-soft: rgba(245, 158, 11, 0.18); --priority-3-border: rgba(245, 158, 11, 0.40);
    --priority-4: #a3e635;  --priority-4-soft: rgba(132, 204, 22, 0.15);  --priority-4-border: rgba(132, 204, 22, 0.40);
    --priority-5: #34d399;  --priority-5-soft: rgba(16, 185, 129, 0.15);  --priority-5-border: rgba(16, 185, 129, 0.40);
}

body {
    margin: 0;
    padding: 0;
    font-family: var(--font-body);
    background-color: var(--color-bg);
    color: var(--color-text-primary);
    overflow: hidden;
    height: var(--app-h);
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-heading);
}

/* Glassmorphism Classes */
.glass-panel {
    background: var(--surf-1);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid var(--color-border);
    box-shadow: var(--shadow-1);
    transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.glass-panel:hover {
    border-color: var(--color-border-active);
}

/* Phase S3 (RELEASE_PLAN.md, 2026-07-31): Nick — "about & data section can
   easily get cut off in safari." Switched the modal shell from Tailwind's
   `max-h-[88vh]` to `calc(var(--app-h) * 0.85)`, reasoning that iOS
   Safari's `vh` unit includes toolbar chrome that isn't actually occupying
   screen space. Phase AB (2026-08-02) then tightened the multiplier to 0.7
   after a real-Safari photo showed it STILL cut off — but Phase AC
   (2026-08-02, same day) proved that whole `--app-h` theory wrong: Nick
   pointed out the landing layer, welcome modal, and tour-finish modal all
   size themselves correctly on his real phone and use nothing but plain
   `max-h-[92vh]`, and the Compare Counties modal — the one other modal in
   this app with the SAME internal shape (fixed header, scrollable middle,
   fixed footer) — uses plain `max-h-[88vh] max-md:max-h-[78vh]`. `--app-h`
   was never actually the fix; it was an unverified theory two phases
   layered defenses onto without ever reproducing the real bug (Playwright/
   desktop Chrome can't — this needed Playwright's WebKit engine, installed
   specifically to check this fix before shipping a third attempt blind).
   Reverted to the same plain-vh approach as every OTHER modal in this file,
   matching Compare's exact values since it's the closest structural match.
   See index.html for the Tailwind classes; `openAboutModal()`'s
   `applyViewportMetrics()` call (app.js) was removed since it no longer
   does anything relevant here. */

/* Custom Scrollbars */
::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}
::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.1);
}
::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.1);
    border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.25);
}
/* Phase AA (2026-08-01): Nick — "the scrolling bar is also not working
   correctly in light mode." It wasn't broken mechanically; it was a
   WHITE-alpha thumb (the rule above), tuned to stand out against this
   app's dark panels, rendering nearly invisible on light mode's now-light
   --surf-1/2 panels — measured via a pixel sample of the rendered rail:
   the thumb differed from its own track by single-digit RGB values, i.e.
   there was nothing to see or reliably grab. Flips the thumb to a DARK
   alpha in light mode so it keeps the same "thumb visibly present, track
   nearly invisible" design intent, just against the opposite backdrop. */
body.theme-light ::-webkit-scrollbar-track {
    background: rgba(15, 23, 42, 0.05);
}
body.theme-light ::-webkit-scrollbar-thumb {
    background: rgba(15, 23, 42, 0.25);
}
body.theme-light ::-webkit-scrollbar-thumb:hover {
    background: rgba(15, 23, 42, 0.42);
}

/* Custom Range Slider Styles */
input[type="range"].custom-slider {
    -webkit-appearance: none;
    width: 100%;
    background: transparent;
    cursor: pointer;
}

input[type="range"].custom-slider:focus {
    outline: none;
}

/* Track styles */
input[type="range"].custom-slider::-webkit-slider-runnable-track {
    width: 100%;
    height: 6px;
    background: var(--bdr-2);
    border-radius: 9999px;
    border: none;
    transition: background 0.2s ease;
}

input[type="range"].custom-slider:hover::-webkit-slider-runnable-track {
    background: var(--txt-4);
}

/* Thumb styles. Phase S4 (RELEASE_PLAN.md, 2026-07-31): Nick — the mobile
   slider knob's grab area should be "much larger, as long as its also
   transparent." The PAINTED disc must stay 16x16 (Nick did not ask for a
   visually bigger knob, and a bigger disc would look wrong against the same
   6px track). A transparent `border` on an appearance:none thumb enlarges
   the ELEMENT'S hit box without enlarging what's painted, IF the width/
   height are the box's TOTAL size and the border is added on top —
   `background-clip: padding-box` then confines the white fill to inside
   the border, so the border ring itself paints nothing.
   ⚠️ First attempt at this got the sizing backwards and shipped a real,
   caught-by-screenshot bug: it set `width/height: 16px` (meant as the
   visible disc size) and ADDED a border on top, but Tailwind's Preflight
   makes `width`/`height` the BORDER-BOX total here, so the border ate INTO
   the 16px instead of surrounding it — at the 9px mobile pad that's
   16 - 2*9 = -2px of actual visible fill, i.e. next to nothing, leaving
   only the box-shadow's dark halo visible. A full-page screenshot (not
   just elementFromPoint measurement) is what caught it. Fixed by making
   width/height `calc(16px + 2 * pad)` — the total box — so the padding-box
   interior is always exactly 16px regardless of box-sizing model.
   margin-top centers the (now larger) total box on the 6px track:
   -(track/2 + total/2) = -(3 + 8 + pad) = -11-pad... re-derived by
   measurement below, not this arithmetic alone — always re-check the
   painted disc with a real screenshot, not just the math, before touching
   this again.
   Nick, after seeing it live: "the slider buttons also look too big."
   Root cause: `box-shadow` always follows the element's BORDER-BOX, which
   is now the full enlarged hit box (34px on mobile), not the 16px visible
   disc — so the same 10px blur that used to halo a 16px circle was
   blooming around a 34px one, reading as a visibly bigger button even
   though the painted disc itself never changed size. `filter: drop-
   shadow()` instead of `box-shadow` respects the element's actual alpha
   channel — since the border is `solid transparent`, drop-shadow casts
   its shadow around only the opaque 16px disc, exactly like before pad
   was added, regardless of how large the invisible hit box grows. */
input[type="range"].custom-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    height: calc(16px + 2 * var(--slider-thumb-pad, 0px));
    width: calc(16px + 2 * var(--slider-thumb-pad, 0px));
    border-radius: 50%;
    border: var(--slider-thumb-pad, 0px) solid transparent;
    /* Phase BY (2026-09-24): no filter. Nick saw dark rectangles around the
       thumb and a yellow haze while dragging on iPhone — Safari paints a
       filter on a range thumb over its whole (padded, square) filter
       region. The disc and a 1px edge ring are drawn by the background
       itself, sized in px from the centre, so they stay exactly 16px
       however big the transparent hit border gets. */
    background: var(--thumb-bg, radial-gradient(circle, #ffffff 0 6.7px, var(--thumb-ring) 7.3px 7.7px, transparent 8.3px));
    background-clip: border-box;
    background-origin: border-box;
    background-repeat: no-repeat;
    margin-top: calc(-5px - var(--slider-thumb-pad, 0px)); /* Centers thumb on the track */
    transition: transform 0.1s ease;
}

input[type="range"].custom-slider:active::-webkit-slider-thumb {
    transform: scale(1.25);
    --thumb-bg: radial-gradient(circle, var(--accent-fill) 0 6.7px, var(--thumb-ring) 7.3px 7.7px, transparent 8.3px);
}

input[type="range"].custom-slider::-moz-range-track {
    width: 100%;
    height: 6px;
    background: var(--bdr-2);
    border-radius: 9999px;
}

/* Firefox mirror of the webkit thumb rule above — same technique (a
   transparent border grows the hit box, background-clip keeps the painted
   disc at 16px). Not empirically verified in this session (only Chrome is
   testable in this sandbox — see project memory on the Playwright/Chromium
   gotcha), but Firefox already auto-centers `-moz-range-thumb` on its
   track, so unlike webkit this needs no margin-top companion change. */
input[type="range"].custom-slider::-moz-range-thumb {
    height: calc(16px + 2 * var(--slider-thumb-pad, 0px));
    width: calc(16px + 2 * var(--slider-thumb-pad, 0px));
    border: var(--slider-thumb-pad, 0px) solid transparent;
    border-radius: 50%;
    background: var(--thumb-bg, radial-gradient(circle, #ffffff 0 6.7px, var(--thumb-ring) 7.3px 7.7px, transparent 8.3px));
    background-clip: border-box;
    background-origin: border-box;
    background-repeat: no-repeat;
}

input[type="range"].custom-slider:active::-moz-range-thumb {
    transform: scale(1.25);
    --thumb-bg: radial-gradient(circle, var(--accent-fill) 0 6.7px, var(--thumb-ring) 7.3px 7.7px, transparent 8.3px);
}

/* D3 SVG map — dark canvas background */
#map svg {
    display: block;
}

/* County and state SVG paths */
.county-path {
    vector-effect: non-scaling-stroke; /* keeps stroke width constant at all zoom levels */
    cursor: pointer;
    transition: fill 0.25s cubic-bezier(0.4, 0, 0.2, 1), fill-opacity 0.25s ease;
}
.state-path {
    vector-effect: non-scaling-stroke;
}

/* D3 floating tooltip */
#d3-tooltip {
    background: var(--surf-1);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid var(--bdr-2);
    color: var(--txt-2);
    border-radius: 12px;
    box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.7);
    padding: 12px 16px;
    font-family: var(--font-body);
    font-size: 11px;
    line-height: 1.4;
    max-width: 280px;
    min-width: 160px;
}

/* Micro-animations */
@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.animate-fade-in-up {
    animation: fadeInUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

/* Rank tag styling */
.rank-badge {
    background: var(--accent-gradient);
    box-shadow: var(--accent-glow);
}

/* Category Switcher Pill Bar (Pills scrollable at top center) */
#category-switcher-bar {
    width: auto;
    scrollbar-width: none; /* Firefox */
}
#category-switcher-bar::-webkit-scrollbar {
    display: none; /* Safari/Chrome */
}

.category-pill {
    transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.category-pill.pill-active {
    background: rgba(99, 102, 241, 0.25) !important;
    border-color: rgba(168, 85, 247, 0.5) !important;
    box-shadow: 0 0 12px rgba(168, 85, 247, 0.2);
}

/* Left Vertical Tabs (Desktop) */
#left-tabs-bar {
    top: 144px;
    width: 56px;
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Bottom Tab Bar (Mobile) */
#bottom-tabs-bar {
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.5);
    background: rgba(8, 10, 20, 0.95);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}

/* Removed old top-left-actions and top-header-bar gap to allow flush header */

/* Single Unified Responsive Settings Panel */
#settings-panel {
    /* Mobile settings: floating bottom sheet above bottom tabs */
    bottom: 76px;
    left: 8px;
    right: 8px;
    height: auto;
    max-height: 230px;
    border-radius: 16px;
    transform: translateY(140%);
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
    background: rgba(17, 24, 39, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.1);
    pointer-events: none;
}
#settings-panel.open {
    transform: translateY(0) !important;
    pointer-events: auto;
}

/* The 768-1023px middle tier used to reshape #settings-panel here into a
   360px slide-in popout anchored right of the (now deleted) left icon
   rail. Removed 2026-07-29 (RELEASE_PLAN.md Phase F5) — there is no middle
   tier anymore; >=600px goes straight to the persistent rail block below. */

/* Hidden below 600px by default (a bare <div>'s default display is block,
   not none) — the @media (min-width: 600px) block below is what actually
   shows it, matching #mobile-tab-bar's opposite pattern just above. */
#rail-resize-handle { display: none; }

/* Phase AO (2026-09-12): the "explore one metric" browse list — shared,
   unscoped (not inside the >=600px rail media query below), because the
   SAME markup/classes populate both the desktop rail's Presets dropdown
   (#rail-solo-list) and the mobile Presets modal (#mobile-solo-list,
   index.html), and the mobile one needs to render correctly below 600px.
   Full-width rows (not a grid, unlike the preset buttons above them) since
   a metric's label needs to read cleanly at whatever width the containing
   dropdown/modal ends up. */
.rail-solo-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 5px 8px;
    border-radius: 6px;
    font-size: 10px;
    font-weight: 600;
    color: var(--txt-2);
    background: transparent;
    border: 1px solid transparent;
    cursor: pointer;
    text-align: left;
    transition: background 0.12s ease, color 0.12s ease;
}
.rail-solo-row:hover {
    background: var(--surf-2);
    color: var(--txt-1);
}
.rail-solo-row.is-active {
    background: var(--accent-soft);
    border-color: var(--accent-border);
    color: var(--accent);
}
.rail-solo-row-icon {
    font-size: 9px;
    opacity: 0.5;
    flex: none;
    margin-left: 6px;
}
.rail-solo-row.is-active .rail-solo-row-icon { opacity: 1; }
#rail-solo-active, #mobile-solo-active {
    cursor: pointer;
}
#rail-solo-active:hover, #mobile-solo-active:hover { text-decoration: underline; }

/* #rail-preset-dropdown floats directly over the rail's own scrolling
   metric-row text — unlike every other .glass-panel surface, which sits
   over the map or an already-dimmed modal backdrop (bg-slate-950/80 on the
   modal's fixed wrapper). .glass-panel's normal 0.85 alpha let that text
   bleed through as illegible double-exposure (caught live: "Sales Tax" and
   "State Income Tax" rail rows ghosting behind the dropdown's own preset
   grid). Raised to near-opaque here only — every other glass-panel spot
   was already validated at 0.85 (Phase AQ) and doesn't have a text-over-
   text problem, so this isn't reopening that. */
#rail-preset-dropdown {
    background: rgba(30, 32, 38, 0.98);
}
body.theme-light #rail-preset-dropdown {
    background: rgba(231, 233, 236, 0.98);
}

/* ============================================================
   Desktop criteria rail (≥600px) — REVAMP_PLAN.md Phase 2b, rebased from
   1024px to 600px on 2026-07-29 (RELEASE_PLAN.md Phase F5 — see
   tailwind.input.css for why 600). Reshapes the EXISTING #settings-panel (a
   bottom-sheet on phones) into a persistent, always-visible, full-height
   left column showing all 7 categories at once. This is a CSS-only
   restructuring of the same DOM, not a parallel copy — the metric rows and
   the career/lifestyle search widgets have unique IDs (getElementById-based)
   and cannot exist twice in the document. Left-edge floating elements that
   assumed the panel was normally off-screen (hamburger menu, map-summary
   panel) get shifted right to clear the rail. ============================================================ */
@media (min-width: 600px) {
    :root {
        /* Fluid rail width (RELEASE_PLAN.md Phase F5c, 2026-07-29; narrowed
           to an ARBITRARY 20-25% in Phase I2, then corrected to a MEASURED
           width in Phase J1, both 2026-07-30). I2's clamp(216px, 30vw,
           260px) was Nick's estimate, not a measured requirement — using it
           revealed real clipping: the widest value string ("Under
           $1,000/mo", avg_rent) needs ~81px against a 48px column. The
           first J1 pass (clamp(290px, 24vw, 330px)) fixed that but widened
           .metric-row-value from 48px to 84px inside the SAME rail width,
           which stole space back from .metric-row-label-text and newly
           truncated 2 of 43 labels ("Min Restaurants per 10k People" 137px,
           "Min Arts & Rec Venues per 10k" 133px) that hadn't truncated
           before — caught by Playwright canvas-measurement + scrollWidth
           checks across all 43 rows, not by inspection. clamp(300px, 24vw,
           340px) fixed that. Phase L (2026-07-30) split the value into a
           fixed prefix span + a narrower 52px number input + a fixed
           suffix span — measured to free up room, but the OPPOSITE showed
           up instead: metrics with a long suffix (restaurants_per_10k's
           "+ per 10k", transit_pct's "%+ Transit") now render that suffix
           as its own un-clamped span rather than inside one fixed-84px box,
           costing MORE total width for those rows than the narrower number
           saved. Re-measured (not assumed) with every metric forced into a
           real target state: the true floor moved to 310px, not down.
           Phase N (2026-07-30) reserved a fixed 53px `min-width` on
           `.metric-value-suffix` so the editable number's right edge stops
           drifting between rows with/without a suffix — that reservation
           was wider than most real suffixes needed, so it cost real label
           width and pushed the floor to 324px.
           Phase P (2026-07-30) removed that reservation entirely (it was
           what stopped the value being flush right AND what truncated the
           labels — see .metric-value-suffix's own comment), tightened the
           number input 52px -> 38px, and stopped .metric-row-label-text
           from growing. Together those hand a lot of width back to the
           label column, so the floor came DOWN for the first time in this
           sequence: re-measured with the same full-row
           scrollWidth-vs-clientWidth sweep in BOTH the untouched and
           every-metric-targeted states, the true floor is 296px (at 294px
           "Min Restaurants per 10k People" truncates in the targeted
           state; at 296px 0/43 truncate in both). Set to 300px for slack.
           Re-run that same two-state sweep before changing this again —
           don't pick a number by feel. Known, accepted consequence: a
           600px viewport leaves the map ~240px — F5c's collapse toggle and
           Phase J3's drag-resize are the escape hatches for that band, not
           a narrower rail everywhere. */
        /* 2026-10-13 (Nick: default "too small", then "best readability", same
           drag limits). Measured in WebKit at 1440x900, rail 300-520px:
           criteria rows gain nothing past ~380px (wider only pushes each
           value away from its label); county-detail tiles stop unwrapping at
           ~400px (84 wrapped labels at 300, 71 at 400, 69 at 520); builder
           text runs ~58 chars/line at 400px (45-75 is the readable band).
           So 400px, easing to 28vw on small laptops so the map keeps room.
           Drag range is app.js RAIL_W_MIN / railWMax(), untouched. */
        --rail-w: clamp(340px, 28vw, 400px);
        /* How much horizontal space the rail takes FROM the map. Separate
           from --rail-w so collapsing (body.rail-hidden) can zero this
           while the panel keeps its width and slides fully off-screen. */
        --rail-offset: var(--rail-w);
        --rail-top: 85px;          /* 45px header + 40px switcher */
        --rail-switcher-top: 45px;
        /* Widest right-side floating control (40px) + its 12px offset + a
           small gap. Subtracted from the map so the D3 projection centers
           in genuinely visible space, not under the buttons. */
        --map-right-gutter: 60px;
        --safe-l: env(safe-area-inset-left, 0px);
        --safe-r: env(safe-area-inset-right, 0px);
    }
    body.rail-hidden {
        --rail-offset: 0px;
    }

    /* Shared strip (index.html, sibling of both panels below) — always on
       top, houses the Criteria/Standings/County switcher + Reset/Share.
       #top-header-bar is a fixed, full-width bar at z-index 1030 that
       would otherwise paint over anything sitting at top:0, so this sits
       below it; #settings-panel and #county-details-drawer both then sit
       below THIS (85px = 45px nav bar + 40px switcher). */
    #rail-view-switcher {
        position: fixed;
        left: var(--safe-l);
        top: var(--rail-switcher-top);
        width: var(--rail-w);
        height: 40px;
        z-index: 1011;
        background: rgba(15, 23, 42, 0.9);
        backdrop-filter: blur(16px);
        border-bottom: 1px solid rgba(255, 255, 255, 0.08);
        border-right: 1px solid rgba(255, 255, 255, 0.08);
        transition: left 0.25s ease, width 0.25s ease;
    }

    /* Rail collapse/reveal (Phase F5c) — only shown 600-1023px, where the
       fluid rail genuinely competes with the map for width; above 1024px
       --rail-w clamps to its original fixed 340px and there's no reason to
       hide it. Two separate elements (never both visible at once) rather
       than one repositioning button — see index.html for why. */
    #rail-collapse-btn {
        display: none;
        align-items: center;
        justify-content: center;
        width: 24px;
        height: 24px;
        border-radius: 6px;
        color: var(--txt-3);
        background: transparent;
        border: 1px solid transparent;
        cursor: pointer;
        transition: all 0.12s ease;
        font-size: 10px;
    }
    #rail-collapse-btn:hover { color: var(--txt-1); background: var(--surf-2); }
    #rail-reveal-btn {
        display: none;
        position: fixed;
        left: var(--safe-l);
        top: var(--rail-top);
        z-index: 1011;
        align-items: center;
        justify-content: center;
        width: 28px;
        height: 44px;
        border-radius: 0 8px 8px 0;
        color: var(--txt-3);
        background: var(--surf-2);
        backdrop-filter: blur(16px);
        border: 1px solid var(--bdr-1);
        border-left: none;
        cursor: pointer;
        transition: color 0.12s ease, background 0.12s ease;
    }
    #rail-reveal-btn:hover { color: var(--txt-1); background: var(--surf-3); }
    body.rail-hidden #rail-reveal-btn { display: flex; }

    /* Drag-resize handle (RELEASE_PLAN.md Phase J3, 2026-07-30; resting
       visibility added Phase K1; restyled to match the mobile grabber
       Phase M2; hit box relocated Phase N, all 2026-07-30). The rail's
       right edge (seam) sits at --rail-offset + --safe-l (same formula
       #map's left uses below). J3/K1 centred the 14px HIT AREA on that
       seam (-7px offset) — fine for a visual line straddling the border,
       but Phase N found this ALSO overlapped the panel's own native
       scrollbar: `document.elementFromPoint()` confirmed clicking the
       scrollbar's track resolved to this handle, not the scrollbar,
       meaning a mouse-only user (no wheel) could not click-drag it at
       all. M2 had already moved the visible PILL off the seam into the
       map gutter, clear of the border/scrollbar; the hit box just never
       followed it. Fixed by starting the hit box AT the seam instead of
       straddling it (`left` drops the `-7px`), so its full 14px width
       sits entirely in the map — measured zero overlap with the
       scrollbar (which Phase N's own `#panel-scroll-area` margin also
       moved further left) instead of the previous 2px near-miss. The
       pill centres naturally within this relocated box via
       `justify-content: center` (no `translateX` needed anymore — the
       box itself now starts where the pill used to be shifted to). */
    /* Phase Y (2026-07-31): this hit-target had NO background of its own —
       only the ::after grab-pill painted anything — so the map's own
       `--surf-0` ocean color showed through the transparent 14px gap
       behind it for the full column height. Invisible in dark mode
       (dark-on-dark); in light mode it read as a vivid light-blue stripe
       running the whole screen, which Nick saw as "the scroll bar... takes
       up the entire vertical screenspace" (it isn't a scrollbar and never
       changed size — the gap was always there). Giving it an opaque
       `var(--surf-1)` makes it read as rail chrome instead of a window
       onto the map.
       Phase AB (2026-08-02): Nick, from a photo — a flat opaque fill across
       the full 14px reads as its own hard-edged rectangle sitting between
       the (now white, since Phase AA) cards and the ocean, especially where
       it meets the map: a dead-straight grey→blue seam the full height of
       the screen ("the pull tab area is a full box... borders not lining
       up"). The vertical position was never wrong (measured: starts exactly
       at --rail-top, matching #settings-panel's own top, no header overlap)
       — it's specifically the hard RIGHT edge, facing open map, that reads
       as a box. First attempt faded only the outer 40% — still left a ~27px
       run of flat, unfaded grey (the card's own right margin PLUS most of
       this handle) before any softening kicked in, so the "box" was barely
       less obvious; Nick confirmed still visible/urgent. Widened to a fade
       across the handle's FULL width instead — opaque at its left edge
       (touching the panel's own already-grey margin, so still no seam
       there) down to fully transparent at its right edge (touching the
       map). This does not resurrect the Phase Y bug above: that was a
       constantly-transparent 14px doing NOTHING to blend, at a time when
       the panel itself was much paler and closer to the ocean's own color;
       here the fade is gradual and anchored to a panel color already
       established as visually distinct on purpose (Phase AA), so it reads
       as a soft vignette at the rail's edge rather than a reappearing gap. */
    #rail-resize-handle {
        display: flex;
        align-items: center;
        justify-content: center;
        position: fixed;
        left: calc(var(--rail-offset) + var(--safe-l));
        top: var(--rail-top);
        bottom: 0;
        width: 14px;
        cursor: col-resize;
        z-index: 1012;
        touch-action: none;
        transition: left 0.25s ease;
        background: linear-gradient(to right, var(--surf-1), transparent);
    }
    #rail-resize-handle::after {
        content: '';
        width: 4px;
        height: 36px;
        border-radius: 2px;
        background: var(--bdr-2);
        /* Nick, 2026-08-01: "a little border around the sidebar pulltab
           only, to make it look like a peninsula off the main sidebar
           panel" — this pill (Nick's own "pull tab" from the Phase AB
           comment above) is the desktop-width pulltab; the reveal chevron
           further up only exists 600-1023px. A visible ring gives the
           pill its own defined edge against the fade-to-transparent
           gradient it sits in, instead of just being a flat tinted bar
           blending into that gradient. box-shadow (not a wider border)
           for the "off the panel" lift, so the 4px width — already tuned
           for the resize hit target — doesn't change. */
        border: 1px solid var(--txt-3);
        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
        transition: background 0.12s ease, border-color 0.12s ease;
    }
    #rail-resize-handle:hover::after,
    #rail-resize-handle:focus-visible::after,
    #rail-resize-handle.dragging::after {
        background: var(--accent-fill);
        border-color: var(--accent-fill);
    }
    #rail-resize-handle:focus-visible { outline: none; }
    body.rail-hidden #rail-resize-handle { display: none; }

    /* Phase CL (2026-09-24): heights here use --app-h (the LIVE visible
       height, set from visualViewport by applyViewportMetrics()), not 100vh.
       On Android Chrome 100vh is the height with the address bar hidden, so
       an unfolded foldable (≥600 px → this layout) lost its bottom ~56 px
       under the address bar. */
    #settings-panel {
        left: var(--safe-l);
        top: var(--rail-top);
        bottom: 0;
        width: var(--rail-w);
        min-width: var(--rail-w);
        height: calc(var(--app-h) - var(--rail-top));
        max-height: none;
        border-radius: 0;
        border-right: 1px solid rgba(255, 255, 255, 0.08);
        transform: none !important;
        pointer-events: auto;
        transition: left 0.25s ease, width 0.25s ease;
    }
    body.rail-hidden #settings-panel {
        transform: translateX(calc(-100% - var(--safe-l))) !important;
    }

    /* Phase N (RELEASE_PLAN.md, 2026-07-30): Nick: the scrollbar sits too
       close to the panel border, and — worse, a real bug, not a cosmetic
       one — a mouse-only user (no wheel, no trackpad) genuinely can't
       click-drag it. Root cause: `document.elementFromPoint()` confirmed
       #rail-resize-handle's 14px hit area (unchanged since Phase J3,
       centred on the seam) has physically overlapped the native
       scrollbar's clickable region since 2026-07-29 — K1/M2's restyling
       only ever moved the drawn pill, never the hit box underneath it.
       margin (not padding) is load-bearing: padding only moves *content*
       away from an already-flush scrollbar, because the native scrollbar
       always renders at the scroll container's own border-box edge —
       only shrinking that box's own width moves the scrollbar itself
       inward, which is what actually creates visible breathing room from
       the border AND clears the resize handle's hit area (verified via
       elementFromPoint() after this change, not assumed).
       Phase O (2026-07-30): that margin-right, on its own, made every row's
       CONTENT area 8px narrower on the right than the left — Nick: the
       value looked "somewhat central" instead of flush right. Traced the
       full ancestor chain (#panel-scroll-area's own p-4 padding + this
       margin + the category card's own border+padding) and measured a real
       26px-left vs 34px-right asymmetry, entirely attributable to this
       one 8px margin having nothing to offset it. `padding-right: 8px`
       below (down from p-4's 16px) restores exact left/right symmetry —
       margin moves the scroll AREA's own box (where the scrollbar renders,
       unaffected by this change), padding moves the CONTENT inside that
       box, so reducing padding-right by the same 8px the margin took
       gives every row back the width it lost, without reopening the
       scrollbar-clearance bug this rule was written to fix. */
    #panel-scroll-area {
        margin-right: 8px;
        padding-right: 8px;
    }

    /* County/Standings views (REVAMP_PLAN.md Phase 2c) — #county-details-
       drawer is a separate DOM subtree (unique-ID content, can't be merged
       with #settings-panel) twinned to the exact same screen rect and
       shown instead of it via switchRailView(); see the body.rail-showing-
       drawer rule below. Selectors prefixed with `body` (not just
       `#county-details-drawer`) to outrank the unconditional base rule
       further down the file (style.css ~512, `height: min(50vh,344px)
       !important`) — equal-specificity `!important` declarations are
       decided by source order, and that base rule comes later, so a
       same-specificity override here would silently lose regardless of
       which one looks more "correct". Same bug class as the 2b.1 collapse
       fix; adding `body` gives an extra type-selector of specificity. */
    body #county-details-drawer {
        left: var(--safe-l);
        right: auto !important;
        top: var(--rail-top);
        bottom: 0 !important;
        width: var(--rail-w) !important;
        min-width: var(--rail-w);
        height: calc(var(--app-h) - var(--rail-top)) !important;
        max-height: none !important;
        border-radius: 0;
        border-right: 1px solid rgba(255, 255, 255, 0.08);
        transform: none !important;
        pointer-events: auto;
        transition: left 0.25s ease, width 0.25s ease;
    }
    body.desktop-panel-closed #county-details-drawer { transform: none !important; }
    body.rail-hidden #county-details-drawer {
        transform: translateX(calc(-100% - var(--safe-l))) !important;
    }
    body #county-details-drawer.drawer-expanded {
        top: var(--rail-top) !important;
        bottom: 0 !important;
        height: calc(var(--app-h) - var(--rail-top)) !important;
    }
    /* Only one of the two panels is shown at a time. */
    body.rail-showing-drawer #settings-panel { display: none !important; }
    body:not(.rail-showing-drawer) #county-details-drawer { display: none !important; }
    /* The drawer's own tab bar (Standings/Metrics/Summary buttons, expand,
       close) is superseded by the shared switcher strip — it's the only
       child providing those, so hiding it doesn't lose anything else. */
    #desktop-tab-bar { display: none !important; }

    /* County view header decluttered (Nick's feedback, 2026-07-19) — the
       Pop/Income/Home stat row duplicated what the Active Priorities
       cards immediately below already show, and the whole header felt
       tight/cluttered stacked into the rail's 340px width. Hide the
       redundant row and tighten vertical spacing slightly. */
    #detail-quick-stats { display: none !important; }
    #county-details-view > div:first-child { padding-top: 8px; padding-bottom: 8px; }
    #county-details-view > div:first-child > div { margin-top: 4px; }
    #county-details-view > div:first-child > div:first-child { margin-top: 0; }

    /* Single-category header only makes sense when one category is
       "active" at a time (<600px); each section has its own header here. */
    #panel-header-shared { display: none; }
    #panel-disabled-overlay { display: none !important; }

    .rail-header-btn {
        display: inline-flex;
        align-items: center;
        gap: 4px;
        font-size: 9px;
        font-weight: 700;
        color: var(--txt-3);
        background: var(--surf-3);
        border: 1px solid var(--bdr-1);
        border-radius: 6px;
        padding: 4px 8px;
        cursor: pointer;
        transition: all 0.12s ease;
    }
    /* Reset moved from the rail-view-switcher header into the Criteria
       panel itself (2026-07-19) — it only applies here, not to Standings/
       County. "Guide me" (Phase 3c) joined it as a second, equal-width
       button; Toggle All (Phase I3, 2026-07-30) joined as a third —
       reuses .rail-header-btn's look, same as the other two. */
    .rail-reset-inline {
        flex: 1;
        justify-content: center;
    }
    .rail-header-btn:hover {
        color: var(--txt-1);
        background: var(--surf-2);
    }


    /* Phase I3 (RELEASE_PLAN.md); moved inside #panel-scroll-area and
       reduced to Reset/Toggle All (Guide moved to #rail-view-switcher)
       Nick's request 2026-08-01 — see index.html for the full reasoning.
       Sticky (not a flex sibling outside the scroll area, as it was
       before) so it still stays put while sections scroll under it;
       padding/position come from index.html's own utility classes now, not
       here, since -top-4/-mx-4 needs to match #panel-scroll-area's p-4
       exactly. */
    .rail-toolbar {
        display: flex;
        gap: 6px;
        align-items: center;
        /* Nick, 2026-08-01: "the reset and all on buttons seem to float and
           have a transparent background... make it stick to just below the
           header in a way where it looks professionally designed." The
           previous `var(--surf-2)` (0.60-alpha in dark mode) plus blur
           still read as genuinely translucent/frosted, not solid — a
           blurred smear of whatever category row was currently scrolled
           underneath was still visible through it, which is what "floats"
           and "transparent" describe. #rail-view-switcher (the header
           immediately above this bar) solved the identical "opaque sticky
           chrome over scrolling map/content" problem already — copying its
           exact treatment (same near-opaque hardcoded navy + same 16px
           blur, plus the same light-mode override to a genuinely solid hex)
           makes this bar read as directly, seamlessly attached to the
           header instead of a separate floating chip. */
        background: rgba(15, 23, 42, 0.92);
        backdrop-filter: blur(16px);
        -webkit-backdrop-filter: blur(16px);
        border-bottom: 1px solid var(--bdr-1);
    }
    body.theme-light .rail-toolbar {
        background: var(--surf-2);
    }

    /* Criteria/Standings/County segmented control in #rail-view-switcher. */
    .rail-view-btn {
        font-size: 9px;
        font-weight: 800;
        letter-spacing: 0.02em;
        text-transform: uppercase;
        color: var(--txt-3);
        background: transparent;
        border: none;
        border-radius: 5px;
        padding: 5px 8px;
        cursor: pointer;
        transition: all 0.12s ease;
    }
    .rail-view-btn:hover { color: var(--txt-1); background: rgba(255, 255, 255, 0.05); }
    /* Phase AB (2026-08-02): Nick — "the amber highlight for the active tab
       looks off... I think it would look better to just do something with
       the active tab being lighter color and the non active tabs looking
       darker from shadowing." An amber-wash fill read as a muddy brown
       patch against the dark switcher bar rather than a clean highlight —
       switched from a COLOR signal to a LIGHTNESS one: active is a plain
       lighter wash (an elevated "card" popped off the bar), inactive stays
       transparent (recedes into it). No accent color left on this control
       at all, consistent with "keep the UI subtle so focus stays on the
       data." Light-mode override further down goes solid white, matching
       the same white-card-on-grey-shell language the category cards use. */
    .rail-view-btn.active {
        color: var(--txt-1);
        background: rgba(255, 255, 255, 0.08);
    }

    /* selectCategoryTab() still toggles .hidden per-category below
       600px; every section is shown here regardless of which (if any)
       is "active". */
    #settings-panel .rail-section-body.hidden { display: block !important; }

    /* The whole header row is the collapse target now (2b.1 — a bare
       17x17.5px chevron was too small/undiscoverable a hit target), so it
       needs its own affordance: pointer cursor, a hover tint, and a
       focus ring since it's `role="button" tabindex="0"` for keyboard
       users. The weight-select and enable-toggle stopPropagation() their
       clicks (index.html) so interacting with them doesn't also collapse
       the section. */
    /* Card treatment (Nick's feedback, 2026-07-18 — sections "blend into
       each other quite well", wanted better differentiation). Was a plain
       border-top divider line; now each section is its own bordered/
       tinted card, header seamlessly joined to its body when expanded
       (:has() — already relied on elsewhere in this file, e.g. the
       drawer-expanded map-legend rule) and a standalone rounded card when
       collapsed or the body is empty. Deliberately its own commit — easy
       to revert to the plain-divider look in one step if this doesn't
       land well. */
    .rail-section-header {
        display: flex !important;
        align-items: center;
        gap: 8px;
        padding: 8px 8px 6px;
        margin-top: 8px;
        background: rgba(255, 255, 255, 0.03);
        border: 1px solid rgba(255, 255, 255, 0.07);
        border-radius: 10px;
        cursor: pointer;
        transition: background 0.12s ease;
    }
    .rail-section-header:first-of-type { margin-top: 0; }
    .rail-section-header:has(+ .rail-section-body:not(.rail-collapsed)) {
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        border-bottom: none;
        padding-bottom: 8px;
        /* The actual seam Nick saw: #settings-panel's scrollable container
           uses Tailwind's `space-y-4`, which was putting a 16px gap
           between every direct child — including a header and its OWN
           body, not just between one card and the next (verified via
           computed style: margin-bottom measured 16px here). Zero it only
           in this specific header-directly-joined-to-expanded-body case,
           leaving the normal 16px gap intact between a body and the next
           category's header. */
        margin-bottom: 0 !important;
    }
    .rail-section-header:hover { background: rgba(255, 255, 255, 0.055); }
    .rail-section-header:focus-visible {
        outline: 2px solid var(--accent-fill);
        outline-offset: -2px;
    }
    .rail-section-icon {
        font-size: 12px;
        width: 16px;
        text-align: center;
        flex-shrink: 0;
    }
    /* Never abbreviates or truncates (Nick's feedback, 2026-07-17) — wraps
       to a second line instead of ellipsis; CATEGORY_NAMES (app.js) also
       had its own baked-in abbreviations ("Climate & Env.") fixed to match. */
    .rail-section-name {
        flex: 1;
        min-width: 0;
        font-size: 10px;
        font-weight: 800;
        letter-spacing: 0.02em;
        text-transform: uppercase;
        color: var(--txt-1);
        white-space: normal;
        line-height: 1.25;
    }
    /* Disabled category (2b.1) — greyed name is now the only signal, no
       "OFF" pill; the section is also auto-collapsed (syncRailSections()). */
    .rail-section-name.rail-name-disabled { color: var(--txt-4); }
    .rail-weight-select {
        font-size: 9px;
        font-weight: 700;
        background: var(--surf-2);
        border: 1px solid var(--bdr-1);
        border-radius: 4px;
        color: var(--txt-2);
        padding: 2px 3px;
        cursor: pointer;
        flex-shrink: 0;
    }
    /* Enlarged from 28x15 (2b.1 — Nick found the original hard to hit/see). */
    .rail-toggle.category-toggle { width: 34px; height: 18px; flex-shrink: 0; }
    .rail-toggle .toggle-dot { width: 14px; height: 14px; }
    .rail-toggle.active .toggle-dot { transform: translateX(16px); }


    /* Purely decorative now — the header row itself is the click/keyboard
       target (see .rail-section-header above); this is a <span>, not a
       <button>, so it doesn't duplicate the row's own tab stop. */
    .rail-chevron {
        color: var(--txt-4);
        font-size: 9px;
        padding: 2px 4px;
        flex-shrink: 0;
    }
    .rail-chevron i { display: inline-block; transition: transform 0.2s ease; }
    .rail-chevron.collapsed i { transform: rotate(-90deg); }

    /* Card body — completes the card started by .rail-section-header
       above; only when expanded (collapsed sections are display:none, see
       the rail-collapsed rule below, so a standalone header already reads
       as its own self-contained rounded card with nothing to connect to). */
    .rail-section-body:not(.rail-collapsed) {
        background: rgba(255, 255, 255, 0.03);
        border: 1px solid rgba(255, 255, 255, 0.07);
        border-top: none;
        border-radius: 0 0 10px 10px;
        padding: 8px 8px 10px;
        margin-bottom: 2px;
    }

    /* Real bug found live (2b.1): all 6 non-first-loaded categories still
       carry the leftover Tailwind `hidden` class from the pre-rail single-
       category-drawer logic (only the initially-active tab's panel lacked
       it). The `#settings-panel .rail-section-body.hidden { display:block
       !important }` rule above (line ~316) has an ID selector and always
       won over this one regardless of source order, so collapsing any
       category other than the first silently did nothing — the class
       toggled but nothing hid. Same fix pattern as the map/header bugs:
       match the ID specificity so source order (this rule comes later)
       decides the tie. */
    #settings-panel .rail-section-body.rail-collapsed { display: none !important; }

    /* Disabled category: dim its rows in place rather than a full
       overlay (there are 7 of these visible at once here, so a
       "Category Inactive" card per section would be noisy) — the rail
       toggle switch is the enable/disable affordance, consistent with
       the plan's spec: rows stay visible, interacting offers nothing. */
    .rail-section-body.rail-category-disabled {
        opacity: 0.35;
        pointer-events: none;
    }

    /* Shift floating elements that assumed the panel was normally
       off-screen so they clear the now-permanent rail.
       Phase AR (2026-09-12): this was a literal 356px (340px rail + 16px),
       stale since Phase F5c made the rail fluid (`--rail-w: clamp(300px,
       24vw, 340px)`, `--rail-offset` dropping to 0 under body.rail-hidden).
       Below 1024px this sat 16-56px too far right of the rail's true edge
       whenever it wasn't at its 340px maximum, and stayed permanently wrong
       once the rail was collapsed. Same var already used by #map's own
       `left` a few rules below — this rule was simply never updated to
       match when that one was introduced. */
    #summary-panel { left: calc(var(--rail-offset) + 16px); }

    /* Right-side control column (Nick's feedback, 2026-07-17): hamburger,
       zoom controls, and the Match legend all share the right:12px column
       (matching #zoom-controls' existing offset) instead of the hamburger
       sitting alone on the left. Stacked with explicit gaps rather than a
       shared flex container — these are three independent fixed elements
       (matches this file's existing convention, e.g. the 356px rail-clear
       offset above) — so a change to any one's height needs the others'
       offsets re-checked; verified live via getBoundingClientRect, not
       just by eye, when this was built. */
    #hamburger-menu-container {
        left: auto !important;
        right: 12px;
        top: calc(4rem + env(safe-area-inset-top, 0px) + 8px) !important;
    }
    /* The dropdown menu opens relative to the button — its base markup
       (`left-0`) assumes the button sits near the LEFT edge (true on
       mobile, where the hamburger stays at left-4) and opens rightward.
       Now that the button is at the right edge on desktop, that same
       `left-0` pushed the 192px-wide menu off the right side of the
       viewport entirely (measured live: right edge at 1580px in a 1440px
       viewport). Open it leftward here instead. */
    #hamburger-dropdown {
        left: auto !important;
        right: 0 !important;
        transform-origin: top right;
    }
    #zoom-controls {
        /* right:16px (not 12px like the hamburger/legend) so its 32px-wide
           buttons' horizontal CENTER lines up with the hamburger's and
           legend's 40px-wide center — all three share one column but
           differ in width, so equal `right` offsets alone would put their
           centers 4px apart (Nick's feedback, 2026-07-18). */
        right: 16px !important;
        top: calc(4rem + env(safe-area-inset-top, 0px) + 76px) !important;
    }

    /* Give the map the remaining width instead of running full-bleed
       under the rail. #map carries Tailwind's `w-full` (width:100%,
       index.html ~167) which wins over the `inset-0`-implied right:0
       stretch once `left` is overridden here — without an explicit
       width override the map box extends past the viewport's right edge. */
    /* Subtracts the right-side control column too (Nick's feedback,
       2026-07-19: map should center within window width MINUS rail width
       MINUS right-button-column width, not just minus the rail). Those
       buttons float above the map (z-index, not layout), so #map's own
       clientWidth — what the D3 projection fits/centers against in both
       initMap() and its resize handler — previously extended the full
       remaining distance to the viewport edge, under the buttons.
       --map-right-gutter (60px) = the widest right-column element (40px) +
       its offset (12px) + a small gap.

       --rail-offset replaces the old literal 340px (Phase F5c, 2026-07-29):
       it tracks --rail-w (fluid, clamp(280px,38vw,340px)) so the map never
       runs under a narrower or wider rail, and it drops to 0 when
       body.rail-hidden is set, letting the map claim the rail's space
       entirely once collapsed. At >=896px --rail-w clamps to exactly
       340px, so this resolves identically to the old fixed values there —
       verify with getBoundingClientRect() before/after any change here. */
    #map {
        left: calc(var(--rail-offset) + var(--safe-l));
        width: calc(100% - var(--rail-offset) - var(--safe-l) - var(--safe-r) - var(--map-right-gutter));
        transition: left 0.25s ease, width 0.25s ease;
    }
}

/* #rail-collapse-btn only makes sense 600-1023px — above 1024px --rail-w
   clamps to its original fixed 340px (see the block above) and there's no
   width pressure to relieve. A single combined-condition query rather than
   nesting this inside the min-width:600px block above: vanilla CSS (unlike
   the CSS Nesting spec) doesn't support @media inside @media, and this
   file is served as a plain stylesheet, not run through any preprocessor
   that would flatten it. */
@media (min-width: 600px) and (max-width: 1023.98px) {
    #rail-collapse-btn { display: flex; }
    body.rail-hidden #rail-collapse-btn { display: none; }
}

/* Collapsible County Details Drawer — desktop */
#county-details-drawer {
    transform: translateX(0);
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
    bottom: 28px !important;
    right: 16px !important;
    width: 300px !important;
    height: min(50vh, 344px) !important;
    max-height: none !important;
    min-height: 80px;
}
/* On desktop, panel is hidden via desktop-panel-closed body class (not .open) */
body.desktop-panel-closed #county-details-drawer {
    transform: translateX(340px) !important;
}
#county-details-drawer.open {
    transform: translateX(0) !important;
}
#county-details-drawer.drawer-expanded {
    top: calc(4rem + 8px + env(safe-area-inset-top, 0px)) !important;
    bottom: 28px !important;
    height: auto !important;
    max-height: none !important;
}

/* Filtered-out section (Nick's feedback, 2026-07-17) — sibling of the main
   standings list (not nested inside its scroll container), so it stays
   pinned at the bottom of the Standings view as just its header row when
   collapsed. Expanded, it takes an equal flex-1 share of #leaderboard-view's
   remaining height alongside the main list — i.e. "half the panel" without
   a hand-picked pixel/percent value that would drift if surrounding chrome
   changes — with its own scrollable body and pagination. */
/* :not(.hidden) so this doesn't fight Tailwind's .hidden{display:none} —
   #filtered-section is an ID selector and would otherwise always win
   regardless of source order, breaking the "no filtered counties at all"
   hidden state (toggled in JS by updateLeaderboard()). */
#filtered-section:not(.hidden) {
    display: flex;
    flex-direction: column;
    min-height: 0;
}
#filtered-section.filtered-section-expanded {
    flex: 1 1 0;
}
#filtered-container {
    flex: 1;
    min-height: 0;
}

/* Desktop: show collapsed tab when panel is closed */
@media (min-width: 600px) {
    body.desktop-panel-closed #desktop-right-tab {
        display: block !important;
    }
}

/* Desktop: inner views must fill the fixed-height drawer */
@media (min-width: 600px) {
    #leaderboard-view,
    #county-details-view,
    #desktop-summary-view {
        flex: 1;
        min-height: 0;
    }
}

/* Desktop: hide per-view expand/close — they live in the unified tab bar header */
@media (min-width: 600px) {
    #leaderboard-view [onclick="toggleDrawerExpand()"],
    #leaderboard-view [onclick="toggleCountyDetails(false)"],
    #county-details-view [onclick="toggleDrawerExpand()"] {
        display: none !important;
    }
}

/* Both: hide the Back button — tab system handles navigation */
#county-details-view [onclick="showLeaderboardView()"] {
    display: none !important;
}

/* Desktop: hide the legacy summary panel (content lives in unified drawer) */
@media (min-width: 600px) {
    #summary-panel {
        display: none !important;
    }
}

/* Desktop summary view inside the unified drawer */
@media (min-width: 600px) {
    #desktop-summary-view:not(.hidden) {
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
    #desktop-summary-view .flex-1 {
        overflow-y: auto;
    }
}

/* Mobile overrides for county-details-drawer are handled by the v17 block below */

/* Premium CSS-driven Custom Tooltips */
[data-tooltip] {
    position: relative;
    cursor: help;
}

[data-tooltip]:focus {
    outline: none;
}

[data-tooltip]::before {
    content: attr(data-tooltip);
    position: absolute;
    bottom: 145%;
    left: -20px;
    transform: translateY(8px);
    background: var(--surf-1);
    color: var(--txt-2);
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 10px;
    line-height: 1.4;
    font-weight: 500;
    width: 220px;
    text-align: left;
    border: 1px solid var(--bdr-2);
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.6);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: transform 0.15s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.15s ease, visibility 0.15s ease;
    z-index: 9999;
}

[data-tooltip]::after {
    content: '';
    position: absolute;
    bottom: 115%;
    left: 4px;
    transform: translateY(8px);
    border-width: 6px 6px 0;
    border-style: solid;
    border-color: var(--surf-1) transparent transparent;
    display: block;
    width: 0;
    z-index: 9999;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: transform 0.15s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.15s ease, visibility 0.15s ease;
}

[data-tooltip]:hover::before,
[data-tooltip]:hover::after,
[data-tooltip]:focus::before,
[data-tooltip]:focus::after,
[data-tooltip]:active::before,
[data-tooltip]:active::after,
[data-tooltip].active-tooltip::before,
[data-tooltip].active-tooltip::after {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* Compact Input Fields for Sub-Metrics */
.sub-metric-input {
    width: 100%;
    background: var(--surf-3);
    border: 1px solid var(--bdr-1);
    border-radius: 6px;
    padding: 4px 8px;
    color: var(--txt-1);
    font-size: 10px;
    font-weight: 600;
    transition: all 0.2s ease;
    text-align: right;
}
select.sub-metric-input {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 6px center;
    background-size: 8px;
    padding-right: 18px;
    text-align-last: right;
    cursor: pointer;
}
select.sub-metric-input option {
    background-color: var(--surf-1);
    color: var(--txt-1);
}
.sub-metric-input:focus {
    outline: none;
    border-color: var(--accent-fill);
    background: var(--surf-2);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.sub-metric-input::placeholder {
    color: var(--txt-4);
    font-weight: 400;
}
/* Hide details marker */
details summary::-webkit-details-marker {
    display: none;
}
details summary {
    list-style: none;
}


/* Category Toggle Switch inside Panel */
.category-toggle {
    width: 34px;
    height: 18px;
    border-radius: 99px;
    background-color: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.15);
    position: relative;
    cursor: pointer;
    transition: background-color 0.25s ease, border-color 0.25s ease;
    flex-shrink: 0;
}
.category-toggle.active {
    background-color: var(--accent-fill);
    border-color: var(--accent-border);
}
.category-toggle .toggle-dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background-color: #ffffff;
    position: absolute;
    top: 2px;
    left: 2px;
    transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.category-toggle.active .toggle-dot {
    transform: translateX(16px);
}

/* Disabled Category Overlay inside Drawer Panel - display controlled by JS inline style */
.panel-disabled-overlay {
    position: absolute;
    inset: 0;
    background: rgba(8, 10, 20, 0.7);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    /* display is set by JS: 'flex' when visible, 'none' when hidden */
    align-items: center;
    justify-content: center;
    z-index: 50;
    transition: opacity 0.25s ease;
    border-radius: 12px;
}

/* Tab button state styling */
.tab-button-disabled {
    opacity: 0.25 !important;
    filter: grayscale(100%) !important;
}
.tab-button-enabled {
    opacity: 1 !important;
    filter: none !important;
}

/* Smooth collapsing container for sliders & advanced filters */
.controls-wrapper {
    max-height: 500px;
    opacity: 1;
    overflow: hidden;
    transition: max-height 0.35s cubic-bezier(0.16, 1, 0.3, 1),
                opacity 0.25s ease,
                margin-top 0.35s ease,
                padding 0.35s ease;
}
.controls-collapsed {
    max-height: 0 !important;
    opacity: 0 !important;
    margin-top: 0 !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    pointer-events: none;
    overflow: hidden;
}


/* === Responsive Match Score Legend === */
#map-legend {
    position: absolute;
    z-index: 20;
    bottom: 96px;
    right: 12px;
    left: auto;
    top: auto;
    transform: none;
    padding: 8px 6px;
    width: 36px;
    border-radius: 12px;
    transition: bottom 0.4s cubic-bezier(0.16, 1, 0.3, 1), transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
#map-legend .legend-title {
    display: none;
}
#map-legend .legend-bar {
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    gap: 4px;
}
#map-legend .legend-gradient {
    width: 8px;
    height: 100px;
    flex: none;
    border-radius: 9999px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    --grad-stops: #1e1b4b 0%, #4c1d95 30%, #c026d3 60%, #ec4899 80%, #fbbf24 100%;
    background: linear-gradient(to top, var(--grad-stops));
}
#map-legend .legend-label {
    font-size: 8px;
}

/* The 768px "horizontal pill at bottom-center" legend layout used to live
   here. Removed 2026-07-29 (RELEASE_PLAN.md Phase F5) — now that both
   blocks fire at the same 600px breakpoint, the vertical right-column
   block below fully overrides every property this one set (position,
   left/right/top/bottom, transform, width, padding, border-radius on
   #map-legend; flex-direction/align-items/gap on .legend-bar; width/
   height/background/border-radius on .legend-gradient; display/font-size/
   white-space/border-right/padding-right/margin-right on .legend-title),
   so it was fully dead weight, not merely redundant in the harmless sense. */

/* Phase AR (2026-09-12): this `bottom: 14px !important` rule is removed —
   found and confirmed vestigial while building the overlay-collision audit
   below. Its git history shows the original version was
   `bottom: calc(75vh + 20px) !important`, "lift legend further when
   standings is expanded to 75vh" — from BEFORE Phase F5's redesign moved
   the legend from a horizontal bottom-center pill to the vertical
   right-column strip it is today. That old value made sense: a
   bottom-anchored legend genuinely needed pushing up when a bottom-sheet-
   style drawer grew to 75% of the viewport underneath it. When F5 rewrote
   the base rule to position the legend via `top` in the right column
   instead, this override was mechanically re-numbered to `14px` rather
   than re-derived — a leftover exactly like the "fully dead weight" block
   documented in the comment just above this one, just missed by that
   cleanup pass.
   `#county-details-drawer.drawer-expanded` (style.css, ~L1084) only grows
   the LEFT-side rail drawer taller (top/bottom offsets within the rail
   column) — it never extends into the right-hand column the legend lives
   in, so there is no longer any geometric reason for the legend to move
   when this fires. Confirmed by measurement, not assumption: with this
   rule present, `#map-legend`'s own box (top unchanged, bottom forced to
   `100vh - 14px`) stretched to swallow the ENTIRE remaining right-column
   space below its visible content at every viewport size once the drawer
   was expanded — tools/audit_overlay_collisions.js caught this as the
   legend "overlapping" the Selection Summary panel at every single tested
   size (1024px through 1920px) whenever drawer-expanded was true, which
   the JS clearance fix (app.js's updateOverlayClearance()) could not
   correct by shrinking the summary panel, because the legend's own
   (mostly-empty) box already occupied that space regardless. Deleting the
   rule instead of patching around it: both scripted (Playwright, both
   themes, full viewport matrix) and a manual screenshot at 1366x768 with
   the drawer expanded confirm the legend renders at its normal compact
   size and position, coexisting cleanly with the taller drawer. */

/* Match legend moves into the right-side control column at the rail
   breakpoint (Nick's feedback, 2026-07-17: "further separated from reset
   view... vertically oriented... 0% and 100% visible"). Reuses the same
   vertical column-reverse gradient structure the <600px mobile legend
   already uses (style.css ~785) rather than inventing a second one — just
   repositioned and with the title shown (mobile hides it to save width;
   desktop has room and Nick wants the "Match" label visible). Rebased from
   1024px to 600px 2026-07-29 (RELEASE_PLAN.md Phase F5). */
@media (min-width: 600px) {
    #map-legend {
        position: fixed;
        left: auto;
        right: 12px;
        top: calc(4rem + env(safe-area-inset-top, 0px) + 240px);   /* RV4: +36px for the city-names button above */
        bottom: auto;
        transform: none;
        /* Phase AU (2026-09-21): was a flat 40px. Widened to fit "MEDIAN"
           (the widest single word in the default solo label, "Median Home
           Price") on one line at this uppercase/tracking-widest size —
           40px wrapped it mid-word ("MEDIA"/"N"), unreadable. Anchored via
           `right:12px` (unchanged, still the Phase F5d/Y8 position), so
           growing width only extends left into open map canvas, not
           toward any neighboring fixed overlay (confirmed via
           tools/audit_overlay_collisions.js, still 0 collisions after this
           change). "Match" and short solo labels just get a little
           breathing room they didn't need. */
        width: 56px;
        padding: 10px 6px;
        border-radius: 14px;
        /* Border removed (Nick's feedback, 2026-07-18) — overrides the
           base markup's Tailwind `border border-white/10` classes. */
        border: none;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 6px;
    }
    #map-legend .legend-title {
        display: flex !important;
        font-size: 8px;
        white-space: nowrap;
        border-right: none;
        padding-right: 0;
        margin-right: 0;
    }
    /* Icon removed so "Match" reads as a plain centered label above the
       gradient (Nick's feedback, 2026-07-18). */
    #map-legend .legend-title i { display: none; }
    /* Phase AO (2026-09-12): "Match" (5 chars) always fit this 40px-wide
       card without help, but a solo'd metric's own label (e.g. "Drive Time
       to Int'l Airport") does not — and this card's own `right:12px` fixed
       anchor + explicit `width:40px` are exactly the kind of hard-won,
       already-fought-over positioning (Phase F5d/Y8) not worth touching
       for this. Originally single-line ellipsis truncation + the full
       label in the `title` attribute (set by syncSoloUI() in app.js) as a
       hover tooltip.
       Phase AU (2026-09-21): solo became the DEFAULT state every new
       visitor sees (not an occasional interaction), and its chosen metric
       ("Median Home Price") truncated to the unreadable "ME…" — directly
       undermining AU3's whole point that solo mode is "self-describing
       from first paint." Switched single-line ellipsis for a 2-line wrap
       instead of widening the card (still not reopening F5d/Y8's fight —
       width/position are untouched, only the text's own line-wrapping
       changed; the box has no fixed height so it grows to fit). The `title`
       tooltip stays as a fallback for labels too long even for 2 lines. */
    #map-legend #legend-title-text {
        display: inline-block;
        max-width: 44px;
        white-space: normal;
        overflow-wrap: break-word;
        line-height: 1.2;
        text-align: center;
    }
    #map-legend .legend-bar {
        flex-direction: column-reverse !important;
        align-items: center;
        gap: 4px;
    }
    #map-legend .legend-gradient {
        width: 8px !important;
        height: 135px !important; /* 50% longer than the 90px baseline (Nick's feedback, 2026-07-18) */
        background: linear-gradient(to top, var(--grad-stops)) !important;
        border-radius: 9999px;
    }
    #map-legend .legend-label { font-size: 8px; }
}
@media (min-width: 600px) {
    /* Phase CC: long single-metric titles (profession names). */
    #map-legend.legend-wide { width: 112px; }
    #map-legend.legend-wide #legend-title-text { max-width: 100px; overflow-wrap: normal; hyphens: auto; }
}
/* Phase CC: the chosen-profession chip was cyan-200 on a pale cyan wash in
   light mode — unreadable. */
body.theme-light #occ-selected-title, body.theme-light #occ2-selected-title { color: #155e75 !important; }

/* Short-viewport compaction (RELEASE_PLAN.md Phase F5d, 2026-07-29) — a
   phone in LANDSCAPE (e.g. iPhone 16 at 844x390) is wide enough to get the
   desktop rail (>=600px) but only ~390px tall, and the rail's chrome
   above (--rail-top: 85px = a ~44px header + 40px switcher) is tuned for
   900px-tall windows. Triggered on height, independent of the width seam
   above — a landscape phone and a portrait desktop can both be >=600px
   wide, only one of them is also short.

   --rail-top's 69px below = compacted header (37px: 4px+4px padding at
   0.25rem each, 28px logo content height since the 28px img beats the
   ~20px text line-height, 1px border-bottom) + 32px switcher — confirmed
   via getBoundingClientRect() at 844x390 (Playwright/Chrome, headless=new;
   the bundled Chromium binary is killed at launch — exit 137 — on this
   machine, chromium.launch({channel:'chrome', args:['--headless=new']})
   worked around it): header measured exactly 37px, #settings-panel's y
   landed exactly at 69px. Both match the box-model calc exactly.

   Placement note: this block MUST come after the "Match legend moves into
   the right-side column" block above (not before it, and not merged into
   it) — that block's #map-legend rule sets top/bottom without
   !important, and its selector has equal specificity to this one's, so
   whichever comes LATER in source order wins regardless of which looks
   more "correct". Verified live: placing this block before that one (its
   original position while drafting) silently lost the override —
   #map-legend kept its tall-viewport position at 844x390 with zero
   console error, indistinguishable from success until measured. Same bug
   class as the 2b.1/2c specificity-vs-source-order bugs this codebase has
   hit twice before. */
@media (min-width: 600px) and (max-height: 500px) {
    :root {
        --rail-top: 69px;
        --rail-switcher-top: 37px;
    }
    #top-header-bar {
        padding-top: calc(0.25rem + env(safe-area-inset-top, 0px)) !important;
        padding-bottom: 0.25rem !important;
    }
    #rail-view-switcher { height: 32px; }
    /* The Match legend's 204px top offset (the vertical-strip block just
       above) assumes a tall window and would run off the bottom of a
       ~390px-tall one; anchor it to the bottom instead. */
    #map-legend { top: auto; bottom: 12px; }
    /* Phase Y8 (2026-07-31): bottom-anchoring alone wasn't enough — the
       legend keeps its full 205px height and simply grows UPWARD into the
       zoom controls, which are fixed just below the hamburger. Measured on
       a 932x430 landscape iPhone: legend occupied y 213-418, zoom controls
       y 140-244, a real 32x31px overlap (Nick: "interference with map
       gradient scale and zoom/centering buttons"). The right-hand column
       has ~162px of clear space below the zoom controls here, so the
       gradient bar shrinks to fit rather than the controls moving —
       keeping all three (hamburger / zoom / legend) in their established
       top-to-bottom order. */
    /* Phase AF (2026-08-01): Nick — "the + and - buttons can disappear to
       give the gradient scale more space to fit properly." Opposite trade
       from Y8's: the buttons give way instead of the legend. Pinch/
       double-tap/wheel zoom are unaffected — only these two buttons go;
       #zoom-controls' third button (reset/recenter) has no touch-gesture
       equivalent, so it stays. Removing two of the three 32px buttons (+
       gaps) shrinks #zoom-controls from 140-244 to 140-172, freeing 72px
       above the legend at every height this query matches. Measured the
       new floor at 667x375 (an iPhone SE in landscape — shorter than
       either of Y8's own reference devices, and the shortest phone this
       plan treats as "realistic"): with the buttons hidden, the legend
       can grow to 183px total (126px of that used to be all non-gradient
       chrome: title/padding/labels/gaps) before it would touch the
       now-shorter zoom controls. Set the gradient to 96px, not the full
       113px available, to keep the same kind of safety margin Y8 itself
       used rather than running to the exact limit. Re-verify this number
       if #map-legend's non-gradient chrome (currently ~70px) ever changes
       — it was measured against that value, not derived from it. */
    #map-legend .legend-gradient { height: 96px !important; }
    #zoom-in-btn, #zoom-out-btn { display: none !important; }
    /* RV4: the city-names button sits beside reset here, not under it, so
       the column stays one button tall above the legend. */
    #zoom-controls { flex-direction: row-reverse !important; }
    /* Needs 45vh (style.css) — 175px on a 390px-tall window, more than a
       landscape phone can spare. Same information is one tap away in the
       rail's Criteria/Standings/County switcher. */
    #selection-summary-panel { display: none !important; }
}

/* Selection summary panel (REVAMP_PLAN.md Phase 2c/item-11 rework,
   2026-07-17) — replaces both the retired desktop Summary tab AND the
   flat legend-chip-row first attempt (which had nowhere good to live once
   the Match legend needed the bottom-center spot's replacement, the right
   column, for itself). Sits where the old horizontal legend pill used to
   be. Sectioned by category — icon + name + priority header, then one row
   per active target in that category — rather than a flat chip list, per
   Nick's ask for something table-like and easy to scan at a glance.
   Populated by updateSelectionSummaryPanel() in app.js. Rail breakpoint
   only; mobile keeps its own Summary tab for now (2d). */
#selection-summary-panel {
    position: fixed;
    /* Right-justified with the hamburger/zoom/legend column (Nick's
       feedback, 2026-07-18) — was centered at the old bottom-center legend
       spot; moved once Match itself moved into the right column. */
    left: auto;
    right: 12px;
    bottom: 14px;
    transform: none;
    z-index: 20;
    width: 240px;
    /* Phase AR (RELEASE_PLAN.md, 2026-09-12): a flat 45vh overlaps the
       Match legend below ~867px of viewport height — the legend occupies a
       fixed pixel band (top: 4rem+204px, ~195px tall) while this panel
       grows upward from a flat 45vh of a SHORTENING viewport, so the two
       converge at common desktop heights like 1366x768/1280x720. There was
       no existing guard for this specific band (the short-viewport query
       just above hides this panel entirely, but only below 500px height —
       leaving 500-867px unguarded). Rather than hand-deriving yet another
       magic-number breakpoint, app.js's updateOverlayClearance() measures
       the real rendered bottom edge of whichever of the legend/zoom/
       hamburger column is lowest and publishes the leftover space as
       --overlay-column-clear; min() below means this can only ever SHRINK
       from 45vh to make room, never grow past it — on any viewport tall
       enough that no overlap is possible, this resolves to plain 45vh,
       identical to the old behavior. The 100vh fallback is for the instant
       before app.js's first measurement runs (or if JS is disabled) —
       falls back to the original unconditional 45vh via min(). */
    max-height: min(45vh, var(--overlay-column-clear, 100vh));
    overflow-y: auto;
    padding: 8px;
    border-radius: 14px;
}
.selection-summary-title {
    font-size: 9px;
    font-weight: 900;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--txt-1);
    padding-bottom: 6px;
    margin-bottom: 6px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    /* Phase BB: it's a <button> now (collapse toggle) — strip UA chrome. */
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    background: none;
    border-top: 0; border-left: 0; border-right: 0;
    padding-left: 0; padding-right: 0; padding-top: 0;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}
.selection-summary-count { color: var(--txt-3); font-weight: 700; margin-left: 2px; }
.selection-summary-chevron { font-size: 8px; color: var(--txt-3); transition: transform 0.15s ease; }
#selection-summary-panel.is-collapsed .selection-summary-chevron { transform: rotate(-90deg); }
#selection-summary-panel.is-collapsed #selection-summary-body { display: none; }
#selection-summary-panel.is-collapsed .selection-summary-title { padding-bottom: 0; margin-bottom: 0; border-bottom: 0; }
.selection-summary-section + .selection-summary-section {
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.selection-summary-section-header {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 3px;
}
.selection-summary-section-header i { font-size: 10px; width: 12px; text-align: center; }
.selection-summary-section-name {
    flex: 1;
    min-width: 0;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--txt-1);
}
.selection-summary-section-priority {
    font-size: 8px;
    font-weight: 800;
    /* Default (no priority-N class resolved) falls back to the old
       amber-everywhere look. */
    color: var(--accent);
    background: var(--accent-soft);
    border: 1px solid var(--accent-border);
    border-radius: 4px;
    padding: 1px 4px;
}
/* Nick, 2026-08-01: "give match criteria priority levels their own
   colors" — updateSelectionSummaryPanel() (app.js) adds one of these as a
   second class alongside .selection-summary-section-priority, keyed off
   the category's actual weight (1-5). See :root's --priority-* tokens for
   the palette. */
.selection-summary-section-priority.priority-1 { color: var(--priority-1); background: var(--priority-1-soft); border-color: var(--priority-1-border); }
.selection-summary-section-priority.priority-2 { color: var(--priority-2); background: var(--priority-2-soft); border-color: var(--priority-2-border); }
.selection-summary-section-priority.priority-3 { color: var(--priority-3); background: var(--priority-3-soft); border-color: var(--priority-3-border); }
.selection-summary-section-priority.priority-4 { color: var(--priority-4); background: var(--priority-4-soft); border-color: var(--priority-4-border); }
.selection-summary-section-priority.priority-5 { color: var(--priority-5); background: var(--priority-5-soft); border-color: var(--priority-5-border); }
.selection-summary-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    padding: 1.5px 0 1.5px 18px;
    font-size: 9px;
}
.selection-summary-row-label { color: var(--txt-3); }
.selection-summary-row-value {
    font-family: ui-monospace, monospace;
    font-weight: 700;
    color: var(--good);
    white-space: nowrap;
}
.selection-summary-row-value i { color: var(--bad); margin-left: 2px; }
.selection-summary-row-muted {
    color: var(--txt-4);
    font-style: italic;
    padding-left: 18px;
}
.selection-summary-empty {
    font-size: 9px;
    color: var(--txt-4);
    text-align: center;
    padding: 4px 2px;
}
/* Phase A3 (RELEASE_PLAN.md): disclaimer shown whenever no personal target
   is set anywhere, so the panel (and the matching export card) can never
   read as an objective "best places" ranking. */
.selection-summary-banner {
    font-size: 9px;
    font-weight: 700;
    color: #cbd5e1;
    text-align: center;
    padding: 5px 8px;
    margin-bottom: 6px;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
}

/* Standings-row pin button (Nick's feedback, 2026-07-19/20) — sits right
   of the match %. Not scoped to the rail breakpoint since standings rows
   are shared markup between mobile and desktop. Always rendered now (was
   hidden entirely once MAX_PINS were active — that shifted every row's
   other content left/right as the icon appeared/disappeared, "looked
   messy"); greys out and becomes unclickable instead via .disabled, so
   row layout never shifts. Pinned uses a distinct color (sky) rather than
   amber, which is already the match-% color on the same row. */
.standings-pin-btn {
    background: transparent;
    border: none;
    color: #475569;
    font-size: 9px;
    padding: 2px 3px;
    cursor: pointer;
    transition: color 0.12s ease;
    flex-shrink: 0;
}
.standings-pin-btn:hover { color: #94a3b8; }
.standings-pin-btn.pinned { color: #38bdf8; }
.standings-pin-btn.disabled {
    color: #334155;
    cursor: default;
    pointer-events: none;
}

/* Mobile legend */
@media (max-width: 599.98px) {
    #map-legend {
        top: 64px;
        bottom: auto;
    }
}

/* === Desktop polish: larger panels and type at 1200px+ === */
@media (min-width: 1200px) {
    /* #settings-panel and #county-details-drawer are intentionally NOT
       resized here — this rule predates the Phase 2b/2c persistent rail
       (REVAMP_PLAN.md). It used to compact the old slide-in drawers to
       50vh/344px; with the rail (and, since 2c, the drawer twinned to the
       rail's geometry) full-height at >=600px, that !important height
       clobbered them down to a fraction of the screen — same bug fixed
       for #settings-panel in the 2b visual-verify follow-up, same fix
       here. See style.css ~272 (rail) and ~330 (drawer twin) for the
       real sizing. */
    #leaderboard-container > div {
        padding: 8px 14px !important;
    }
    #leaderboard-container > div {
        padding: 8px 14px !important;
    }
    #leaderboard-container > div span:nth-child(2) {
        font-size: 12px !important;
    }
    #leaderboard-container > div span:nth-child(3) {
        font-size: 12px !important;
    }
    #summary-panel {
        max-width: 320px !important;
    }
}

/* === Desktop: summary panel sizing === */
@media (min-width: 600px) {
    #summary-panel {
        max-height: min(72vh, 640px) !important;
        min-width: 220px;
    }
    #summary-panel .glass-panel {
        max-height: inherit;
        overflow: hidden;
    }
    #summary-content-list {
        max-height: calc(min(72vh, 640px) - 52px) !important;
        overflow-y: auto !important;
    }
}

/* === Options & Summary Panels === */
/* z-index bumped from 1010 to 1055 (2026-07-31, RELEASE_PLAN.md) — Nick:
   "the options panel should also always be on top for instances like when
   using mobile how the criteria panel could possibly overlap over the
   options panel." It shared the exact same z-index (1010) as
   #settings-panel and #county-details-drawer, both of which can expand to
   cover most of the mobile screen (the drag-resizable bottom sheet, Phase
   J4) — measured a real geometric overlap in that expanded state.
   Equal-z-index ties resolve by DOM order, and #appearance-panel happens to
   come later in index.html, so it happened to already win that tie in
   testing — but that's an accident of source order, not a guarantee; a
   future reordering of the markup would silently break it. 1055 makes the
   win explicit and independent of DOM position: above both panels (1010)
   and #summary-panel (1020). Not above the hamburger dropdown it's opened
   from (1040), but that doesn't matter — the dropdown closes itself the
   moment Options opens, so the two are never visible at once. Still well
   below modal/toast tiers (10000+). */
/* Phase AA (2026-08-01): Nick — "make sure on mobile all this is contained
   enough it doesn't reach the limits of the browser window." This panel had
   no max-height/overflow at all — it just grew to fit its content, which on
   a short viewport (932x430 landscape phone measured directly: bottom edge
   ~180px past the visible window) ran the Areas/System sections straight
   off the bottom of the browser with no scrollbar and no way to reach them.
   `--app-h` is the same real-viewport-height custom property
   `.about-modal-shell` already uses for this exact class of bug — anchored
   to this panel's own `top` offset (not a flat percentage) so the max-height
   is exactly "however much room is actually left below the panel," on any
   device. */
#appearance-panel {
    top: calc(3.5rem + 12px);
    left: 50%;
    transform: translateX(-50%) translateY(-10px);
    width: 290px;
    max-height: calc(var(--app-h) - (3.5rem + 12px) - 16px);
    overflow-y: auto;
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
    z-index: 1055;
}
#appearance-panel.open {
    visibility: visible;
    opacity: 1;
    pointer-events: auto;
    transform: translateX(-50%) translateY(0);
}
#summary-panel {
    visibility: hidden;
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
    z-index: 1020;
}
#summary-panel.open {
    visibility: visible;
    opacity: 1;
    pointer-events: auto;
    transform: scale(1);
}
/* Mobile: center the summary panel horizontally at the top */
@media (max-width: 599.98px) {
    #summary-panel {
        left: 8px !important;
        right: 8px !important;
        top: calc(3.5rem + env(safe-area-inset-top, 0px)) !important;
        width: auto !important;
        min-width: unset !important;
        max-width: none !important;
    }
}
@media (max-width: 599.98px) {
    #appearance-panel {
        left: 8px !important;
        right: auto !important;
        width: 262px !important;
        top: calc(6.5rem + env(safe-area-inset-top, 0px)) !important;
        max-height: calc(var(--app-h) - (6.5rem + env(safe-area-inset-top, 0px)) - 12px) !important;
        transform: translateY(-8px) !important;
    }
    #appearance-panel.open {
        transform: translateY(0) !important;
    }
}

/* === Share Modal === */
#share-modal .glass-panel {
    animation: fadeInUp 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

/* === About / Info Modals — never go above the header === */
#about-modal,
#info-modal,
#share-modal {
    padding-top: calc(3.5rem + env(safe-area-inset-top, 0px) + 8px);
    align-items: flex-start;
    padding-bottom: 16px;
}
@media (min-width: 600px) {
    #about-modal,
    #info-modal,
    #share-modal {
        padding-top: 0;
        align-items: center;
        padding-bottom: 0;
    }
}
#about-modal,
#info-modal { padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
@media (min-width: 600px) { #about-modal, #info-modal { padding-bottom: 0; } }
#about-modal > div,
#info-modal > div {
    max-height: calc(100vh - 3.5rem - env(safe-area-inset-top, 0px) - 24px);
    /* Phase CA (2026-09-24): Nick — the Sources panel ran off the bottom of
       the phone. iOS Safari's 100vh is the height with its toolbar HIDDEN,
       so with the toolbar showing the card's bottom sat under it. dvh is
       the visible height; the home-indicator inset is kept clear too. The
       card's middle (the tab content) is the part that scrolls. */
    max-height: calc(100dvh - 3.5rem - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - 24px);
    min-height: 0;
}
@media (min-width: 600px) {
    #about-modal > div,
    #info-modal > div {
        max-height: 88vh;
    }
}

/* === 3-State Metric Pill Controls === */
/* Option B metric row (REVAMP_PLAN.md Phase 2a) — one line per metric:
   label+info | state chip | always-visible slider | value | lock (hard filter).
   Generated by renderMetricRows() in app.js from METRIC_DEFS in config.js.

   CSS Grid, not flexbox: each row is an independent flex container, so
   flexbox's per-row auto-sizing does NOT align a column across sibling rows
   (this was the alignment bug — a longer chip/value string on one row pushed
   its neighbors around, shifting where every other row's slider started).
   Grid with FIXED pixel tracks for every column except the label guarantees
   every row's chip/slider/value/lock line up in the same place regardless of
   that row's own content. Column widths are sized from the actual longest
   rendered strings (see REVAMP_PLAN.md 2a's follow-up notes) — the label
   column absorbs whatever's left and only the handful of longest labels
   (e.g. "Min Restaurants per 10k People") ellipsize. */
/* Two-line row (REVAMP_PLAN.md 2b.1 — supersedes the earlier one-line grid,
   which gave the label column only ~110px at the 340px rail width, pushing
   the info icon into the ellipsis on ~15 of the 41 metrics and making it
   unreachable). Line 1 is the label + info icon and never truncates; line 2
   is chip/slider/value/lock, and the slider — previously squeezed into a
   fixed 60px column — now gets the freed-up width via `1fr`. */
.metric-row {
    display: flex;
    flex-direction: column;
    row-gap: 4px;
    padding: 6px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.metric-row:last-child { border-bottom: none; }

.metric-row-label {
    display: flex;
    align-items: center;
    gap: 4px;
    /* 9px -> 8.5px in Phase I2, restored to 9px in Phase J1 (RELEASE_PLAN.md)
       — the 8.5px was a truncation fallback compensating for I2's
       too-narrow rail (216px, an arbitrary width); J1's measured
       clamp(290px, 24vw, 330px) rail no longer needs it, and 8.5px was a
       legibility regression Nick never asked for. */
    font-size: 9px;
    font-weight: 700;
    color: var(--txt-3);
    line-height: 1.3;
}
/* Phase I1 (RELEASE_PLAN.md): the value moved from line 2 (the controls
   grid) onto line 1, beside the label — freeing a whole grid column below
   for I2's narrower rail, since the value is what used to cost 60px there.
   min-width:0 lets this side truncate instead of the value input ever
   getting compressed.
   Phase P (2026-07-30): `flex: 1 1 auto` -> `flex: 0 1 auto`. The `1` in
   the grow slot made this span stretch to fill ALL leftover row width,
   which shoved the ⓘ info icon (its next sibling) away from the label it
   describes and parked it against the value instead — measured 59-119px
   of empty space between the end of the label text and its own icon.
   Nick: "the info button should be directly next to each criteria name
   instead of directly next to the value." With grow off, this span is
   only as wide as its text, the icon sits immediately after it, and
   .metric-value-wrap's `margin-left: auto` (below) absorbs the leftover
   space to push the value flush right. Shrink stays at 1 so long labels
   still ellipsis rather than overflowing. */
.metric-row-label-text {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Phase AO (2026-09-12): click-to-solo — a per-row solo ICON was
       measured and ruled out (the row's grid is fully accounted for:
       chip | flexible | lock, zero spare fixed-width columns — see
       buildMetricRowHtml()'s own comment), so the label itself is the
       entry point instead. cursor+underline-on-hover is the only visual
       change; the row's layout is untouched. */
    cursor: pointer;
}
.metric-row-label-text:hover,
.metric-row-label-text:focus-visible {
    color: var(--accent);
    text-decoration: underline;
    text-decoration-style: dotted;
    text-underline-offset: 2px;
}
/* occ_salary's label ("Min Salary — Selected Profession", 33 chars) is the
   longest in the app. Phase J1 re-measured this exception rather than
   assuming it: Playwright confirmed it still wraps to 2 lines (23px tall
   vs. 12px for 1 line) at every rail width from 300-330px, i.e. every
   viewport up to ~1375px wide — it only collapses to one line at the
   340px ceiling (≥1375px viewports). It already carries its own hint line
   below the row (buildMetricRowHtml()'s occ_salary-only #occ-salary-hint),
   so letting it wrap here is the right call for the vast majority of
   screens, not just a leftover from Phase I2 — keep this rule. */
#metric-row-occ_salary .metric-row-label-text {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

.metric-row-controls {
    display: grid;
    grid-template-columns: 46px minmax(0, 1fr) 18px;
    align-items: center;
    column-gap: 5px;
}

/* Phase AJ (2026-08-01): Nick — "I want to allow even more compact range
   on the side panel, even when its known to cutoff visuals, the user has
   the control." app.js's clampRailW() toggles body.rail-narrow whenever
   the rail is dragged/keyboard-resized below the measured no-clip floor
   (296px) — this class only ever appears at the >=600px desktop rail
   breakpoint (the drag handler itself is gated to that width), so these
   rules don't need their own media-query wrapper. Below that floor,
   something has to give; this spends the deficit on CHROME (padding,
   gaps, the value column's slack) before it spends it on the labels/
   numbers themselves, so content survives as far down as possible.
   Deliberately does NOT touch #panel-scroll-area's margin-right/
   padding-right — that's Phase N/O's scrollbar-clearance architecture,
   and Phase AB's resize-handle gradient depends on the panel's right
   edge staying the same grey it already is; shrinking it re-opens two
   closed bugs for a few px of gain. */
body.rail-narrow .metric-row-controls {
    grid-template-columns: 40px minmax(0, 1fr) 16px;
    column-gap: 4px;
}
body.rail-narrow .rail-section-body:not(.rail-collapsed) {
    padding-left: 4px;
    padding-right: 4px;
}

/* === Any/Off/Target chip (Phase AM follow-up, 2026-08-02) ===
   Nick's own explicit redesign, given after reviewing two built variants
   ("one issue I dont like... I want it to behave this way"), replacing
   both:
   - The switch (.metric-chip-track/-knob) toggles ONLY Any <-> Off, and
     is ALWAYS neutral-colored in both positions — the switch itself
     never turns red or otherwise signals "off"; ONLY its knob position
     changes (left = Off, right = Any).
   - "Off" is instead signaled by dimming the REST of the row
     (.metric-row-off below) — label, slider, lock all dim; the chip is
     deliberately excluded from that selector so it stays at full
     brightness regardless of the row's own state.
   - "Any" reads exactly like every other enabled row — full brightness,
     nothing dimmed (the slider's own former default-dimmed-until-
     targeted look is gone; see .metric-row-slider-wrap below).
   - The moment the slider moves, the switch is REPLACED by a pill
     (.metric-chip-pill) reading "Target" (amber) — or, if the hard-
     filter lock is active, "Required" (red) instead. Switch and pill are
     mutually exclusive: exactly one is visible per state
     (chip-off/chip-any/chip-target, set by updateMetricPillUI(), app.js).
   role="switch" throughout — Off is the only "off" position for
   accessibility purposes; Any and Target both read as "on". === */
.metric-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    border: none;
    padding: 0;
    background: none;
    /* Measured directly: the pre-redesign chip (same font/padding shape)
       rendered at 24px tall in this row's flex context, well past what
       the track's own 12px visual size would suggest. min-height keeps
       the CLICKABLE area at that same size — a compact switch inside a
       larger hit target is a standard pattern, not a shrunk tap target. */
    min-height: 24px;
}
.metric-chip:focus-visible { outline: 2px solid var(--accent-fill); outline-offset: 1px; }

/* --- The switch: Any <-> Off only, always neutral --- */
.metric-chip-track {
    position: relative;
    display: block;
    width: 36px;
    height: 16px;
    border-radius: 999px;
    background: var(--surf-3);
    border: 1px solid var(--bdr-2);
    transition: background 0.15s ease, border-color 0.15s ease;
}
/* Phase BI (RELEASE_PLAN.md): the state is written INSIDE the switch —
   Nick: "when off says off, when on says on or becomes illuminated." The
   word sits on the side the knob isn't on. Pure CSS off the chip-* class
   updateMetricPillUI() already sets, so no JS changes. */
.metric-chip-track::after {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    font-size: 7.5px;
    font-weight: 900;
    letter-spacing: 0.04em;
    line-height: 1;
}
.metric-chip.chip-off .metric-chip-track::after { content: 'OFF'; right: 4px; color: var(--txt-3); }
.metric-chip.chip-any .metric-chip-track::after { content: 'ON'; left: 5px; color: #2a1a00; }
.metric-chip.chip-any .metric-chip-track {
    background: var(--accent-fill);
    border-color: var(--accent-fill);
}
.metric-chip-knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--txt-3);
    transition: transform 0.15s ease, background 0.15s ease;
}
.metric-chip.chip-off .metric-chip-knob { transform: translateX(0); }
.metric-chip.chip-any .metric-chip-knob { transform: translateX(20px); background: #fff; }
.metric-chip:hover .metric-chip-track { filter: brightness(1.1); }

/* --- The pill: replaces the switch entirely once a target is set --- */
.metric-chip-pill {
    display: none;
    font-size: 7px;
    font-weight: 900;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
    padding: 3px 5px;
    border-radius: 4px;
    /* Phase BI / D7: lit, like the ON switch — solid fill, dark text. */
    border: 1px solid var(--accent-fill);
    background: var(--accent-fill);
    color: #2a1a00;
}
.metric-chip.chip-target .metric-chip-track { display: none; }
.metric-chip.chip-target .metric-chip-pill { display: block; }
/* Hard-filter lock active (toggleMetricStrict(), app.js) — the SAME
   target, now framed as a requirement rather than a preference. */
.metric-chip.chip-target.chip-strict .metric-chip-pill {
    border-color: var(--bad);
    background: var(--bad);
    color: #fff;
}

/* Whole-row dim for Off — deliberately does NOT include .metric-chip
   (see the section comment above: the switch always stays full-
   brightness). The slider itself no longer has its own separate
   dimmed/not-dimmed state (removed below) — this row-level class is now
   the only thing that dims it, only for Off, never for Any. */
.metric-row.metric-row-off .metric-row-slider-wrap,
.metric-row.metric-row-off .metric-row-lock {
    opacity: 0.4;
    transition: opacity 0.15s ease;
}
/* Phase CW (2026-09-25): off labels stay fully opaque in the secondary text
   colour (was 40% opacity — failed WCAG contrast; light mode already did this). */
.metric-row.metric-row-off .metric-row-label { opacity: 1; }
.metric-row.metric-row-off .metric-row-label-text { color: var(--txt-3); }

/* Phase AN (RELEASE_PLAN.md, 2026-09-12): brief highlight on whichever
   airport slider got PUSHED as a side effect of dragging its partner (see
   app.js's pushAirportTarget()) — "so the user sees why a control they did
   not touch moved." A background flash rather than a border/outline: this
   row already has borrowed/reused chrome on every edge (chip, lock,
   slider track), so a background wash is the one visual channel with no
   existing meaning to collide with. 1.2s total, added/removed by JS —
   this class carries no state, only the transient animation. */
@keyframes metric-row-coupled-flash {
    0% { background: var(--accent-soft); }
    100% { background: transparent; }
}
.metric-row-coupled {
    animation: metric-row-coupled-flash 1.2s ease-out;
    border-radius: 6px;
}

.metric-row-slider-wrap {
    min-width: 0;
    /* Nick, 2026-08-02: "make sure the toggles and sliders share the same
       line visually." .metric-chip centers its track via flexbox
       (display:flex; align-items:center on the chip itself), so it always
       sits dead-center in the row's 24px grid cell. This wrap had no such
       rule — the bare <input type="range"> (rendered only 6px tall in
       Chrome, matching its ::-webkit-slider-runnable-track) was left to
       default inline baseline alignment against the row's 24px line-height,
       which lands a short inline replaced element a few px below true
       center, not centered. flex + align-items:center makes the slider a
       centered flex child instead, matching the chip exactly. */
    display: flex;
    align-items: center;
}
.metric-row-slider-wrap .custom-slider { width: 100%; }

/* Phase I1 (RELEASE_PLAN.md): now a real <input>, not a span — every
   criteria value is manually editable. Resting state deliberately still
   reads as plain text (transparent background, invisible border) so the
   row doesn't look like a form; the border only appears on hover/focus,
   which is the one visual cue that it's editable.
   flex:0 0 84px (56px in I1, wrongly narrowed to 48px in I2's
   arbitrary pass, corrected in Phase J1 to a MEASURED width — the widest
   value string across all 43 metrics, "Under $1,000/mo" (avg_rent), is
   ~73px of 8px-monospace text; +8px for this rule's own 3px+3px padding
   plus slack = 84px. J1 also re-measured every other wide value
   (avg_property_tax_paid, terrain_relief, airport_dist) and confirmed 84px
   clears all of them — re-run tools/test_metric_input.js's per-metric
   label generation before changing this again rather than picking a
   number by feel, which is exactly the mistake J1 was created to fix.
   min-width:0 is load-bearing, not decorative: form controls carry an
   intrinsic minimum content size (their default unstyled ~size=20 width)
   that flexbox otherwise respects OVER an explicit flex-basis, so without
   this override the input rendered at ~110px regardless of the flex-basis
   — silently eating almost the entire row and squeezing
   .metric-row-label-text down to a few px, which made every label
   truncate regardless of length. Caught by Playwright measurement during
   I2 (RELEASE_PLAN.md), not by inspection. */
/* Phase L (RELEASE_PLAN.md, 2026-07-30): the value is now THREE elements —
   a fixed prefix span, this number-only input, a fixed suffix span — not
   one input holding the whole formatted string. .metric-value-wrap is the
   flex row that holds all three. */
/* margin-left:auto is what actually right-justifies the value (Phase P,
   2026-07-30). It absorbs every pixel of leftover row width into this
   element's own left margin, so the prefix/number/suffix group is always
   flush against the row's right edge no matter how short the label is —
   and, because the leftover space now lives HERE instead of inside
   .metric-row-label-text's grow, the ⓘ icon stays next to the label. */
.metric-value-wrap {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    margin-left: auto;
}
.metric-value-prefix,
.metric-value-suffix {
    font-size: 8px;
    font-family: ui-monospace, monospace;
    font-weight: 700;
    color: var(--accent);
    white-space: nowrap;
}
/* Phase P (2026-07-30) — DELIBERATELY no reserved width on the suffix.
   Phase N reserved 53px here (the widest suffix, "+/10 Rating") so the
   NUMBERS would line up in a column across rows. That was optimising the
   wrong thing: a fixed slot wider than most suffixes ("%" is 5px) leaves
   the leftover 48px as dead space AFTER the last visible character, so the
   value stopped being flush right — Nick: it "is not right justified and
   looks off because it is somewhat central in the panel while nothing
   else is" — and the same 53px was stolen from every label, which is what
   truncated criteria names on a narrow rail ("cutting off the criteria
   name on desktop ... solely because of the positioning of the value").
   These two goals are mutually exclusive: with variable-width suffixes you
   can align the numbers OR the right edge, never both. Right-edge
   alignment is the one that matters (it's what every other element in the
   panel does), so the suffix is content-sized again and the whole
   prefix/number/suffix group is pushed flush right by
   .metric-value-wrap's margin-left:auto. Do not reintroduce a min-width
   here without re-reading this. */
/* Phase R (2026-07-31): width is now CONTENT-SIZED by JS, not a fixed
   flex-basis. Every previous attempt here picked a fixed width wide enough
   for the widest number in the app ("1,000"), which meant every SHORTER
   value — i.e. almost all of them — left the difference as empty space.
   Because the number is `text-align: right`, that empty space lands
   BETWEEN the prefix and the number: "Any Limit" ......34px...... "—".
   That gap is what made the yellow value read as "somewhat central in the
   panel" (Nick) even though the group's outer right edge was provably
   flush — Phases N/O/P all measured the outer edge, which kept passing,
   and never measured the gap inside the group.
   sizeMetricValueInput() (app.js) sets an exact `Nch + 10px` width from
   the current value's length on every render and on every keystroke. `ch`
   is the advance width of "0", which is exact here because this rule's
   font is monospace — so the box always fits its content and nothing else.
   Do not reintroduce a fixed width/flex-basis here; that is the specific
   mistake this rule exists to prevent. */
.metric-row-value {
    flex: 0 0 auto;
    min-width: 0;
    font-size: 8px;
    font-family: ui-monospace, monospace;
    font-weight: 700;
    color: var(--accent);
    text-align: right;
    white-space: nowrap;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 3px;
    padding: 1px 3px;
    cursor: text;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.metric-row-value:hover {
    border-color: rgba(255, 255, 255, 0.12);
    background: rgba(255, 255, 255, 0.03);
}
.metric-row-value:focus {
    outline: none;
    border-color: rgba(245, 158, 11, 0.5);
    background: rgba(245, 158, 11, 0.08);
}

/* Always rendered (never display:none) so its column never shifts the rest
   of the row — just dims to near-invisible and stops responding to clicks
   until there's actually a target to hard-filter on. */
/* justify-self: center -> end (Phase Q, 2026-07-31). The value on line 1 is
   flush against the row's true right edge (Phase P's margin-left:auto), but
   this icon was centered inside its own 18px grid column, landing ~4-9px
   short of that same edge — so the value could never actually sit "over the
   padlock" no matter how right-justified it was; the padlock itself wasn't
   at the edge. Nick: "why cant it be right justified... such that it is
   located over the padlock symbol." end pins the icon's box to the column's
   (and therefore the row's) right edge, matching the value above it. */
.metric-row-lock {
    font-size: 9px;
    color: var(--txt-4);
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 2px 0;
    transition: color 0.12s ease, opacity 0.15s ease;
    justify-self: end;
}
.metric-row-lock.inactive {
    opacity: 0.2;
    pointer-events: none;
}
.metric-row-lock:hover { color: var(--txt-3); }
.metric-row-lock.active { color: var(--bad); }

/* Optional extra line under a row (currently only occ_salary's hint about
   picking a profession first) */
.metric-row-hint {
    font-size: 8px;
    color: var(--txt-4);
    line-height: 1.4;
    padding-left: 2px;
    margin-top: -2px;
}

/* Gradient preset swatches in the Settings panel */
.preset-swatch {
    position: relative;
    flex: 1;
    height: 16px;
    border-radius: 6px;
    border: 1px solid rgba(255, 255, 255, 0.15);
    cursor: pointer;
    padding: 0;
    transition: transform 0.12s ease, border-color 0.12s ease;
}
.preset-swatch:hover {
    transform: scale(1.04);
    border-color: rgba(255, 255, 255, 0.4);
}
.preset-swatch.active {
    border-color: #fbbf24;
    box-shadow: 0 0 0 1px rgba(251, 191, 36, 0.5);
}
/* Small corner dot marking colorblind-safe palettes */
.preset-swatch.cb-safe::after {
    content: "";
    position: absolute;
    top: -3px;
    right: -3px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #34d399;
    border: 1px solid rgba(15, 23, 42, 0.8);
}

/* Hex text input beside a color swatch */
.hex-text-input {
    width: 100%;
    margin-top: 3px;
    background: var(--surf-3);
    border: 1px solid var(--bdr-1);
    border-radius: 4px;
    color: var(--txt-1);
    font-family: ui-monospace, monospace;
    font-size: 8px;
    padding: 2px 3px;
    text-align: center;
    text-transform: uppercase;
}
.hex-text-input:focus {
    outline: none;
    border-color: var(--accent-fill);
}
.hex-text-input.invalid {
    border-color: #fb7185;
    color: #fb7185;
}

/* Hobby selector chips */
.hobby-chip {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 9px;
    font-weight: 700;
    padding: 7px 8px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.03);
    color: #94a3b8;
    cursor: pointer;
    transition: all 0.12s ease;
    text-align: left;
    line-height: 1.2;
}
.hobby-chip i { width: 13px; text-align: center; }
.hobby-chip:hover { background: rgba(255, 255, 255, 0.07); color: #cbd5e1; }
.hobby-chip.active {
    background: rgba(244, 63, 94, 0.15);
    border-color: rgba(244, 63, 94, 0.4);
    color: #fda4af;
}

/* Info icon beside a metric label — a real button (click/tap opens
   #metric-info-popover via showMetricInfo()); see METRIC_INFO in config.js,
   rendered inline by renderMetricRows(). Not a bare <i> with a `title` —
   title-only tooltips never fire on touch devices. */
.metric-info-icon {
    display: inline-flex;
    font-size: 8px;
    color: var(--txt-4);
    margin-left: 4px;
    padding: 3px;
    background: transparent;
    border: none;
    cursor: pointer;
    transition: color 0.12s ease;
    vertical-align: middle;
}
.metric-info-icon:hover { color: var(--accent); }

/* Shared popover shown by showMetricInfo() — same visual language as the
   map's #d3-tooltip. */
#metric-info-popover {
    background: var(--surf-1);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid var(--bdr-2);
    color: var(--txt-2);
    border-radius: 10px;
    box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.7);
    padding: 10px 12px;
    font-family: var(--font-body);
    font-size: 10px;
    line-height: 1.5;
    max-width: 240px;
}
/* Phase CD: the Activities "What's counted" list. Long, so it scrolls
   within the viewport instead of running off a phone screen. */
#metric-info-popover { max-height: calc(100dvh - 24px); overflow-y: auto; }
.hobby-info-list { margin: 6px 0 2px; padding: 0; list-style: none; }
.hobby-info-list li { padding: 1px 0; }
.hobby-info-list b { color: var(--txt-1); font-weight: 700; }
.hobby-info-btn {
    display: inline-flex; align-items: center; gap: 3px;
    background: none; border: 0; padding: 0; cursor: pointer;
    color: var(--txt-2); font: inherit; font-weight: 700; text-decoration: underline;
    text-underline-offset: 2px;
}
.hobby-info-btn i { font-size: 9px; color: var(--txt-3); }
.metric-info-source {
    display: block;
    margin-top: 4px;
    color: var(--txt-3);
    font-size: 9px;
    font-weight: 600;
}
/* County-detail-only line (Phase, 2026-07-31) — showMetricInfo()'s optional
   `displayValue` param. Sits between the description and the source line;
   amber to match the yellow value text every metric tile already uses. */
.metric-info-value {
    display: block;
    margin-top: 4px;
    color: var(--accent);
    font-size: 9px;
    font-weight: 700;
}
/* Phase BM: "Lower/Higher is better" line in the ⓘ popover. */
.metric-info-direction {
    display: block;
    margin-top: 4px;
    color: var(--txt-1);
    font-size: 9px;
    font-weight: 700;
}
.metric-info-direction i { color: var(--accent); margin-right: 2px; }

/* First-use hint banner — 3 icon rows (2026-07-21), replacing one dense
   wrapped sentence. Each row is its own mechanic, matching the icon it
   actually uses elsewhere in the rail (fa-sliders on the hamburger,
   fa-lock on the metric row's own lock button) so the tip visually
   points at the real control, not just describes it.
   Phase AB (2026-08-02): now the card inside a full-screen overlay
   (#metric-hint-overlay) instead of an inline banner stretched to its
   scroll-area parent's width — capped to a sane standalone card width. */
.metric-hint-banner {
    /* Phase BY (2026-09-24): neutral card. Nick: the Quick tips card was
       "all orange" and didn't fit either theme. Amber now appears only in
       the miniature lit switch and TARGET pill, as on the real rows. */
    /* surf-1 is translucent glass in dark mode; laid over surf-0 so the
       tab labels under the card don't show through. */
    background: linear-gradient(var(--surf-1), var(--surf-1)), var(--surf-0);
    border: 1px solid var(--bdr-1);
    border-radius: 12px;
    padding: 12px 30px 12px 12px;
    position: relative;
    max-width: 330px;
    width: 100%;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.hint-title {
    font-size: 9px;
    font-weight: 900;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--txt-2);
    margin-bottom: 8px;
}
.hint-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 10px;
    line-height: 1.5;
    color: var(--txt-1);
}
.hint-row b { font-weight: 800; }
.hint-switch {
    flex: none;
    width: 30px;
    margin: 1px 14px 0 0;
    padding: 2px 0 2px 5px;
    border-radius: 9999px;
    background: var(--accent-fill);
    color: #2a1a00;
    font-size: 7px;
    font-weight: 900;
    line-height: 10px;
    position: relative;
}
.hint-switch::after {
    content: '';
    position: absolute;
    right: 2px;
    top: 2px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #fff;
}
.hint-row + .hint-row { margin-top: 7px; }
.hint-icon {
    flex: none;
    width: 30px;
    margin-right: 14px;
    color: var(--txt-3);
    padding-top: 1px;
    font-size: 10px;
    text-align: center;
}
/* Phase AM follow-up round 2 (2026-08-02) — real miniatures of the chip's
   own .metric-chip-pill, same tokens, so these can't drift out of sync
   with what the pill actually looks like in the rail. Not reusing
   .metric-chip-pill directly: that class is `display:none` by default
   and only shown via a `.chip-target`/`.chip-strict` ancestor state this
   standalone preview doesn't have — simpler to duplicate the handful of
   declarations than fight that dependency. */
.hint-pill {
    flex: none;
    display: inline-block;
    font-size: 7px;
    font-weight: 800;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
    padding: 3px 0;
    width: 44px;
    text-align: center;
    border-radius: 4px;
    border: 1px solid;
    margin-top: 1px;
}
.hint-pill-target {
    background: var(--accent-fill);
    border-color: var(--accent-fill);
    color: #2a1a00;
}
.hint-pill-required {
    background: var(--bad);
    border-color: var(--bad);
    color: #fff;
}
.metric-hint-banner .hint-dismiss {
    position: absolute;
    top: 6px;
    right: 7px;
    font-size: 11px;
    color: var(--txt-3);
    cursor: pointer;
    background: none;
    border: none;
    line-height: 1;
    padding: 2px;
    transition: color 0.12s ease;
}
.metric-hint-banner .hint-dismiss:hover {
    color: var(--txt-1);
}

/* =============================================================
   MOBILE PANEL SYSTEM v17
   Tab bar anchored at TOP of 40vh panel zone.
   Map: top → 60vh. Tab bar: bottom calc(40vh-44px).
   Content: between tab bar and category icon bar.
   ============================================================= */

/* Hidden on desktop */
#mobile-tab-bar { display: none; }
#mobile-panel-grabber { display: none; }
#metrics-empty-state { display: none; }
#metrics-empty-state.visible { display: flex !important; }
#mobile-recenter-btn { display: none; }

@media (max-width: 599.98px) {
    /* Local zone math (RELEASE_PLAN.md Phase F5e, 2026-07-29) — the panel
       zone itself was already proportional (40vh), but the chrome inside
       it (36px tab bar + 52px category bar) was hard pixels: 33% of the
       zone on a 320px-tall SE, 24% on a 448x998 Pro Max. --ui-scale
       (app.js's applyViewportMetrics(), measured against a 390x844
       reference device) holds that proportion constant instead; --app-h
       tracks the LIVE viewport height (not 100vh, which on iOS Safari is
       the toolbar-HIDDEN height and would let this zone run under the
       toolbar). Both fall back to neutral values (style.css :root) if JS
       hasn't run yet. Declared on :root (not a bare statement in this
       @media block, which plain CSS custom properties can't be) so every
       selector below can read them. */
    :root {
        --m-tabbar-h: calc(36px * var(--ui-scale));
        --m-catbar-h: calc(52px * var(--ui-scale));
        /* --m-zone-frac (RELEASE_PLAN.md Phase J4, 2026-07-30): the 0.40
           default is now a variable, not a literal, so #mobile-panel-
           grabber's drag can write ONE number and have every dependent
           measurement below (map bottom, tab bar position, all three panel
           heights, recalc indicator) follow for free — do not add a second
           height system alongside this one. */
        --m-zone-frac: 0.40;
        --m-zone-h: calc(var(--app-h) * var(--m-zone-frac));
        --m-panel-h: calc(var(--m-zone-h) - var(--m-tabbar-h) - var(--m-catbar-h) - env(safe-area-inset-bottom, 0px));
        /* Phase S4: enlarges the slider thumb's invisible border (see the
           base ::-webkit-slider-thumb rule) — phone only, since mouse
           targeting on desktop was never the complaint. 9px measured as the
           largest value that does not let the enlarged thumb intercept a
           tap meant for the "Any" chip 5px to the track's left at the
           slider's minimum value (tools/measure_thumb_hit.js) — the tightest
           of the two neighbors; the lock icon on the other side has more
           clearance. Do not raise this without re-measuring that gap. */
        --slider-thumb-pad: 9px;
    }

    /* ── Map: top 60vh ── */
    #map {
        bottom: var(--m-zone-h) !important;
        height: auto !important;
    }

    /* ── Legend: horizontal pill at header level ── */
    /* 2026-10-14 (Nick: menu, legend, city and recenter "slightly off"): one
       top line and one 40px height for all four. The legend centres in the
       gap between the menu (16+40+8 px from the left) and the city button
       (16+40+8+40+8 px from the right) instead of on the screen, so it can't
       run into either; a title makes it taller, but its top stays on the line. */
    #map-legend {
        position: fixed !important;
        top: calc(3.5rem + env(safe-area-inset-top, 0px)) !important;
        left: 64px !important;
        right: 112px !important;
        bottom: auto !important;
        transform: none !important;
        width: fit-content !important;
        max-width: calc(100vw - 176px);
        margin: 0 auto !important;
        min-height: 40px;
        box-sizing: border-box;
        display: flex !important;
        flex-direction: column;
        justify-content: center;
        padding: calc(5px * var(--ui-scale)) calc(10px * var(--ui-scale)) !important;
        border-radius: 9999px !important;
    }
    #map-legend .legend-bar {
        flex-direction: row !important;
        align-items: center;
        gap: 6px;
    }
    #map-legend .legend-gradient {
        width: calc(72px * var(--ui-scale)) !important;
        height: calc(7px * var(--ui-scale)) !important;
        background: linear-gradient(to right, var(--grad-stops)) !important;
    }
    #map-legend .legend-title { display: none !important; }

    /* ── Tab bar: anchored to the TOP of the panel zone ──
       bottom: --m-zone-h minus its own height → top edge lands exactly at
       the map boundary. No gap, no seam. Phase K3 (2026-07-30): the row's
       own border-bottom moved down onto each individual .mobile-tab-btn
       instead (below) — a single full-width border here can't be "turned
       off" under just the active tab, which is what the folder-tab look
       needs. */
    #mobile-tab-bar {
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 2px;
        position: fixed;
        bottom: calc(var(--m-zone-h) - var(--m-tabbar-h));
        left: 0;
        right: 0;
        height: var(--m-tabbar-h);
        z-index: 1022;
        background: rgba(5, 7, 14, 0.98);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
        padding: 0 8px;
        transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
    }

    /* Panel drag-resize grabber (RELEASE_PLAN.md Phase J4, 2026-07-30; fixed
       + moved Phase K2, 2026-07-30). J4's `bottom: calc(var(--m-zone-h) -
       var(--m-tabbar-h))` was a genuine latent bug, not just badly placed:
       that formula is #mobile-tab-bar's OWN `bottom` value, i.e. the
       TAB BAR'S BOTTOM edge (its seam with the panel below) — verified via
       Playwright (`#map`'s bottom and `#mobile-tab-bar`'s top both measured
       at the SAME y; the grabber's pre-transform position measured a full
       `--m-tabbar-h` below that, at the tab bar's bottom, not its top).
       `translate(-50%, -50%)` centered it there, so half the hit box
       covered the tab row — Nick's real-device "interferes with the
       existing tab buttons" complaint. Fixed anchor: `bottom:
       var(--m-zone-h)` alone IS the true map/tab-bar boundary (`#map`'s
       `bottom` uses the same bare `--m-zone-h`) — this box's own
       pre-transform bottom edge already lands exactly there, so no Y
       translate is needed to clear the tab bar at all.
       Phase M3 (2026-07-30): `translate(-50%, calc(-100% - 4px))` was
       double-counting that — the box was ALREADY fully above the
       boundary before the transform, so the extra `-100%` lifted it a
       redundant 24px further, measured at a 38px gap between the visible
       pill and the tab bar (Nick: "the gap... should be closer to each
       other"). Fixed by dropping the Y-translate to 0 and instead
       aligning the pill to the box's own bottom edge (`align-items:
       flex-end` + 5px bottom padding on the box) — verified by live CSS
       injection to land the pill 5px above the tab bar, keeping the full
       24px hit box intact. `touch-action: none` is load-bearing: without
       it the browser claims the vertical gesture for scrolling and
       pointermove never fires usefully — the single most common way this
       feature gets gotten wrong. Dragging is bound ONLY to this element in
       app.js, never the panel body — the panel's own content scrolls and
       the map above has its own d3 pan/pinch; a whole-surface drag would
       fight both. */
    /* Phase S2 (RELEASE_PLAN.md, 2026-07-31): Nick — the grabber "needs to
       be a little more obvious or connect in a way." It used to be a bare
       pill floating in empty space above #mobile-tab-bar; the two elements
       are mathematically adjacent already (this box's `bottom: var(--m-
       zone-h)` is the exact same y-coordinate as the tab bar's own top
       edge — see the Phase K2/M3 history above), but with no shared
       background the seam between them was invisible, so the pill read as
       disconnected. Now shares the tab bar's own background/blur and drops
       the border where the two meet, so it reads as one continuous sheet
       with a small rounded tab poking up out of it — a real "grip handle"
       rather than a hint floating over the map. Height/hit-box unchanged
       (still the 24px K2/M3 established); only the paint changed. */
    #mobile-panel-grabber {
        position: fixed;
        left: 50%;
        bottom: var(--m-zone-h);
        transform: translate(-50%, 0);
        width: 72px;
        /* box-sizing is border-box here (Tailwind's global Preflight
           reset, not this file) — height already INCLUDES padding, so
           24px + 5px bottom padding keeps the same 24px hit box K2 set,
           just with 19px of it now above the pill instead of the pill
           being centered in the middle of it. */
        height: 24px;
        padding-bottom: 5px;
        z-index: 1023;
        display: flex;
        align-items: flex-end;
        justify-content: center;
        touch-action: none;
        cursor: ns-resize;
        /* Phase CA (2026-09-24): Nick wanted angled sides "like a manila
           folder tab". The tab's paint moved to ::before, cut to a
           trapezoid with rounded top corners; this box (the hit area)
           stays the same rectangle. A flat clip-path, not a 3D tilt:
           WebKit skewed the perspective() version into a parallelogram.
           The 1px edge is a drop-shadow, which follows the cut shape. */
        width: 88px;
        background: none;
        filter: drop-shadow(0 0 0.75px var(--grabber-bdr, rgba(255, 255, 255, 0.22)));
    }
    #mobile-panel-grabber::before {
        content: '';
        position: absolute;
        inset: 0;
        background: var(--grabber-bg, rgba(5, 7, 14, 0.98));
        clip-path: path('M0 24 L9.2 3.1 Q10.6 0 14 0 L74 0 Q77.4 0 78.8 3.1 L88 24 Z');
        pointer-events: none;
    }
    #mobile-panel-grabber::after {
        content: '';
        position: relative;
        width: 36px;
        height: 4px;
        /* Phase Y (2026-07-31): Nick — "the mobile pull tab margin... it
           just looks too close to the tabs." The grabber's own box
           (height/hit-target) stays the K2/M3-established 24px — see this
           block's own comment above on why that's load-bearing — this
           just nudges the pill itself up off the box's bottom edge a
           little further, adding daylight between the grip and
           #mobile-tab-bar's row right below without touching the hit box. */
        margin-bottom: 4px;
        border-radius: 2px;
        background: rgba(255, 255, 255, 0.28);
        transition: background 0.12s ease;
    }
    #mobile-panel-grabber.dragging::after {
        background: var(--accent-fill);
    }
    #mobile-panel-grabber.dragging {
        --grabber-bdr: var(--accent-border);
    }
    body.mobile-panel-expanded #mobile-panel-grabber { display: none; }

    /* Nick, 2026-07-20: tightened further ("take up less space", then "the
       header looks too tall, the button around the text is too big") —
       height dropped 44px→36px (the active-tab highlight pill filled the
       full row height, so a shorter row directly shrinks that pill instead
       of it towering over the small text). This trades a little off the
       44px touch-target guideline used elsewhere for tap-critical controls
       (pin/similar-county) — acceptable here since a tab bar's individual
       targets are wide (flex:1, ~93px each on a 375px screen) even if
       shorter, so mis-taps are unlikely. Expand now lives inline in each
       panel's own header (see leaderboard-view / county-details-view
       headers in index.html) instead of a separate floating row, so this
       bar only ever needs to fit its own 4 tabs. */
    .mobile-tab-btn {
        display: flex;
        flex: 1 1 0;
        align-items: center;
        justify-content: center;
        gap: calc(3px * var(--ui-scale));
        min-height: var(--m-tabbar-h);
        border-radius: calc(6px * var(--ui-scale));
        font-size: calc(8.5px * var(--ui-scale));
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.02em;
        /* Phase Y7 (2026-07-31): Nick — "the tab label colors are not dark
           enough" in light mode. Was `rgba(148,163,184,0.55)` — slate-400
           at 55% alpha, tuned to read as "dimmed" against the original
           near-black tab bar; on the light sheet that same wash is barely
           visible. `--txt-3` is the theme's own secondary-text tier, so
           it dims correctly in BOTH themes instead of relying on alpha. */
        color: var(--txt-3);
        background: transparent;
        border: 1px solid transparent;
        /* Phase K3 (2026-07-30): per-button divider (moved off the row,
           above) so the active tab can independently drop it — see
           .active below. */
        border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        cursor: pointer;
        transition: all 0.15s ease;
        white-space: nowrap;
        line-height: 1;
    }
    /* Phase K3 (2026-07-30): "manila folder" flat-merge look, locked via
       AskUserQuestion — tabs keep their current size/shape; only the
       ACTIVE one loses its bottom border and squares its bottom corners
       (border-radius 0 on the bottom two only, top two stay rounded) so it
       reads as flowing straight into the panel below rather than sitting
       as a separate rounded pill above a dividing line. Inactive tabs are
       untouched. */
    /* Phase AB (2026-08-02): same lightness-over-color swap as
       .rail-view-btn.active above. K3's flat-merge illusion (dropping the
       bottom border so the active tab flows into the panel below) never
       depended on matching background colors — verified screenshot-clean
       on its own back in K3 — so changing the fill here doesn't disturb it. */
    .mobile-tab-btn.active {
        color: var(--txt-1);
        background: rgba(255, 255, 255, 0.08);
        border-color: rgba(255, 255, 255, 0.12);
        border-bottom-color: transparent;
        border-radius: calc(6px * var(--ui-scale)) calc(6px * var(--ui-scale)) 0 0;
    }
    .mobile-tab-btn i { font-size: calc(9.5px * var(--ui-scale)); }

    /* ── Expanded: hide tab bar entirely so panel is full-screen below hamburger ── */
    body.mobile-panel-expanded #mobile-tab-bar {
        display: none !important;
    }

    /* ── Content panels: fill zone below tab bar, above category icon bar ──
       bottom = catbar height + safe, height = --m-panel-h (already nets
       out zone height minus tab bar minus category bar minus safe area —
       see the :root block at the top of this @media). All three panels
       below (#settings-panel, #county-details-drawer, #summary-panel)
       occupy the exact same rect and MUST stay identical — a mismatch
       shows as a jump when switching tabs. */

    #settings-panel {
        bottom: calc(var(--m-catbar-h) + env(safe-area-inset-bottom, 0px)) !important;
        left: 0 !important;
        right: 0 !important;
        border-radius: 0 !important;
        width: 100% !important;
        height: var(--m-panel-h) !important;
        max-height: none !important;
        overflow: hidden !important;
        transform: translateY(110%) !important;
        transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important;
        display: flex;
    }
    #settings-panel.open {
        transform: translateY(0) !important;
    }

    #county-details-drawer {
        bottom: calc(var(--m-catbar-h) + env(safe-area-inset-bottom, 0px)) !important;
        left: 0 !important;
        right: 0 !important;
        width: 100% !important;
        height: var(--m-panel-h) !important;
        max-height: none !important;
        overflow: hidden !important;
        border-radius: 0 !important;
        transform: translateY(110%) translateX(0) !important;
    }
    #county-details-drawer.open {
        transform: translateY(0) !important;
    }
    /* Desktop drawer-expanded is overridden on mobile via mobile-panel-expanded body class */
    #county-details-drawer.drawer-expanded {
        top: calc(56px + env(safe-area-inset-top, 0px)) !important;
        bottom: calc(52px + env(safe-area-inset-bottom, 0px)) !important;
        max-height: none !important;
        height: auto !important;
    }

    /* Mobile full-expand (Nick, 2026-07-20: "make it full screen until
       collapsed" — the previous expanded state left a sliver at top for the
       hamburger and at bottom for the category icon bar; this now covers the
       entire viewport, both of those included, until the same in-header
       button collapses it back). z-index raised above the hamburger
       (z-[1040]) and category bar (z-[1020]) so it's genuinely on top; all
       three of those plus the map's own recenter button are display:none'd
       outright rather than left to incidentally get covered by z-order —
       the recenter button acts on the map, which is itself fully hidden
       behind the panel while expanded, so it has nothing to do anyway. The
       only way out while expanded is the collapse button itself. */
    body.mobile-panel-expanded #county-details-drawer {
        top: 0 !important;
        bottom: 0 !important;
        left: 0 !important;
        right: 0 !important;
        height: auto !important;
        max-height: none !important;
        overflow: hidden !important;
        border-radius: 0 !important;
        transform: translateY(0) !important;
        z-index: 1050 !important;
        padding-top: env(safe-area-inset-top, 0px);
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }
    body.mobile-panel-expanded #hamburger-menu-container,
    body.mobile-panel-expanded #bottom-tabs-container,
    body.mobile-panel-expanded #mobile-recenter-btn {
        display: none !important;
    }

    #summary-panel {
        position: fixed !important;
        top: auto !important;
        bottom: calc(var(--m-catbar-h) + env(safe-area-inset-bottom, 0px)) !important;
        left: 0 !important;
        right: 0 !important;
        width: auto !important;
        min-width: unset !important;
        max-width: none !important;
        height: var(--m-panel-h) !important;
        max-height: none !important;
        border-radius: 0 !important;
        display: none !important;
        transform: none !important;
        overflow: hidden !important;
    }
    /* Summary scrolls within the fixed-height zone */
    #summary-panel #summary-content-list {
        max-height: none !important;
        overflow-y: auto !important;
        flex: 1;
        padding: 0 !important;
    }
    #summary-panel > .glass-panel > div:first-child {
        display: none !important;
    }
    #summary-panel > .glass-panel {
        border-radius: 0 !important;
        flex: 1;
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }

    /* ── Body-class driven panel visibility ── */

    body.mobile-tab-criteria #settings-panel {
        display: flex !important;
        transform: translateY(0) !important;
        pointer-events: auto !important;
    }
    body.mobile-tab-criteria #county-details-drawer,
    body.mobile-tab-criteria #summary-panel {
        display: none !important;
    }

    body.mobile-tab-standings #county-details-drawer,
    body.mobile-tab-metrics #county-details-drawer {
        display: flex !important;
        transform: translateY(0) !important;
        pointer-events: auto !important;
    }
    body.mobile-tab-standings #settings-panel,
    body.mobile-tab-metrics #settings-panel,
    body.mobile-tab-standings #summary-panel,
    body.mobile-tab-metrics #summary-panel {
        display: none !important;
    }

    body.mobile-tab-summary #summary-panel {
        display: flex !important;
        visibility: visible !important;
        opacity: 1 !important;
        pointer-events: auto !important;
    }
    body.mobile-tab-summary #settings-panel,
    body.mobile-tab-summary #county-details-drawer {
        display: none !important;
    }

    /* ── Metrics empty state (shown by JS when no county selected) ── */
    #metrics-empty-state {
        display: none;
    }
    #metrics-empty-state.visible {
        display: flex !important;
    }

    /* ── Standings: spacious full-width rows ── */
    #leaderboard-container > div {
        padding: calc(10px * var(--ui-scale)) calc(16px * var(--ui-scale)) !important;
        border-radius: 0 !important;
        gap: calc(12px * var(--ui-scale)) !important;
        border-bottom: 1px solid rgba(255,255,255,0.04) !important;
    }
    #leaderboard-container > div span:first-child {
        font-size: calc(11px * var(--ui-scale)) !important;
        width: calc(22px * var(--ui-scale)) !important;
    }
    #leaderboard-container > div span:nth-child(2) {
        font-size: calc(13px * var(--ui-scale)) !important;
        font-weight: 600 !important;
    }
    #leaderboard-container > div span:nth-child(3) {
        font-size: calc(13px * var(--ui-scale)) !important;
        font-weight: 900 !important;
    }

    /* Standings header: bigger, hide desktop-only buttons */
    #leaderboard-view > div:first-child {
        padding: calc(10px * var(--ui-scale)) calc(14px * var(--ui-scale)) !important;
    }
    #leaderboard-view > div:first-child span.text-\[9px\] {
        font-size: calc(11px * var(--ui-scale)) !important;
        font-weight: 900 !important;
    }
    #leaderboard-view [onclick="toggleDrawerExpand()"],
    #leaderboard-view [onclick="toggleCountyDetails(false)"] {
        display: none !important;
    }

    /* Filtered-out items: smaller indent is fine */
    #filtered-container > div {
        padding: calc(8px * var(--ui-scale)) calc(16px * var(--ui-scale)) !important;
        gap: calc(10px * var(--ui-scale)) !important;
    }

    /* Desktop zoom controls hidden */
    #zoom-controls { display: none !important; }

    /* Category icon bar: allow scaled-up active icons to show without clipping */
    #bottom-tabs-container {
        overflow: visible !important;
        padding-bottom: env(safe-area-inset-bottom, 0px) !important;
    }
    /* 7 labeled category buttons share the width evenly and shrink on narrow
       phones instead of overflowing off-screen. width/height override the
       static Tailwind w-11/h-11 (44px) on the button markup itself — those
       utility classes can't reference a CSS custom property, so --tap
       (RELEASE_PLAN.md Phase F5e: max(44px, 44px * --ui-scale), the 44px
       touch-target floor) has to win here instead.

       Deliberately `width:` not `min-width:` — with `flex: 1 1 0`
       (flex-basis:0), min-width would be strictly enforced even when 7 *
       44px doesn't fit the container, which it genuinely doesn't at the
       320px floor (iPhone SE1: ~304px available after padding / 7 =
       ~43.4px each), forcing the row 4px past the viewport edge instead
       (real horizontal-scroll risk, worse than a touch target). `width`
       lets flex-grow/shrink gracefully compress it instead — verified via
       Playwright at 320x568: buttons render 43.4x44px, 0.6px under the
       floor, with zero body overflow. Same class of accepted trade-off
       this file already makes for .mobile-tab-btn's 36px row height
       (comment above it) — negligible in practice, avoids a worse
       failure mode. */
    #bottom-tabs-container > button {
        flex: 1 1 0;
        min-width: 0;
        max-width: calc(54px * var(--ui-scale));
        width: var(--tap);
        height: var(--tap);
    }
    #bottom-tabs-container > button > span:not([id]) {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        max-width: 100%;
    }

    /* Mobile recenter button: upper right, below header */
    #mobile-recenter-btn {
        display: flex !important;
    }

    /* Recalc indicator position */
    #recalc-indicator {
        top: calc(var(--m-zone-h) - 56px) !important;
        margin-top: 0 !important;
    }

    /* Phase J1/J2 (RELEASE_PLAN.md): mobile rows have ~358px of width —
       far more than the desktop rail's measured minimum. The mobile
       problem was never room, it was the touch target's SIZE (48x14px,
       under the --tap 44px floor). Widen and grow it, scaled by
       --ui-scale like every other constant in this block, so it stays
       proportional across the phone size range instead of hardcoding one
       device's numbers.
       Phase M4 (2026-07-30): re-opened this trade-off on Nick's explicit
       request ("the editable field... looks off or excessively wide, when
       we only want to change the number") — 100px was sized to also hold
       the extreme-state text ("Any Limit"), which Phase M4's JS change
       (renderMetricValue()) moved into the prefix span instead, so this
       only ever needs to fit a number now. Measured (not guessed) via
       Playwright canvas.measureText at this rule's own 11px monospace
       font: the widest bare number across all 43 metrics' full slider
       range is "1,000" (avg_rent) at 34px — 34px + this rule's own 3px+3px
       padding + 4px slack = 44px, replacing the old 100px.
       min-height also drops from --tap (44px) to 32px — a DELIBERATE,
       documented departure from Phase F3's 44px touch-target rule, per
       Nick's report that the row felt too tall. Same trade-off this file
       already accepts for .mobile-tab-btn below (36px row height, "wide
       target, low mis-tap risk" reasoning) applies here too: the slider
       remains the coarse, large-target control for this same value, and
       this field is for precise numeric entry, not a primary tap target. */
    /* Phase R (2026-07-31): the fixed 44px basis is gone for the same
       reason the desktop 38px one is — see the base rule. It's content-
       sized by sizeMetricValueInput() now, which uses `ch` and therefore
       scales with this rule's own font-size automatically. Only the
       font-size and the touch min-height stay phone-specific. */
    .metric-row-value {
        flex: 0 0 auto;
        font-size: calc(11px * var(--ui-scale));
        min-height: calc(32px * var(--ui-scale));
    }
    /* Phase L (RELEASE_PLAN.md, 2026-07-30): the prefix/suffix spans need
       the same scaled font as the number input next to them — otherwise
       they'd render at the desktop 8px size, visually mismatched beside an
       11px-scaled number on a touch target sized for tapping. */
    .metric-value-prefix,
    .metric-value-suffix {
        font-size: calc(11px * var(--ui-scale));
    }
    /* Phase P (2026-07-30): the mobile twin of Phase N's 73px suffix
       reservation is removed for the same reason the desktop one is — see
       the base `.metric-value-suffix` comment. It produced the same dead
       space after short suffixes here, and Nick reported the misalignment
       on BOTH layouts ("the same core problem persists on both desktop and
       mobile layouts"). */
    /* Phase M4 (2026-07-30): tighter row padding/gap, measured by ablation
       against the 85px baseline row height (7 rows needed 594px inside a
       250px panel) — combined with the min-height reduction above, this
       lands the row at ~59-65px depending on device, reported and verified
       rather than assumed. Scoped to phone only; .metric-row itself
       (style.css, shared with desktop) is untouched. */
    .metric-row {
        padding: calc(3px * var(--ui-scale)) 0;
        row-gap: calc(2px * var(--ui-scale));
    }
}


/* Guided Tour (Phase 3b, REVAMP_PLAN.md) — spotlight "cutout" via a giant
   box-shadow rather than an actual DOM hole, so it works for any target
   shape/size without special-casing. Position/size set per-step in JS
   (showTourStep()) from the target's own getBoundingClientRect(). */
.tour-spotlight {
    position: fixed;
    border-radius: 12px;
    border: 2px solid var(--accent-fill);
    box-shadow: 0 0 0 9999px rgba(2, 6, 23, 0.82), 0 0 24px var(--accent-fill);
    transition: top 0.3s cubic-bezier(0.4, 0, 0.2, 1), left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        width 0.3s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    pointer-events: none;
}
/* Nick, 2026-08-01: "fix the tour in light and dark mode to match the
   themes of the other pages... this one is still an outlier." The dimming
   shadow above was deliberately left un-tokenized (see the comment below)
   for a reason that's still valid in dark mode — it's a scrim, not a
   surface — but it hardcoded ONE alpha (0.82, near-opaque near-black) for
   both themes, which is exactly the same bug just fixed for the 6 modal
   backdrops (#about-modal etc., further down this file): dark mode's own
   modals already use a ~0.8-alpha dark scrim, so 0.82 here matches them
   fine, but light mode's modals were deliberately lightened to a 0.45-
   alpha translucent wash so dimmed content stays visibly legible/colorful
   instead of the page going near-black — the tour never got that same
   treatment, so running it in light mode was the one remaining place the
   whole screen still crushed to near-black. Same alpha as those modals now. */
body.theme-light .tour-spotlight {
    box-shadow: 0 0 0 9999px rgba(15, 23, 42, 0.45), 0 0 24px var(--accent-fill);
}
/* Phase CB: multi-target spotlight — one outline around the union. */
.spotlight-shape { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
.spotlight-dim { fill: rgba(2, 6, 23, 0.82); }
body.theme-light .spotlight-dim { fill: rgba(15, 23, 42, 0.45); }
.spotlight-edge { fill: none; stroke: var(--accent-fill); stroke-width: 2; stroke-linejoin: round; }

/* Phase X (2026-07-31): background/border/text read from tokens now — this
   class was previously hardcoded dark-only, so light mode showed a
   near-black callout box sitting on an otherwise-light page (caught by
   screenshot, same shape of bug as the pre-X3 #d3-tooltip/#metric-info-
   popover). The dimming scrim above is intentionally NOT tokenized — it's
   a spotlight overlay meant to darken the rest of the page in both themes,
   not a surface. */
.tour-callout {
    position: fixed;
    z-index: 2;
    width: 280px;
    max-width: calc(100vw - 24px);
    background: var(--surf-1);
    border: 1px solid var(--bdr-2);
    border-radius: 14px;
    padding: 14px 16px 12px;
    box-shadow: var(--shadow-1);
    transition: top 0.3s cubic-bezier(0.4, 0, 0.2, 1), left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.tour-callout-text {
    font-size: 12px;
    line-height: 1.5;
    color: var(--txt-1);
    margin-bottom: 12px;
}
.tour-callout-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.tour-skip-btn {
    font-size: 10px;
    font-weight: 700;
    color: var(--txt-4);
    background: transparent;
    border: none;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 6px 4px;
}
.tour-skip-btn:hover { color: var(--txt-3); }
.tour-next-btn {
    font-size: 10px;
    font-weight: 900;
    color: white;
    background: var(--accent-fill);
    border: none;
    border-radius: 8px;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 7px 16px;
    transition: transform 0.12s ease;
}
.tour-next-btn:hover { transform: scale(1.04); }
/* Phase AU follow-up (2026-09-21): the criteria-hint callout has only one
   action ("Got it"), unlike the real tour's Skip+Next pair — .tour-callout-
   actions' own space-between would leave it stranded on the left. */
.criteria-hint-actions {
    justify-content: flex-end;
}
.tour-progress {
    margin-top: 8px;
    text-align: center;
    font-size: 9px;
    font-weight: 700;
    color: var(--txt-4);
    font-family: ui-monospace, monospace;
}

/* === Compare Counties table (rebuilt per Nick's ask, 2026-07-21): every raw
   metric grouped by the rail's 7 categories, info buttons on each metric,
   best-of-pinned value highlighted per row. The scroll container is
   #compare-content (overflow:auto), so the header row sticks to its top and
   the metric column sticks to its left edge — both need opaque backgrounds
   since the modal panel behind them is translucent glass. ===

   Light-mode fix (2026-08-01): every color below was a literal hex/rgba,
   not a Tailwind utility and not a token — invisible to both the light-
   mode allowlist mechanism further down this file AND to the token system,
   so the whole table stayed dark-mode-only regardless of theme (Nick:
   "all broken in lightmode"). Converted to the shared --surf/--txt/--bdr/
   --accent/--good tokens used everywhere else, so this table now follows
   the theme the same way every other panel does. The two sticky
   backgrounds (#compare-corner/#compare-col-head and #compare-metric-cell)
   are the one exception kept as an explicit override rather than a shared
   token: they must render fully OPAQUE (the table body scrolls underneath
   them) while --surf-1/--surf-2 are deliberately translucent glass in dark
   mode — light mode's --surf-1 is already solid opaque hex, so it's reused
   directly there; dark mode keeps its original literal, which was already
   tuned to look like --surf-1 composited over the ocean. */
.compare-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
}
.compare-corner,
.compare-col-head {
    position: sticky;
    top: 0;
    background: #0f172a;
    border-bottom: 1px solid var(--bdr-1);
    padding: 8px 10px;
    text-align: left;
    vertical-align: top;
}
body.theme-light .compare-corner,
body.theme-light .compare-col-head,
body.theme-light .compare-metric-cell {
    background: var(--surf-1);
}
.compare-corner {
    left: 0;
    z-index: 30;
    padding-left: 16px;
    font-size: 8px;
    font-weight: 900;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--txt-3);
}
.compare-col-head { z-index: 20; min-width: 118px; }
.compare-county-name {
    font-size: 11px;
    font-weight: 800;
    color: var(--txt-1);
    text-align: left;
    line-height: 1.25;
    cursor: pointer;
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
}
.compare-county-name:hover { color: var(--accent); }
.compare-head-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
}
.compare-match-pill {
    font-size: 9px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    color: var(--txt-2);
    background: var(--surf-2);
    border: 1px solid var(--bdr-1);
    border-radius: 999px;
    padding: 1px 7px;
    white-space: nowrap;
}
.compare-best-pill {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent-border);
}
.compare-unpin {
    font-size: 9px;
    color: var(--txt-4);
    background: none;
    border: none;
    padding: 2px 4px;
    cursor: pointer;
}
.compare-unpin:hover { color: var(--bad); }
.compare-cat-row td {
    padding: 14px 10px 5px 16px;
    font-size: 9px;
    font-weight: 900;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--txt-3);
    border-bottom: 1px solid var(--bdr-1);
    white-space: nowrap;
}
.compare-cat-row i { margin-right: 5px; font-size: 10px; }
/* A colspan cell spans the whole (wider-than-viewport) table, so sticky on
   the cell itself is a no-op — the inner label span is what pins to the
   scrollport's left edge during horizontal scroll. */
.compare-cat-label {
    display: inline-block;
    position: sticky;
    left: 16px;
}
.compare-metric-cell {
    position: sticky;
    left: 0;
    z-index: 10;
    background: #0f172a;
    font-size: 10px;
    font-weight: 600;
    color: var(--txt-3);
    padding: 5px 10px 5px 16px;
    white-space: nowrap;
    border-bottom: 1px solid var(--bdr-1);
}
.compare-val {
    font-size: 11px;
    font-weight: 600;
    color: var(--txt-2);
    padding: 5px 10px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    border-bottom: 1px solid var(--bdr-1);
}
.compare-best {
    color: var(--good);
    font-weight: 800;
    background: var(--good-soft);
}
.compare-footnote {
    font-size: 9px;
    color: var(--txt-4);
    margin: 12px 16px 16px;
    line-height: 1.6;
}
.compare-best-swatch {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 2px;
    background: var(--good);
    opacity: 0.5;
    margin-right: 3px;
    vertical-align: -1px;
}

/* === Local Activities icon badges (Phase AG, 2026-08-01; tooltip reveal
   added Phase AM follow-up round 2, 2026-08-02) — Nick: "make local
   activities into icon badges that say the name when hovered over or
   tapped on mobile," later: "it can show a tooltip, the pill doesnt need
   to expand on hover." Badge itself is now a FIXED-size icon-only pill,
   never resizing; the label floats above it as a small absolutely-
   positioned tooltip bubble instead of expanding the pill inline — same
   show/hide triggers as before (hover/focus for desktop, tap toggling
   .is-open for mobile — buildActivityBadges() in app.js still closes any
   sibling before opening the tapped one, unchanged). position:relative
   on the badge anchors the tooltip; z-index keeps it above sibling
   badges in the wrapped row. Tokens from the start, not Tailwind color
   literals — this is markup with no light-mode allowlist entry. === */
.activity-badge-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.activity-badge {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    background: var(--surf-2);
    border: 1px solid var(--bdr-1);
    color: var(--txt-2);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.activity-badge:hover,
.activity-badge:focus-visible,
.activity-badge.is-open {
    background: var(--accent-soft);
    border-color: var(--accent-border);
    color: var(--accent);
    outline: none;
    z-index: 1;
}
.activity-badge i { font-size: 10px; }
.activity-badge-label {
    position: absolute;
    bottom: calc(100% + 5px);
    left: 50%;
    transform: translateX(-50%) translateY(2px);
    background: var(--surf-1);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid var(--bdr-2);
    border-radius: 6px;
    box-shadow: 0 6px 18px -4px rgba(0, 0, 0, 0.6);
    padding: 3px 7px;
    font-size: 9px;
    font-weight: 700;
    color: var(--txt-1);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s ease, transform 0.12s ease;
}
.activity-badge:hover .activity-badge-label,
.activity-badge:focus-visible .activity-badge-label,
.activity-badge.is-open .activity-badge-label {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* === Landing page CTA hierarchy (Phase AJ/AK naming — actually AK,
   2026-08-01) — Nick: promote the tour over Explore, "it should be the
   amber accent that is everywhere else on the site." Primary uses the
   real --accent instead of the leftover indigo/purple gradient Phase
   X5's de-purple sweep never reached (this button and #welcome-start-btn,
   one screen later in the same flow, were the last two). Text color
   INVERTS per theme because --accent itself does (Phase X: dark's is a
   bright #fbbf24 that needs dark text; light's is a darker amber-700
   that needs light text — same two literal values already used
   everywhere else in the app, just applied to a fill instead of text
   here). Secondary is a full-contrast OUTLINE, deliberately not dimmed —
   a ghosted secondary reads as disabled, which would produce the
   opposite of the retention effect promoting the tour is trying for. === */
.landing-cta-primary {
    background: var(--accent-fill);
    color: #1e1b4b;
    border: none;
}
body.theme-light .landing-cta-primary {
    color: #ffffff;
}
.landing-cta-primary:hover {
    filter: brightness(1.08);
}
.landing-cta-secondary {
    background: transparent;
    border: 1px solid var(--bdr-2);
    color: var(--txt-1);
}
.landing-cta-secondary:hover {
    background: var(--surf-2);
}

/* === "More counties like this" (Phase 7a) — replaces the ranked Standings
   list entirely while active; .similar-mode-banner is the only thing
   updateLeaderboard()'s normal path never renders, so it doubles as a
   visual "you're in a different mode now" cue. === */
.similar-mode-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 8px;
    margin-bottom: 4px;
    background: var(--accent-soft);
    border: 1px solid var(--accent-border);
    border-radius: 7px;
}
.similar-mode-title {
    font-size: 10px;
    font-weight: 700;
    color: var(--accent);
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
}
.similar-mode-title i { color: var(--accent); flex: none; }
.similar-mode-title strong { color: var(--txt-1); font-weight: 800; }
.similar-mode-exit {
    flex: none;
    font-size: 9px;
    font-weight: 700;
    color: var(--accent);
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 999px;
    padding: 3px 8px;
    cursor: pointer;
    white-space: nowrap;
    transition: color 0.12s ease, background 0.12s ease;
}
.similar-mode-exit:hover { color: var(--txt-1); background: rgba(255, 255, 255, 0.12); }

/* ============================================================================
   Phase U2 (RELEASE_PLAN.md, 2026-07-31) — county panel percentile-bar tiles.
   Nick: "each section is essentially a standard info graphic... make it
   easily revertable if I dont like it." Entirely gated on `body.county-
   infographic`, set once at boot from config.js's COUNTY_INFOGRAPHIC_STYLE
   constant (app.js's createMetricRowElement() checks the same constant
   before ever emitting a `.metric-pct-tile`). To revert: flip that one
   constant to `false` — this whole block then matches nothing, and the
   original plain-value tile markup (still intact, unmodified, in app.js)
   is what renders instead. To remove entirely: delete this block, the
   constant, the body-class toggle in app.js's DOMContentLoaded handler,
   and the `if (pct !== null)` branch in createMetricRowElement — nothing
   elsewhere references any of it.
   ============================================================================ */
/* Nick (2026-07-31): "make sure the spacing on these tiles looks
   professional... center info vertically and horizontally." The tile
   itself is centered via Tailwind utilities on the element
   (items-center/justify-center/text-center, app.js); this track needs an
   explicit width because an empty <div> (its marker child is
   position:absolute, so it contributes no intrinsic size) has zero
   natural width, and a `flex column` parent with `align-items:center`
   would otherwise center it down to nothing instead of stretching it. */
body.county-infographic .metric-pct-track {
    position: relative;
    width: 100%;
    height: 4px;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--bdr-1), var(--bdr-2));
}
body.county-infographic .metric-pct-marker {
    position: absolute;
    top: 50%;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent-fill);
    box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
    transform: translate(-50%, -50%);
}
body.county-infographic .metric-pct-caption {
    font-size: 8px;
    font-weight: 700;
    color: var(--txt-3);
    letter-spacing: 0.01em;
    white-space: nowrap;
}
/* The shared target-annotation line (targetHtml, also used by the plain
   fallback tile) is `flex items-center gap-1` — left-aligned internally by
   design there, but here it should read as one centered row like every
   other line in the tile rather than hugging the tile's left edge. */
body.county-infographic .metric-pct-tile > div:not(.metric-pct-track) {
    justify-content: center;
}

/* ============================================================================
   Phase V (RELEASE_PLAN.md, 2026-07-31) — light mode.
   Nick: "lets try out a light mode toggle... light blue 'ocean' around the
   county map... sidebar and general information panels would be a more
   greyish color." Scoped, per Nick's own choice among the options
   presented, to MAP + MAIN PANELS — the criteria rail (#settings-panel),
   the county/standings drawer (#county-details-drawer), and the persistent
   map chrome (top header bar, zoom controls, hamburger menu, legend,
   selection summary). Modals, the guided tour, the landing/welcome layers,
   and the exported PNG are explicitly NOT converted — best-effort only,
   see V2's note on the export.

   Two different mechanisms are doing the work here, for two different
   reasons:
   1. The existing `--color-bg`/`--color-surface`/`--color-border`/
      `--color-text-*` custom properties (:root, top of this file) already
      drive `body`'s own background AND every `.glass-panel` (which
      #settings-panel and the drawer's cards already use) — redefining
      them under `body.theme-light` is enough to turn the map's "ocean"
      light blue for free, since #map never paints its own background; it
      only ever shows body's background through the SVG. No new selector
      needed for that part.
   2. Almost everything ELSE visible inside these panels uses HARDCODED
      Tailwind utility classes (`bg-slate-900`, `text-slate-400`, `border-
      white/10`, …) — literal color values baked in at Tailwind's PRECOMPILE
      step (this repo ships a static `tailwind.css`, no runtime JIT; see
      index.html's own comment on that), so no `:root` change can reach
      them. These need explicit, scoped overrides. Rather than hardcoding a
      light color into every single override rule (unmaintainable), each
      rule references one of a small set of `--lm-*` tokens defined once
      below — retuning the light theme later means editing those, not
      hunting through every rule.
   Every override below is scoped to the in-scope containers by selector
   (never a bare `.bg-slate-900` with no ancestor), so it cannot leak into
   an out-of-scope surface that happens to share the same Tailwind class.
   ============================================================================ */
body.theme-light {
    --color-bg: #dbeafe;              /* light blue "ocean" behind the map */
    --color-surface: rgba(255, 255, 255, 0.82);
    --color-surface-hover: rgba(255, 255, 255, 0.95);
    --color-border: rgba(15, 23, 42, 0.1);
    --color-border-active: rgba(15, 23, 42, 0.22);
    --color-text-primary: #0f172a;
    --color-text-secondary: #475569;
    --color-text-muted: #64748b;

    /* Light-mode equivalents for the hardcoded Tailwind utilities used
       inside the in-scope panels — see the big comment above. Phase X
       (2026-07-31) retired the old lm-surface / lm-text / lm-border
       tokens in favor of the shared surf, txt and bdr-1 tokens defined
       above, since nothing outside this block ever referenced --lm-*.
       --lm-overlay has no equivalent in the new token set (a light wash
       for hover/overlay states, not a surface tier) so it stays. */
    --lm-overlay: rgba(15, 23, 42, 0.05); /* replaces bg-white/5, /10 */

    /* Phase X token foundation, light values. NOT just lighter versions of
       the dark :root block — --accent/--good/--bad get their OWN literal
       values here because reusing the dark-tuned hex fails contrast on a
       white panel: #fbbf24 (dark's --accent) on #ffffff is ~1.6:1, while
       #b45309 is ~5.9:1. Same reasoning for --good/--bad. */
    --surf-0: #dbeafe;                      /* KEEP: Nick's map background */
    /* Phase Y (2026-07-31): Nick — "there is too much white when it can
       be a sharp looking grey scale." --surf-1 came off pure #ffffff so
       the WHOLE ladder reads as grey-scale, not white-with-grey-steps.
       Phase AA (2026-08-01): Nick — the rail still read as "white on
       white." The Y values (f1f5f9/dde3ea/c3ccd6) all carry the same
       slight cool/blue cast as the ocean behind them (R<G<B on every one),
       so panel and page blended into one pale wash instead of reading as
       distinct grey chrome. Retuned to genuinely NEUTRAL greys (R≈G≈B)
       with a bigger step between each tier, so the ladder reads as
       "grey-scale" against the blue ocean rather than "paler blue." */
    /* Phase AQ (2026-09-12): Nick — "light mode kinda looks like vibe coded
       slop... let's play around with the aesthetic there to give it some
       more life." Diagnosed (not guessed): dark mode's --surf-1/2/3 are
       TRANSLUCENT rgba over --surf-0, composited with .glass-panel's
       existing `backdrop-filter: blur(16px)` — that's the app's whole
       glassmorphism identity. These three were OPAQUE hexes, so the same
       blur rule had nothing translucent to blur; opaque backgrounds fully
       hide whatever's behind them, making the blur a no-op. That is the
       single biggest cause Nick's "vibe coded" read traces to.
       Fix: the exact same R/G/B Phase AA/Y already validated (e7e9ec /
       d3d6da / b5b9bf — chosen specifically to fix a real "white on white"
       complaint; NOT reopening that by changing hue), now at dark mode's
       own alpha steps (0.85 / 0.60 / 0.55) instead of opaque. .glass-panel
       needed zero changes — its blur was already correct and unconditional;
       only the token VALUES were the problem. Verified: AA's own pixel-
       sample method (rail vs. map) re-run after this change, still reads as
       distinct grey chrome, not blended — see the Phase AQ status log entry
       for the measured delta. */
    --surf-1: rgba(231, 233, 236, 0.85);    /* panel base — neutral grey, glass */
    --surf-2: rgba(211, 214, 218, 0.60);    /* nested card / toolbar — visible step */
    --surf-3: rgba(181, 185, 191, 0.55);    /* inset — visible step */
    --bdr-1:  rgba(15, 23, 42, 0.16);
    --bdr-2:  rgba(15, 23, 42, 0.30);
    --txt-1:  #0f172a;
    --txt-2:  #334155;
    --txt-3:  #64748b;
    --txt-4:  #94a3b8;
    /* Phase AQ (2026-09-12): Nick — "the brand color changes identity
       between themes... let's give light mode its own life." The ORIGINAL
       fix here (Phase X) was correct about the PROBLEM (#fbbf24 on white is
       ~1.6:1, real contrast failure) but overcorrected the SOLUTION — it
       swapped --accent to #b45309 everywhere, including the ~20 places
       accent is a background/border/glow/outline rather than text. A fill
       doesn't have a text-contrast obligation the way a glyph does, so
       every one of those places lost the app's actual brand color for no
       reason — that's the "vibe coded" drift Nick named. --accent stays
       #b45309 (still needed for the ~20 real TEXT usages); --accent-fill is
       the bright #fbbf24 used everywhere else, matching dark mode exactly
       so the score gradient, chips, and highlight rings finally look like
       the SAME app in both themes. */
    --accent:        #b45309;               /* amber-700 — readable on white, TEXT only */
    --accent-fill:   #fbbf24;               /* bright amber — fills/borders/indicators, same as dark */
    --accent-soft:   rgba(245, 158, 11, 0.20);
    --accent-border: rgba(180, 83, 9, 0.38);
    --good:      #047857;  --good-soft: rgba(5, 150, 105, 0.14);
    --bad:       #be123c;  --bad-soft:  rgba(190, 18, 60, 0.12);
    /* Phase AQ (2026-09-12): a single soft shadow reads as "flat" —
       "nothing floats" was AQ0's finding #3. Two-layer contact+lift shadow
       (a tight, near-black-alpha shadow for where the panel actually meets
       the page, plus a broader, softer one for ambient lift), same
       technique real UI shadow systems use for depth on a light ground.
       Warmed very slightly (a touch of red/amber in the tint, not pure
       slate) rather than a cool grey shadow, per AQ1's "warmer shadows" —
       subtle enough it doesn't fight the neutral surf ladder AA/Y tuned. */
    --shadow-1: 0 1px 3px rgba(30, 20, 10, 0.09), 0 12px 28px rgba(30, 20, 10, 0.13);

    /* Priority scale (see :root above for the palette reasoning) — same
       darker-for-contrast-on-white treatment as --accent/--good/--bad get
       in this block, P1/P3/P5 reuse those exact readable-on-white tones. */
    --priority-1: #be123c;  --priority-1-soft: rgba(190, 18, 60, 0.12);   --priority-1-border: rgba(190, 18, 60, 0.38);
    --priority-2: #c2410c;  --priority-2-soft: rgba(194, 65, 12, 0.12);   --priority-2-border: rgba(194, 65, 12, 0.38);
    --priority-3: #b45309;  --priority-3-soft: rgba(245, 158, 11, 0.20); --priority-3-border: rgba(180, 83, 9, 0.38);
    --priority-4: #4d7c0f;  --priority-4-soft: rgba(77, 124, 15, 0.12);  --priority-4-border: rgba(77, 124, 15, 0.38);
    --priority-5: #047857;  --priority-5-soft: rgba(5, 150, 105, 0.14);  --priority-5-border: rgba(5, 150, 105, 0.38);
}

/* The map "ocean" is NOT actually body's background showing through, as
   first assumed — initMap() (app.js) sets an INLINE `style="background:
   ..."` (var(--surf-0)'s literal, kept in sync by hand — see Phase AH)
   directly on the `<svg>` element at creation time, so body's
   own (correctly light) background was there the whole time, just hidden
   behind this opaque SVG sitting on top of it. Inline styles beat a plain
   stylesheet rule, but not an `!important` one, which is the only reason
   this single exception needs `!important` outside the Tailwind-utility-
   override rules below (those need it because Tailwind's precompiled
   classes are also effectively "locked in"; this one is different — a
   JS-set inline style — but the fix is the same shape). Caught by a
   screenshot showing the map still solid black while every panel around
   it had correctly turned light. */
body.theme-light #map svg {
    background: var(--color-bg) !important;
}

/* Three more hardcoded, non-Tailwind, non-`--color-surface` backgrounds
   found the same way as the map SVG above — by screenshot, since a
   mobile-viewport pass still showed the bottom sheet and its tab bar solid
   black after every panel around them had converted. None of these are
   reachable by the Tailwind-utility overrides below, because none of them
   ARE Tailwind utilities:
   - `#settings-panel` (style.css ~L296, the phone-width base rule) sets its
     own literal `background: rgba(17, 24, 39, 0.85)` — an ID selector,
     which beats `.glass-panel`'s class-selector `background: var(--color-
     surface)` on specificity alone, so redefining the variable never had a
     chance to reach this element's mobile background.
   - `#mobile-tab-bar` and `#mobile-panel-grabber` (Phase S2) share a
     hand-picked `rgba(5, 7, 14, 0.98)` specifically so the two read as one
     continuous surface — reusing that same intent here with the light
     token instead of a fresh color keeps them still matching each other. */
body.theme-light #settings-panel {
    background: var(--surf-1) !important;
}
body.theme-light #mobile-tab-bar {
    background: var(--surf-1) !important;
}
body.theme-light #mobile-panel-grabber {
    --grabber-bg: #ffffff;   /* paints ::before, the folder tab (Phase CA) */
    --grabber-bdr: rgba(15, 23, 42, 0.18);
}
/* #rail-view-switcher (style.css ~L427) is the same shape of bug as
   #settings-panel above — its own hardcoded `background: rgba(15, 23, 42,
   0.9)` on the ID itself, not a Tailwind class, so no [class*="bg-slate-*"]
   override below can ever reach it. Found by tools/audit_theme.js CHECK 1. */
body.theme-light #rail-view-switcher {
    background: var(--surf-2) !important;
}
/* Nick: "the pull tab icon is whited out now." The grip pill
   (#mobile-panel-grabber::after) is `rgba(255,255,255,0.28)` — a light
   mark meant to stand out against the ORIGINAL near-black grabber; once
   the grabber's own background became near-white above, that same pill
   was white-on-white and read as gone. Needs a dark mark instead.
   Phase Y (2026-07-31): Nick — "the drag pull icon is too light, it
   should be a darker grey" — bumped 0.28 -> 0.5 alpha, still short of
   solid black so it reads as "grey grip", not a harsh line. */
body.theme-light #mobile-panel-grabber::after {
    background: rgba(15, 23, 42, 0.5) !important;
}
/* Phase X5 (2026-07-31): dragging state no longer needs a light-mode
   override -- the base #mobile-panel-grabber.dragging::after rule now
   reads `var(--accent)`, which already resolves to the theme-correct
   amber. The old override here was itself a hardcoded indigo. */

/* Reusable "this container OR anything inside it" selector, defined once
   via :is() so every rule below only has to be written once instead of
   once-per-container. ⚠️ A first version of this block used a plain
   descendant combinator per container (`#top-header-bar [class*="..."]`),
   which only matches DESCENDANTS — it silently missed every container that
   carries its own dark background class directly on itself rather than on
   a child (#top-header-bar, #map-legend both do: `bg-slate-950/90` and
   `bg-slate-950/80` sit right on the container element). Caught by
   `getComputedStyle()` on the actual element (still `oklab(0.129 ...)`,
   Tailwind v4's dark slate, after toggling the theme) and by screenshot —
   the header bar and legend stayed visibly dark while everything else
   converted. `:is(#id, #id *)` matches the container itself AND every
   descendant, so a class on either one is caught the same way. */
/* Phase X4 (RELEASE_PLAN.md, 2026-07-31): every rule below shares ONE
   container list — CSS has no selector variables, so the same 21
   containers (both `#id` and `#id *`, see the comment above on why both
   forms are needed) are repeated in full on every rule rather than kept as
   per-rule subsets. A container with no matching class just matches
   nothing extra; this is simpler to maintain than proving which subset
   each Tailwind family actually needs. Expanded from 11 to 21 containers
   this phase: added #rail-view-switcher, #appearance-panel (the Options
   panel), #summary-panel, #about-modal, #share-modal, #welcome-modal,
   #landing-layer, #tour-callout, #metric-info-popover, #d3-tooltip — none
   of these were ever in scope before, so they stayed dark in light mode.

   ⚠️ Phase Y7 (2026-07-31): every utility matcher below is the anchored
   pair `:is([class^="x"], [class*=" x"])`, NOT a bare `[class*="x"]`.
   That is load-bearing. A bare substring match also matches Tailwind's
   PREFIXED VARIANTS — `[class*="text-amber-"]` happily matches
   `hover:text-amber-400` — so every button carrying a hover: color class
   had that color applied UNCONDITIONALLY in light mode, not just on
   hover. 27 such variant classes exist across index.html/app.js, which is
   why the hamburger menu's four entries collapsed into near-identical
   tinted labels (Nick: "Options 60 second guide about&date are all too
   similar colors"). Anchoring to "start of attribute, or preceded by a
   space" means only the real, unprefixed class matches, since a variant
   is always preceded by `:` instead. Keep this shape for any new matcher.

   Compare Counties fix (2026-08-01): Nick — "the compare counties panel
   is all broken in lightmode." `#compare-modal`/`#compare-content` were
   never added to this container list, so its static markup (header
   title/subtitle, close buttons, the "no counties pinned" placeholder)
   kept every dark-tuned Tailwind literal forever — same bug class as
   every other container above. Added both here. This alone does NOT fix
   the actual comparison table: buildCompareTable() (app.js) renders
   `.compare-*` classes with their own hardcoded hex colors (`#0f172a`,
   `#fff`, `#94a3b8`, etc., not Tailwind utilities at all), which this
   allowlist mechanism cannot touch by construction — those are converted
   to theme tokens directly at their own rule definitions below instead.

   Phase AL (2026-08-02): #contribute-modal added the same way, from the
   start rather than as a follow-up fix — the Request card intentionally
   uses `text-emerald-`/`bg-emerald-`/`border-emerald-` instead of cyan
   specifically BECAUSE emerald's whole family is already covered here
   (verified via contrast check: text-cyan-400 measured 1.8:1 on white,
   nowhere near legible, and cyan isn't in this list at all — rather than
   widening the allowlist to a color used in ~15 other places across the
   app with its own unaudited light-mode implications, the new element's
   own color choice was made to fit what's already safely covered). */
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-slate-950"], [class*=" bg-slate-950"]) {
    background-color: var(--surf-1) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-slate-900"], [class*=" bg-slate-900"]) {
    background-color: var(--surf-2) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-slate-800"], [class*=" bg-slate-800"]) {
    background-color: var(--surf-3) !important;
}
/* Semantically tinted surfaces (Phase V never converted these — root
   cause #2 in the diagnosis). */
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-emerald-950"], [class*=" bg-emerald-950"]) {
    background-color: var(--good-soft) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-indigo-950"], [class*=" bg-indigo-950"]) {
    background-color: var(--surf-2) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-pink-950"], [class*=" bg-pink-950"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-rose-"], [class*=" bg-rose-"]) {
    background-color: var(--bad-soft) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-amber-500"], [class*=" bg-amber-500"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-indigo-500"], [class*=" bg-indigo-500"]) {
    background-color: var(--accent-soft) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-black"], [class*=" bg-black"]) {
    background-color: var(--surf-3) !important;
}
/* Text families replace the deleted blanket color: rule (X2) — each maps
   to its MEANING rather than a blind grey conversion, which is the whole
   point (amber stays amber, grey inverts). */
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-white"], [class*=" text-white"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-slate-100"], [class*=" text-slate-100"]) {
    color: var(--txt-1) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-slate-200"], [class*=" text-slate-200"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-slate-300"], [class*=" text-slate-300"]) {
    color: var(--txt-2) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-slate-400"], [class*=" text-slate-400"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-slate-500"], [class*=" text-slate-500"]) {
    color: var(--txt-3) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-slate-600"], [class*=" text-slate-600"]) {
    color: var(--txt-4) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-amber-"], [class*=" text-amber-"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-yellow-"], [class*=" text-yellow-"]) {
    color: var(--accent) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-emerald-"], [class*=" text-emerald-"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-green-"], [class*=" text-green-"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-teal-300"], [class*=" text-teal-300"]) {
    color: var(--good) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-rose-"], [class*=" text-rose-"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-pink-"], [class*=" text-pink-"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-red-"], [class*=" text-red-"]) {
    color: var(--bad) !important;
}
/* indigo/purple text -> accent, EXCEPT the category icons inside the
   county detail panel's active-metrics list — those keep their identity
   colors (X5 recolors only the two purple-family ones, Education and
   Community; the rest stay as-is in both themes). */
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-indigo-"], [class*=" text-indigo-"]):not(#detail-active-metrics *),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="text-purple-"], [class*=" text-purple-"]):not(#detail-active-metrics *) {
    color: var(--accent) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="border-white"], [class*=" border-white"]) {
    border-color: var(--bdr-1) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="border-indigo-"], [class*=" border-indigo-"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="border-purple-"], [class*=" border-purple-"]) {
    border-color: var(--accent-border) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="border-emerald-"], [class*=" border-emerald-"]) {
    border-color: var(--good) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="border-rose-"], [class*=" border-rose-"]),
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="border-pink-"], [class*=" border-pink-"]) {
    border-color: var(--bad) !important;
}
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #top-header-bar, #top-header-bar *, #zoom-controls, #zoom-controls *, #hamburger-menu-container, #hamburger-menu-container *, #hamburger-dropdown, #hamburger-dropdown *, #map-legend, #map-legend *, #selection-summary-panel, #selection-summary-panel *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *, #compare-modal, #compare-modal *, #compare-content, #compare-content *, #contribute-modal, #contribute-modal *, #request-modal, #request-modal *, #welcome-modal, #welcome-modal *, #landing-layer, #landing-layer *, #tour-callout, #tour-callout *, #metric-info-popover, #metric-info-popover *, #d3-tooltip, #d3-tooltip *):is([class^="bg-white/"], [class*=" bg-white/"]) {
    background-color: var(--lm-overlay) !important;
}
/* Hover states on these surfaces use hover:bg-white/10 etc. — same
   overlay token, just triggered on :hover instead of unconditionally. */
body.theme-light :is(#settings-panel, #settings-panel *, #mobile-tab-bar, #mobile-tab-bar *, #mobile-panel-grabber, #mobile-panel-grabber *, #bottom-tabs-container, #bottom-tabs-container *, #county-details-drawer, #county-details-drawer *, #hamburger-dropdown, #hamburger-dropdown *, #rail-view-switcher, #rail-view-switcher *, #appearance-panel, #appearance-panel *, #summary-panel, #summary-panel *, #about-modal, #about-modal *, #share-modal, #share-modal *)[class*="hover:bg-white"]:hover {
    background-color: var(--lm-overlay) !important;
}

/* Phase X7 (RELEASE_PLAN.md, 2026-07-31) — light-mode depth pass. Nick:
   "go with more shading in the side bars and panels." Everything above
   made light mode correctly colored; this section makes it correctly
   SHADED. The rail's `.rail-section-header`/`.rail-section-body` cards
   and the county panel's metric tiles use white-alpha washes
   (rgba(255,255,255,0.03) etc.) sized for a dark base -- on a WHITE panel
   that wash is invisible, so category cards and metric tiles read as one
   flat white surface with only a hairline border. These give each its own
   explicit surface tier instead of relying on a wash. */
/* ⚠️ Scoped to >=600px deliberately (Phase Y7, 2026-07-31). The BASE
   `.rail-section-header`/`.rail-section-body` card treatment these
   override lives entirely inside this same `@media (min-width: 600px)`
   block — below 600px those elements have no card background at all and
   are meant to sit flush on the panel. Phase X7 declared these two
   overrides OUTSIDE any media query, so the desktop-only card shading
   leaked onto mobile, painting a --surf-2 slab behind the metric rows
   that the phone layout never intended. Nick: "the darker grey background
   is too dark. that should blend into the entire panel background color
   on mobile as well." */
/* Phase AA (2026-08-01): Nick, on the shipped build — the sidebar still
   read as "white on white." Root cause: this rule painted the card the
   SAME --surf-2 as the panel shell one level up (#settings-panel uses
   --surf-1, its toolbar/rail-view-switcher use --surf-2, and this rule put
   the category cards at that identical --surf-2 too) — three different
   containers, two distinct colors, so the card never had its own tier to
   stand on. Flipped the relationship: cards are now crisp WHITE with a
   visible border, popping OFF the grey --surf-1 shell instead of blending
   toward it — the standard "grey canvas, white card" dashboard look Nick
   asked for ("greyscale and professional"), and an unmistakable boundary
   instead of one flat pale wash.
   Phase AB (2026-08-02): Nick — "make the UI subtle... compare with typical
   light themes of websites." The first pass leaned on --bdr-2 (0.30 alpha)
   for definition, heavier than the hairline borders typical light-mode
   dashboards use (Stripe/Linear/Vercel-style: a soft shadow carries most of
   the separation, the border is barely-there). Backed off to --bdr-1 (0.16)
   plus a small shadow for the same amount of visual separation with less
   visual weight — --bdr-2 itself was left untouched since 12 other,
   unrelated rules across both themes already rely on it. */
@media (min-width: 600px) {
    body.theme-light .rail-section-header {
        background: #ffffff;
        border-color: var(--bdr-1);
        box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
    }
    body.theme-light .rail-section-body:not(.rail-collapsed) {
        background: #ffffff;
        border-color: var(--bdr-1);
        box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
    }
}
/* County detail panel's metric tiles (createMetricRowElement()'s
   percentile-bar tile and the plain fallback tile, app.js) default to
   `bg-slate-950/40` when no target is set -- the generic bg-slate-950
   family rule above maps that to --surf-1 (correct for the PANEL shell),
   but these tiles are nested ONE level deeper and need --surf-2 to read
   as a card sitting on the panel rather than a second identical panel.
   Higher specificity (ID + attribute) than the generic family rule further
   up this file, and declared after it, so it wins the tie either way. */
body.theme-light #detail-active-metrics :is([class^="bg-slate-950"], [class*=" bg-slate-950"]),
body.theme-light #detail-inactive-metrics :is([class^="bg-slate-950"], [class*=" bg-slate-950"]) {
    background-color: var(--surf-2) !important;
}

/* Phase Y7 (2026-07-31): #top-header-bar is "the darkest shade of grey on
   the screen" (Nick) — its own dedicated dark background, with the
   wordmark pinned light-on-dark against the general text-white -> --txt-1
   mapping the rest of light mode wants.

   ⚠️ Both selectors below carry an [class*=] attribute qualifier for a
   REASON, not decoration. Phase Y's first attempt used a bare
   `body.theme-light #top-header-bar` (0,1,1,1) and silently lost to the
   generic family rule further up this file — `body.theme-light :is(…,
   #top-header-bar, …):is([class^="bg-slate-950"], [class*=" bg-slate-950"])`, which scores (0,1,2,1)
   because `:is()` inherits its most specific argument's ID weight and the
   attribute selector adds another class-level point. Both are
   `!important`, so specificity decided it and the header stayed
   --surf-1 white — with white-on-white wordmark text, since the text rule
   below DID win its own race. Caught by screenshot on mobile ("the top
   banner is not dark. the logo is white on white, it doesnt work").
   Matching the attribute qualifier here levels the specificity at
   (0,1,2,1), and being last in the file wins the tie by source order. */
body.theme-light #top-header-bar:is([class^="bg-slate-950"], [class*=" bg-slate-950"]) {
    background-color: #334155 !important;
}
body.theme-light #top-header-bar :is([class^="text-white"], [class*=" text-white"]) {
    color: #f9fafb !important;
}

/* Nick, 2026-08-01: "the light mode how it works and data sources is too
   white... make it match the rest of the site." Root cause: #about-modal
   (and every other full-screen modal backdrop — #share-modal,
   #compare-modal, #contribute-modal, #welcome-modal, #landing-layer) all
   carry `bg-slate-950/80` (or /85) DIRECTLY on themselves as the dimming
   scrim behind the modal card, same shape of bug as #top-header-bar above
   (a container's OWN class, not a descendant's). The generic family rule
   further up matches that class and replaces it with opaque `var(--surf-1)`
   — same flat color as the card itself sitting on top of it — so in light
   mode the scrim and the card became visually identical and the whole
   screen read as one undifferentiated pale wash, not "a card floating over
   a dimmed page" the way it does (and always did) in dark mode. A
   translucent dark scrim, same convention as #mobile-panel-grabber::after
   above, keeps the same "dim what's behind the card" effect light mode UIs
   also expect, rather than trying to invert it to a light-tinted scrim. */
body.theme-light :is(#about-modal, #share-modal, #compare-modal, #contribute-modal, #request-modal, #welcome-modal, #landing-layer):is([class^="bg-slate-950"], [class*=" bg-slate-950"]) {
    background-color: rgba(15, 23, 42, 0.45) !important;
}

/* Hamburger menu entry icons (Phase Y7, 2026-07-31). Nick: "Options 60
   second guide about&data are all too similar colors." Two separate
   causes: (1) the hover-variant matching bug documented further up tinted
   the LABELS as well as the icons, and (2) "60-Second Guide" and "About &
   Data" were both literally text-amber-400, so even once the labels were
   fixed those two icons stayed identical. Four distinct hues assigned as
   dedicated classes rather than Tailwind utilities, precisely so the
   light-mode family collapse (which folds amber/indigo/purple into one
   --accent and emerald/green/teal into one --good) can't flatten them
   back together — same reasoning as the category icons' exemption. Each
   gets its own per-theme value, since the dark-mode 400-weight hues are
   too light to read against the light dropdown. */
.hm-ico-options { color: #38bdf8; }   /* sky     — settings / controls */
.hm-ico-guide   { color: #fbbf24; }   /* amber   — the tour CTA        */
.hm-ico-about   { color: #34d399; }   /* emerald — info / data         */
.hm-ico-share   { color: #f472b6; }   /* pink    — share / bookmark    */
body.theme-light .hm-ico-options { color: #0369a1; }
body.theme-light .hm-ico-guide   { color: #b45309; }
body.theme-light .hm-ico-about   { color: #047857; }
body.theme-light .hm-ico-share   { color: #be185d; }

/* (Old Data Sources .src-* card system removed in Phase BS; see the About-modal block near the end of this file.) */

/* ============================================================================
   Areas — geographic scoping panel (RELEASE_PLAN.md Phase Z1, 2026-07-31).
   Nick: "a clever way of excluding areas like counties or particular states.
   An intuitive filter somewhere."

   Originally lived as the first section of the criteria rail, reusing
   .rail-section-header/.rail-section-body. Relocated into the Settings
   panel (index.html) per Nick: "This exclusion menu should all be
   somewhere within the settings, not user front facing," then (Phase AB,
   2026-08-02) its collapsible chevron header was itself replaced with a
   plain on/off .category-toggle switch — "make this a toggle that expands
   when turned on... so it fits well with the light mode toggle" — matching
   the Display/Light Mode row right above it. Phase AA (2026-08-01) added
   the State/County scope toggle, reusing the same .areas-mode-row/-btn
   classes the Exclude/Keep-only mode toggle already used. Tokens only — no
   raw hex — so it themes with everything else automatically.
   ============================================================================ */
.areas-badge {
    flex: none;
    font-size: 8px;
    font-weight: 800;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--txt-3);
    background: var(--surf-3);
    border: 1px solid var(--bdr-1);
    border-radius: 999px;
    padding: 2px 7px;
}
/* Something is actually being filtered — make the badge assert itself. */
.areas-badge.active {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent-border);
}
.areas-mode-row {
    display: flex;
    gap: 4px;
    margin-bottom: 7px;
}
.areas-mode-btn {
    flex: 1;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--txt-3);
    background: var(--surf-3);
    border: 1px solid var(--bdr-1);
    border-radius: 6px;
    padding: 5px 6px;
    cursor: pointer;
    transition: all 0.12s ease;
}
.areas-mode-btn:hover { color: var(--txt-1); }
.areas-mode-btn.active {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent-border);
}
.areas-hint {
    font-size: 9px;
    line-height: 1.45;
    color: var(--txt-4);
    margin-bottom: 6px;
}
.areas-hint-tip {
    display: flex;
    align-items: flex-start;
    gap: 5px;
    margin-top: 7px;
    margin-bottom: 0;
}
.areas-hint-tip i { margin-top: 1px; color: var(--accent); }
.areas-search {
    width: 100%;
    font-size: 10px;
    color: var(--txt-1);
    background: var(--surf-3);
    border: 1px solid var(--bdr-1);
    border-radius: 6px;
    padding: 4px 7px;
    margin-bottom: 6px;
}
.areas-search::placeholder { color: var(--txt-4); }
.areas-search:focus {
    outline: none;
    border-color: var(--accent-fill);
}
/* Capped + scrollable: shared by both scopes (51 state rows, or up to 3,143
   county rows — Phase AA) so neither pushes the rest of the Settings panel
   off the end of a short viewport. */
.areas-state-list {
    max-height: 168px;
    overflow-y: auto;
    border: 1px solid var(--bdr-1);
    border-radius: 6px;
    background: var(--surf-3);
}
.areas-state-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 7px;
    font-size: 10px;
    color: var(--txt-2);
    cursor: pointer;
}
.areas-state-row:hover { background: var(--surf-2); }
.areas-state-row input { accent-color: var(--accent-fill); cursor: pointer; }
.areas-state-row .areas-state-count {
    margin-left: auto;
    font-size: 8px;
    color: var(--txt-4);
    font-family: ui-monospace, monospace;
}
.areas-subhead {
    font-size: 8px;
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--txt-4);
    margin: 8px 0 4px;
}
.areas-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.areas-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 9px;
    font-weight: 600;
    color: var(--txt-2);
    background: var(--surf-3);
    border: 1px solid var(--bdr-1);
    border-radius: 999px;
    padding: 2px 4px 2px 7px;
    cursor: pointer;
}
.areas-chip:hover { color: var(--bad); border-color: var(--bad); }
.areas-chip i { font-size: 8px; opacity: 0.7; }
.areas-footer {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    padding-top: 7px;
    border-top: 1px solid var(--bdr-1);
}
.areas-count {
    flex: 1;
    min-width: 0;
    font-size: 9px;
    color: var(--txt-3);
    line-height: 1.35;
}
.areas-clear {
    flex: none;
    font-size: 9px;
    font-weight: 700;
    color: var(--txt-3);
    background: transparent;
    border: 1px solid var(--bdr-1);
    border-radius: 5px;
    padding: 3px 7px;
    cursor: pointer;
}
.areas-clear:hover { color: var(--bad); border-color: var(--bad); }

/* ============================================================================
   Phase AB (2026-08-02) — subtler tab navigation + light-mode polish.
   Nick: "the amber highlight for the active tab looks off... make the UI
   subtle so the focus can be on the data." Two things bundled here since
   they're the same underlying fix applied to different controls: swap the
   accent-COLOR-as-fill idiom for a lightness-as-elevation one on real
   navigation tabs (not every accent-soft usage in the app — badges/chips
   that mean "this is an active filter" are a different signal and were
   left alone). Light-mode versions go solid white, the same "white card
   pops off the grey shell" language the category cards already use, so
   the active tab now reads as a small elevated card rather than a colored
   patch in both themes.
   ============================================================================ */
@media (min-width: 600px) {
    body.theme-light .rail-view-btn.active {
        background: #ffffff;
        box-shadow: 0 1px 3px rgba(15, 23, 42, 0.10);
    }
    /* Phase AV (RELEASE_PLAN.md, 2026-09-21): "stuck on County" — the
       inactive Criteria/Standings tabs and the Guide button (all three use
       --txt-3 by default) measured 3.25:1 and 2.51:1 against this bar's real
       rendered background in light mode (pixel-sampled, not
       getComputedStyle — the bar's own background is translucent, so a
       computed-style read alone would have missed the true composited
       color), both under WCAG AA's 4.5:1. The only tab that read as
       "clickable" was the one already selected — on a low-res/projector
       display, worse. Dark mode was NOT touched: the identical elements
       already measure 7.03:1 / 7.30:1 there (same pixel-sampling method),
       comfortably over the bar — nothing to fix, so nothing was changed,
       keeping the blast radius to exactly the measured problem.
       Deliberately NOT switching to --accent/--accent-fill despite AV3's
       plan-time suggestion to reuse those tokens: Phase AB explicitly
       removed accent color from this exact control on Nick's own direction
       ("the amber highlight for the active tab looks off... switched from
       a COLOR signal to a LIGHTNESS one... no accent color left on this
       control at all") — using --accent here would silently reverse that.
       --txt-2 (already defined, already used elsewhere) is a stronger
       neutral grey step that stays fully inside the lightness-only
       language AB established; re-measured live at 7.08:1 / 5.47:1 before
       committing to this rule, both comfortably clearing 4.5:1. */
    body.theme-light #rail-view-switcher .rail-view-btn:not(.active),
    body.theme-light #rail-view-switcher .rail-header-btn {
        color: var(--txt-2);
    }
}
body.theme-light .mobile-tab-btn.active {
    background: #ffffff;
    border-color: var(--bdr-1);
    border-bottom-color: transparent;
}
/* Phase AV (RELEASE_PLAN.md, 2026-09-21): the desktop rail-view-switcher's
   inactive tabs weren't the only place this bug lived — .mobile-tab-btn
   (bottom nav, phone widths) uses the same --txt-3-in-light-mode pattern
   and measured the identical failure, pixel-sampled: 3.91:1 in light mode
   (dark mode already passes at 7.85:1, untouched). Same fix, same reasoning
   as the desktop rule above — --txt-2, not --accent, stays inside this
   control's existing lightness-only language. */
body.theme-light .mobile-tab-btn:not(.active) {
    color: var(--txt-2);
}

/* Phase AB (2026-08-02): Nick — "the center view button on mobile has...
   [an icon that] looks out of place with how light it is compared to the
   others." #mobile-recenter-btn's `text-slate-300` (a pale color meant for
   dark glass panels) was never swept by the light-mode container allowlist
   above — that giant shared selector covers #zoom-controls (this same icon,
   desktop version) but this standalone mobile button was missed, so it
   stayed near-white on the now-light glass panel. One targeted override
   rather than adding it to every one of the ~15 repeated giant selectors
   above, since this button only ever uses this single Tailwind color class. */
body.theme-light #mobile-recenter-btn {
    color: var(--txt-2) !important;
}

/* Phase BC (RELEASE_PLAN.md): county-tab tiles for metrics the user has
   switched on. Outline + a text pill so the marker never relies on color
   alone (Phase AV: low-contrast tints read as "disabled"). */
.metric-tile-active {
    box-shadow: inset 0 0 0 1.5px var(--accent-border), 0 0 0 1px var(--accent-border);
}
.metric-tile-pill {
    margin-top: 4px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 8px;
    font-weight: 700;
    color: var(--accent);
    background: var(--accent-soft);
    border: 1px solid var(--accent-border);
    border-radius: 999px;
    padding: 1px 6px;
    max-width: 100%;
    line-height: 1.25;
    text-align: center;
    justify-content: center;
}

/* Phase BH/BP (RELEASE_PLAN.md): shared confirmation dialog. */
.confirm-dialog {
    position: fixed; inset: 0; z-index: 10100;
    display: flex; align-items: center; justify-content: center;
    background: rgba(2, 6, 23, 0.6);
    padding: 16px;
}
.confirm-dialog.hidden { display: none; }
.confirm-dialog-card {
    width: 100%; max-width: 340px;
    border-radius: 16px; padding: 18px;
    border: 1px solid var(--bdr-2, var(--bdr-1));
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
}
.confirm-dialog-title { font-size: 14px; font-weight: 800; color: var(--txt-1); margin: 0 0 6px; }
.confirm-dialog-body { font-size: 12px; line-height: 1.45; color: var(--txt-2); margin: 0 0 16px; }
.confirm-dialog-actions { display: flex; gap: 8px; justify-content: flex-end; }
.confirm-dialog-btn {
    min-height: 36px; padding: 0 14px; border-radius: 10px;
    font-size: 12px; font-weight: 700; cursor: pointer;
    color: var(--txt-2); background: var(--surf-3); border: 1px solid var(--bdr-1);
}
.confirm-dialog-btn:hover { color: var(--txt-1); }
.confirm-dialog-ok { color: #fff; background: var(--bad); border-color: transparent; }
.confirm-dialog-ok:hover { color: #fff; filter: brightness(1.08); }

/* Phase BH: mobile panel-header buttons (All Off · Guide · Random). */
.mobile-head-btn {
    width: 28px; height: 28px; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 8px; cursor: pointer;
    color: var(--txt-2); background: transparent; border: 0;
    font-size: 12px;
}
.mobile-head-btn:hover { color: var(--txt-1); background: var(--surf-3); }
/* All Off with nothing on has nothing to do. */
.all-off-btn:disabled { opacity: 0.4; cursor: default; pointer-events: none; }

/* Phase BO: legend key for hatched no-data counties. */
.legend-nodata {
    display: flex; align-items: center; justify-content: center; gap: 4px;
    margin-top: 6px;
    font-size: 8px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--txt-2);
    white-space: nowrap;
}
.legend-nodata.hidden { display: none; }
.legend-nodata-swatch {
    width: 12px; height: 9px; border-radius: 2px; flex-shrink: 0;
    background: repeating-linear-gradient(45deg, #111827 0 2.2px, #d1d5db 2.2px 6px);
}

/* Phase BZ: phones hide the legend title, so with 2+ metrics a bare
   "12% ▬ 97%" had no context (Nick). The word "Match" leads the bar there.
   Desktop shows it as the title instead. */
.legend-match-prefix { display: none; }
@media (max-width: 599px) {
    #map-legend.is-match .legend-match-prefix {
        display: inline;
        font-size: calc(9px * var(--ui-scale, 1));
        font-weight: 900;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--txt-2);
    }
    /* Two rows (bar + "No data" key) read as a card, not a pill. */
    #map-legend:has(#legend-nodata:not(.hidden)) { border-radius: 12px !important; }
    #map-legend .legend-nodata { margin-top: 4px; }
}

/* The sticky .rail-toolbar's -mx-4 bleed overhangs the scroll area by 8px,
   which gave the rail a stray horizontal scrollbar (visible in WebKit).
   Nothing in the rail is meant to scroll sideways. */
#panel-scroll-area { overflow-x: hidden; }

/* Phase BR: category-bar tabs are legible by default — primary text colour,
   not 50% white / greyscale. Zero-specificity :where() so the active tab's
   category colour utility (text-pink-400 etc., set in selectCategoryTab)
   still wins. */
:where(#bottom-tabs-container > button) { color: var(--txt-1); }

/* Phase BR: the mobile panel header shows the FULL category name ("Transit
   & Connectivity"), never ellipsized. It takes the free width beside the
   header buttons and wraps to a second line rather than truncating. */
#panel-title-text.panel-title-text {
    flex: 1 1 auto;
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.15;
}

/* Phase BR/BS (RELEASE_PLAN.md): About modal — three equal labelled tabs and
   short, token-coloured content that fits every phone width. */
.about-tabs {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
    padding: 12px 16px 4px;
}
.about-tab-btn {
    display: flex; align-items: center; justify-content: center; gap: 5px;
    min-height: 34px; min-width: 0;
    padding: 0 6px;
    border-radius: 10px;
    border: 1px solid var(--bdr-1);
    background: transparent;
    color: var(--txt-2);
    font-size: 11px; font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}
.about-tab-btn span { overflow: hidden; text-overflow: ellipsis; }
.about-tab-btn i { font-size: 10px; flex-shrink: 0; }
.about-tab-btn:hover { color: var(--txt-1); background: var(--surf-3); }
.about-tab-btn.active { color: var(--txt-1); background: var(--surf-3); border-color: var(--bdr-2); }
.about-pane {
    flex-direction: column;
    gap: 12px;
    padding: 14px 18px 18px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--txt-2);
    overflow-wrap: anywhere;
}
.about-lede { margin: 0; color: var(--txt-1); font-size: 13px; }
.about-note { margin: 0; }
.about-note b, .about-steps b, .src-group b { color: var(--txt-1); }
.about-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.about-steps li { display: flex; gap: 10px; align-items: flex-start; }
.about-steps li i { width: 16px; text-align: center; margin-top: 3px; color: var(--accent); flex-shrink: 0; }
.about-inline-swatch { display: inline-block; vertical-align: -1px; }
.src-group { border: 1px solid var(--bdr-1); border-radius: 12px; padding: 8px 12px; }
.src-group-head { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em; color: var(--txt-1); margin-bottom: 4px; }
.src-group ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; }
/* Narrow phones (measured: labels clip at 320 and 360px across WebKit,
   Chromium and Gecko): stack the icon over the label and let the label wrap
   rather than truncate. */
@media (max-width: 380px) {
    .about-tabs { padding-left: 12px; padding-right: 12px; gap: 5px; }
    .about-tab-btn { flex-direction: column; gap: 3px; min-height: 46px; padding: 4px 4px; white-space: normal; text-align: center; line-height: 1.1; font-size: 10.5px; }
    .about-tab-btn span { overflow: visible; }
}

/* Phase BK: ≤/≥ direction switch on the three merged metrics. */
.metric-op-btn {
    margin-left: 4px;
    min-width: 18px; height: 16px;
    padding: 0 4px;
    border-radius: 5px;
    border: 1px solid var(--bdr-2);
    background: var(--surf-3);
    color: var(--txt-1);
    font-size: 10px; font-weight: 900; line-height: 1;
    cursor: pointer;
    vertical-align: 1px;
}
.metric-op-btn:hover { border-color: var(--accent-fill); }

/* Phase BW: blank-map legend — greyed gradient, the title carries the hint
   (and is shown on phones too, where the title is normally hidden). */
#map-legend.is-blank .legend-gradient { filter: grayscale(1); opacity: 0.45; }
#map-legend.is-blank #legend-title-text { display: inline !important; }
@media (max-width: 599px) {
    #map-legend.is-blank .legend-gradient, #map-legend.is-blank #legend-label-max { display: none !important; }
    #map-legend.is-blank #legend-label-min { font-size: 11px; white-space: nowrap; }
}


/* ===========================================================================
   Phase BU (RELEASE_PLAN.md, 2026-09-24) — light mode, designed.
   Nick: "It looks more like a dark mode site that was automatically
   converted, and not designed." The converted look came from: frosted grey
   glass panels (grey on grey), 40%-opacity "off" rows, and every empty
   value shouting in orange monospace. Light mode now uses what native light
   apps use: solid white surfaces lifted by soft shadows, a light-grey
   nested tier, hairline borders, and colour reserved for things that are
   on. The dark header stays (Nick's explicit 2026-07-31 call).
   Token-first: components already read the surf, txt and bdr tokens, so
   most of this is new values, not new selectors.
   =========================================================================== */
body.theme-light {
    --surf-1: #ffffff;
    --thumb-ring: rgba(15, 23, 42, 0.28);
    --surf-2: #f4f5f7;
    --surf-3: #e8ebef;
    --bdr-1:  #e0e4ea;
    --bdr-2:  #c8ced7;
    --txt-1:  #111827;
    --txt-2:  #374151;
    --txt-3:  #6b7280;
    --txt-4:  #9ca3af;
    --color-border: #e0e4ea;
    --color-border-active: #c8ced7;
    --shadow-1: 0 1px 2px rgba(16, 24, 40, 0.06), 0 10px 28px rgba(16, 24, 40, 0.10);
}
/* Solid surfaces: no frosted glass in light mode. */
body.theme-light .glass-panel {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}
/* The rail, its sticky toolbar and the phone panels: white, not grey.
   The `[class]` qualifier is deliberate: the older light-mode family rules
   are `:is(#id …):is([class*="bg-slate-950"])` (0,1,2,1) with !important;
   matching that specificity and coming later is what lets these win. */
body.theme-light #settings-panel[class],
body.theme-light #rail-view-switcher[class],
body.theme-light .rail-toolbar[class],
body.theme-light #mobile-tab-bar[class],
body.theme-light #bottom-tabs-container[class],
body.theme-light #panel-header-shared[class] {
    background: #ffffff !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
}
body.theme-light .rail-toolbar[class] { border-bottom: 1px solid var(--bdr-1); }
body.theme-light #panel-header-shared[class] { border-bottom: 1px solid var(--bdr-1) !important; }
body.theme-light #bottom-tabs-container[class] { border-top: 1px solid var(--bdr-1) !important; box-shadow: 0 -4px 16px rgba(16, 24, 40, 0.06) !important; }
/* Desktop rail: white category cards on a faint grey ground. Phones show one
   category at a time, so the whole panel is simply white there. */
body.theme-light #panel-scroll-area[class] { background: var(--surf-2) !important; }
@media (max-width: 599px) {
    body.theme-light #panel-scroll-area[class] { background: #ffffff !important; }
}
body.theme-light .rail-section-header,
body.theme-light .rail-section-body:not(.rail-collapsed) {
    background: #ffffff !important;
    border-color: var(--bdr-1) !important;
}
/* "Off" rows: readable, just quieter — no 40% fade. */
body.theme-light .metric-row.metric-row-off .metric-row-label { opacity: 1; color: var(--txt-3); }
body.theme-light .metric-row.metric-row-off .metric-row-slider-wrap { opacity: 0.55; }
body.theme-light .metric-row.metric-row-off .metric-row-lock { opacity: 0.45; }
body.theme-light .metric-row-label-text { color: var(--txt-1); }
body.theme-light .metric-row.metric-row-off .metric-row-label-text { color: var(--txt-3); }
/* Slider track + thumb: crisp on white. */
body.theme-light .custom-slider { background: var(--surf-3); }

/* Both themes: colour only means "a target is set". */
.metric-row:not(.metric-row-targeted) .metric-row-value,
.metric-row:not(.metric-row-targeted) .metric-value-prefix,
.metric-row:not(.metric-row-targeted) .metric-value-suffix {
    color: var(--txt-3);
    font-weight: 600;
}

/* Floating map controls + legend + Match Criteria: white cards. */
body.theme-light #map-legend[class],
body.theme-light #selection-summary-panel[class],
body.theme-light #zoom-controls button[class],
body.theme-light #mobile-recenter-btn[class],
body.theme-light #btn-hamburger[class] {
    background: #ffffff !important;
    border-color: var(--bdr-1) !important;
    box-shadow: var(--shadow-1);
}
/* Modals: white sheets over a soft dim, not grey glass. */
body.theme-light :is(#about-modal, #share-modal, #compare-modal, #contribute-modal, #request-modal, #tour-finish-modal, #confirm-dialog) .glass-panel,
body.theme-light .confirm-dialog-card {
    background: #ffffff !important;
    border-color: var(--bdr-1) !important;
}
body.theme-light :is(#about-modal, #share-modal, #compare-modal, #contribute-modal, #request-modal):is([class^="bg-slate-950"], [class*=" bg-slate-950"]) {
    background-color: rgba(15, 23, 42, 0.35) !important;
}

/* ===========================================================================
   Phase CF (RELEASE_PLAN.md, 2026-09-24): Request form type picker, optional
   device & performance details, and Settings → Report a problem.
   =========================================================================== */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* The form scrolls; without this its flex children shrink instead (the
   message box collapsed to one line on phones). */
#request-form > * { flex-shrink: 0; }
.request-kind {
    width: 100%;
    min-height: 44px;
    line-height: 1.3;
    border-radius: 12px;
    border: 1px solid var(--bdr-2);
    background: var(--surf-2);
    color: var(--txt-1);
    padding: 10px 12px;
    font-size: 12px;
    font-weight: 600;
}
.request-kind:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.request-diag {
    border: 1px solid var(--bdr-1);
    border-radius: 12px;
    padding: 10px 12px;
    background: var(--surf-2);
    font-size: 11px;
    color: var(--txt-2);
}
.request-diag.hidden { display: none; }
.request-diag-check { display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--txt-1); cursor: pointer; }
.request-diag-check input { width: 16px; height: 16px; accent-color: var(--accent-fill); }
.request-diag details { margin-top: 6px; }
.request-diag summary { cursor: pointer; color: var(--txt-2); font-weight: 600; }
.request-diag-note { margin: 6px 0; line-height: 1.45; }
.request-diag pre {
    margin: 0;
    max-height: 160px;
    overflow: auto;
    font-size: 10px;
    line-height: 1.4;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    background: var(--surf-3);
    border-radius: 8px;
    padding: 8px;
    color: var(--txt-2);
}
.report-problem-btn {
    background: var(--surf-2);
    border: 1px solid var(--bdr-2);
    color: var(--txt-1);
}
.report-problem-btn:hover { background: var(--surf-3); }

/* Phase CL: keep the last item in the desktop rail clear of a home
   indicator / rounded corner on tablets and unfolded phones. */
@media (min-width: 600px) {
    #panel-scroll-area { padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
}

/* Phase CP: the ≤ / ≥ row in Quick tips — a miniature of .metric-op-btn. */
.hint-op {
    flex: none; width: 44px; margin-top: 1px; text-align: center;
    font-size: 8px; font-weight: 900; line-height: 14px;
    border: 1px solid var(--bdr-2); border-radius: 4px;
    color: var(--txt-1); background: var(--surf-2);
}

/* Phase CQ: sub-headings inside a long category (Education / Health). */
.metric-subhead {
    font-size: 9px; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--txt-3); padding: 10px 0 2px; border-bottom: 1px solid var(--bdr-1); margin-bottom: 2px;
}
.metric-subhead:first-child { padding-top: 2px; }

/* Phase CV: FAQ inside About → How it works. */
.about-faq { margin-top: 6px; border-top: 1px solid var(--bdr-1); padding-top: 10px; }
.about-faq-title { font-size: 11px; font-weight: 900; letter-spacing: .08em; text-transform: uppercase; color: var(--txt-3); margin: 0 0 6px; }
.about-faq-item h3 { font-size: 12px; font-weight: 800; color: var(--txt-1); margin: 8px 0 2px; }
.about-faq-item p { margin: 0; color: var(--txt-2); }

/* 2026-09-25 (Nick): phones name the metric when one is shown alone, so a
   screenshot says what the scale is. Title row above the bar; the pill
   becomes a card; the short prefix ("Golf", "Tech") is redundant then. */
@media (max-width: 599px) {
    #map-legend.has-title .legend-title {
        display: block !important;
        text-align: center;
        font-size: calc(9px * var(--ui-scale, 1));
        line-height: 1.2;
        margin: 0 auto 3px;
        /* Long names wrap to a second centred line rather than being cut
           off (Nick); the card never grows past ~72% of the screen. */
        max-width: 72vw;
        white-space: normal;
        text-wrap: balance;
        overflow-wrap: break-word;
    }
    #map-legend.has-title .legend-title i { display: none; }
    #map-legend.has-title #legend-title-text { display: inline !important; }
    #map-legend.has-title .legend-match-prefix { display: none !important; }
    #map-legend.has-title { border-radius: 12px !important; }
    /* Nick: the scale should sit centred under a long title, or stretch to
       match it — it stretches: labels at the edges, gradient fills between. */
    #map-legend.has-title .legend-bar { display: flex; width: 100%; justify-content: space-between; }
    #map-legend.has-title .legend-gradient { flex: 1 1 auto; width: auto !important; min-width: calc(72px * var(--ui-scale)); }
}

/* County tab → the county's static page (pipeline/build_county_pages.js). */
#detail-county-page { text-decoration: none; }
#detail-county-page:hover, #detail-county-page:focus-visible { text-decoration: underline; }

/* .metric-row sets display:flex, which out-ranked Tailwind's .hidden, so the
   profession rows (hidden until a profession is picked; jobs per 1k never
   has a row) always showed. */
.metric-row.hidden { display: none; }

/* ================================================================
   Phase DB (RELEASE_PLAN.md, 2026-09-27): one right edge for the
   right-hand column. Nick: "all right sidewall alignment should be
   consistent." Menu, zoom stack, legend and Match Criteria all sit at
   right:12px; menu and zoom buttons share one 40px width. The legend sizes
   to its title (56-128px) and wraps only BETWEEN words (it used to break
   "MONTHLY" into "MONTHL / Y" in a fixed 56px box).
   ================================================================ */
@media (min-width: 600px) {
    #zoom-controls { right: 12px !important; }
    #zoom-controls button { width: 40px; height: 36px; }
    #map-legend, #map-legend.legend-wide { width: auto; min-width: 56px; max-width: 128px; }
    #map-legend #legend-title-text,
    #map-legend.legend-wide #legend-title-text {
        /* As wide as the longest WORD: words stack, none is ever split. */
        width: min-content;
        min-width: 44px;
        max-width: 116px;
        overflow-wrap: normal;
        word-break: normal;
        hyphens: manual;
    }
    /* Match Criteria: label | value grid, labels wrap at most once. */
    .selection-summary-row {
        display: grid;
        grid-template-columns: minmax(0, 1fr) max-content;
        align-items: baseline;
        column-gap: 10px;
    }
    .selection-summary-row-label { line-height: 1.3; }
    .selection-summary-row-value { font-family: inherit; letter-spacing: 0; }
}
@media (min-width: 1440px) {
    #selection-summary-panel { width: 280px; }
}

/* Phase DC (2026-09-27): an empty value box (no target / ranked) takes no
   width, so "No target" and ranked ranges end on one right edge. */
.metric-row-value.is-empty { width: 0 !important; padding: 0 !important; border: 0 !important; min-width: 0 !important; }
.metric-value-prefix { cursor: text; }

/* Phase DE (2026-09-27): Nick, "the side panel should have the ability to
   become smaller width if everything else permits." Labels wrap onto a
   second line instead of ending in "…", so the rail can go down to 232px
   (RAIL_W_MIN) with every label and value still fully readable. */
.metric-row-label { align-items: baseline; }
.metric-row-label-text {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    overflow-wrap: normal;
}
.metric-row-label .metric-info-icon,
.metric-row-label .metric-op-btn { flex: none; align-self: flex-start; }
.metric-value-wrap { align-self: baseline; align-items: baseline; }
/* Phase DE: keep "All Off" on one line in a narrow rail. */
body.rail-narrow .rail-header-btn i { display: none; }
body.rail-narrow .rail-header-btn { white-space: nowrap; }

/* Phase DD (2026-09-27): map hover tooltip in the site's current style. */
#d3-tooltip {
    padding: 10px 12px;
    border-radius: 12px;
    box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.45);
    min-width: 200px;
    max-width: 280px;
    font-size: 11px;
}
.tt-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.tt-name { font-family: var(--font-heading, inherit); font-weight: 800; font-size: 13px; line-height: 1.2; color: var(--txt-1); text-wrap: balance; }
.tt-pill { flex: none; white-space: nowrap; font-weight: 700; font-size: 11px; line-height: 1; padding: 4px 8px; border-radius: 999px; color: var(--accent); background: var(--accent-soft); }
.tt-pill-muted { color: var(--txt-3); background: var(--surf-2); }
.tt-rows { display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 3px 12px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--bdr-1); }
.tt-k { color: var(--txt-3); }
.tt-v { color: var(--txt-1); font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.tt-more { margin-top: 4px; color: var(--txt-4); font-size: 10px; }
.tt-foot { margin-top: 8px; color: var(--txt-4); font-size: 10px; }

/* Phase DD (2026-09-27): County tab header. The name gets its own line and
   wraps ("Yolo County, …" was cut off at 1280), the actions sit on the row
   below at a clickable 24px, and nothing in the match row breaks mid-phrase. */
@media (min-width: 600px) {
    #county-details-view > div:first-child > .flex:first-child { flex-wrap: wrap; row-gap: 6px; }
    #detail-county-name {
        flex: 1 0 100%;
        white-space: normal !important;
        overflow: visible !important;
        text-overflow: clip !important;
        font-size: 15px;
        text-wrap: balance;
    }
    #detail-nav-buttons > button { width: 24px !important; height: 24px !important; border-radius: 6px !important; font-size: 11px; }
}
#detail-county-match > span, #detail-county-page { white-space: nowrap; }
#county-details-view > div:first-child > .flex.items-center.gap-2 { flex-wrap: wrap; row-gap: 4px; }
.metric-tile-pill { border-radius: 8px; white-space: nowrap; }

/* Phase DG (2026-09-27): flip which way is better. Only shown while the
   metric is on (no clutter on the 60-odd rows that are off). Amber when
   flipped from the metric's usual direction. */
.metric-dir-btn {
    flex: none;
    align-self: center;
    width: 18px; height: 18px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 5px;
    border: 1px solid var(--bdr-1);
    background: var(--surf-2);
    color: var(--txt-3);
    font-size: 9px;
    cursor: pointer;
}
.metric-dir-btn:hover, .metric-dir-btn:focus-visible { color: var(--txt-1); border-color: var(--bdr-2); }
.metric-dir-btn.is-flipped { color: var(--accent); border-color: var(--accent-border); background: var(--accent-soft); }
.metric-row.metric-row-off .metric-dir-btn { visibility: hidden; }
/* Above the lock icon: same 18px column and 5px gap as .metric-row-controls,
   on every row, so values still end on one right edge. */
.metric-dir-slot { flex: none; width: 18px; margin-left: 5px; display: inline-flex; justify-content: center; align-self: center; }
.selection-summary-dir {
    border: 0; background: none; padding: 0; cursor: pointer;
    color: var(--good); font: inherit; font-weight: 700;
    text-decoration: underline dotted; text-underline-offset: 2px;
}
.selection-summary-dir i { font-size: 8px; margin-left: 2px; opacity: .8; }
.selection-summary-dir:hover { color: var(--txt-1); }
/* Phase DD: long names in County tab cards wrap instead of "…". */
.tile-wrap { white-space: normal; overflow-wrap: anywhere; line-height: 1.25; }

/* 2026-09-27 (Nick: "you gotta do better on the side panel pull tab
   highlighting, it looks bad"): no more white capsule in a dark gradient
   strip. At rest: a hairline on the panel edge and a faint 2x3 dot grip;
   hover / keyboard focus / drag: both turn accent. */
@media (min-width: 600px) {
    #rail-resize-handle { background: transparent; }
    #rail-resize-handle::before {
        content: '';
        position: absolute; left: 0; top: 0; bottom: 0;
        width: 1px;
        background: var(--bdr-1);
        transition: background 0.12s ease, width 0.12s ease;
    }
    #rail-resize-handle::after {
        width: 6px; height: 14px;
        border: 0; border-radius: 0; box-shadow: none;
        background: radial-gradient(circle, var(--txt-4) 1px, transparent 1.4px) 0 0 / 3px 5px;
        opacity: 0.9;
    }
    #rail-resize-handle:hover::before,
    #rail-resize-handle:focus-visible::before,
    #rail-resize-handle.dragging::before { width: 2px; background: var(--accent); }
    #rail-resize-handle:hover::after,
    #rail-resize-handle:focus-visible::after,
    #rail-resize-handle.dragging::after {
        background: radial-gradient(circle, var(--accent) 1px, transparent 1.4px) 0 0 / 3px 5px;
        border: 0;
    }
}

/* Info popup histogram (2026-09-27). */
#metric-info-popover { max-width: 260px; }
.metric-hist { margin: 6px 0 8px; }
.metric-hist svg { display: block; width: 100%; height: 52px; }
.metric-hist rect { fill: var(--txt-4); opacity: 0.55; }
.metric-hist .mh-mark { stroke: var(--accent); stroke-width: 2; }
.mh-axis { display: flex; justify-content: space-between; gap: 6px; margin-top: 3px; font-size: 9px; color: var(--txt-4); font-variant-numeric: tabular-nums; }
.mh-note { margin-top: 4px; font-size: 10px; color: var(--txt-2); }
.mh-note b { color: var(--txt-1); }
/* Lock icon centered in its 18px column so the flip button sits exactly above it. */
.metric-row-controls > :last-child { justify-self: center; }

/* Phase DK (2026-09-27): "how measured here" line in the ⓘ popup and the ≈
   marker on County tab values not measured in the county itself. Orange, not
   the amber the values themselves use, so it reads as a caveat. */
.metric-src-note {
    display: block;
    margin: 6px 0;
    padding: 5px 7px;
    border-radius: 6px;
    background: rgba(148, 163, 184, 0.10);
    color: var(--txt-2);
    font-size: 10px;
    line-height: 1.35;
}
.metric-src-note b { color: var(--txt-1); }
.metric-src-note.is-approx { background: rgba(249, 115, 22, 0.12); }
.metric-src-note.is-approx b { color: #fb923c; }
.src-approx {
    color: #fb923c;
    font-weight: 900;
    margin-right: 2px;
    cursor: help;
}
body.theme-light .src-approx,
body.theme-light .metric-src-note.is-approx b { color: #c2410c; }

/* L1 (2026-09-27): language support. Page hidden until the static text is
   translated (i18n.js removes the class; 4 s safety timeout). */
html.i18n-wait body { visibility: hidden; }
.lang-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 6px; }
.lang-row > span { flex: 1; }
.lang-picker { display: inline-flex; border: 1px solid var(--bdr-2); border-radius: 7px; overflow: hidden; }
.lang-picker button {
    font: inherit; font-size: 9px; font-weight: 700; line-height: 1; padding: 4px 7px;
    background: transparent; color: var(--txt-2); border: 0; cursor: pointer;
}
.lang-picker button + button { border-left: 1px solid var(--bdr-2); }
.lang-picker button.active { background: var(--accent-fill); color: #1e293b; }
.lang-report {
    flex-basis: 100%; text-align: left; background: none; border: 0; padding: 0; cursor: pointer;
    font: inherit; font-size: 9px; font-weight: 600; color: var(--txt-3); text-decoration: underline; text-underline-offset: 2px;
}
.lang-report[hidden] { display: none; }

/* L4 (2026-09-27): Simplified Chinese. Inter/Outfit have no Chinese glyphs,
   so the system's Chinese face follows them; letter-spacing and all-caps do
   nothing useful for Chinese and spread the characters; the 7-9 px labels
   the English layout uses are unreadable in Chinese, so they get a floor. */
:root[lang|="zh"] {
    --font-heading: 'Outfit', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
    --font-body: 'Inter', "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
}
:root[lang|="zh"] body, :root[lang|="zh"] body * { letter-spacing: 0 !important; }
:root[lang|="zh"] .text-\[7px\], :root[lang|="zh"] .text-\[8px\] { font-size: 10px; }
:root[lang|="zh"] .text-\[9px\] { font-size: 10.5px; }
/* L3: longer pill text (Spanish "Supera 10%") must stay inside the tile. */
.metric-tile-pill { max-width: 100%; }
:root[lang="es"] .metric-tile-pill { gap: 2px; padding-left: 4px; padding-right: 4px; }
:root[lang="es"] .metric-tile-pill > i { display: none; }   /* decorative; room for the longer word */

/* ── Infographic builder (infographic.js) ────────────────────────────── */
/* The mode takes over the sidebar / phone sheet; everything else in it is
   hidden until Exit, then comes back exactly as it was. */
body.ib-on #settings-panel > :not(#ib-panel),
body.ib-on #county-details-drawer,
body.ib-on #map-legend,
body.ib-on #mobile-tab-bar,
body.ib-on #bottom-tabs-container,
body.ib-on #rail-view-switcher > :not(#ib-switch-label) { display: none !important; }
#ib-panel, #ib-switch-label { display: none; }
body.ib-on #ib-panel { display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; overflow-y: auto;
  padding: 14px 16px 24px; color: var(--txt-2); font-size: 13px; }
body.ib-on #ib-switch-label { display: flex; align-items: center; gap: 8px; padding: 0 8px; font: 800 11px/1 var(--font-heading, Outfit), system-ui, sans-serif;
  letter-spacing: .1em; text-transform: uppercase; color: var(--txt-1); }
#ib-switch-label i, .ib-title i { color: var(--accent); }
/* Panel rows never shrink: the column scrolls instead (a squeezed <select> was unreadable on phones). */
#ib-panel > * { flex: none; }
.ib-county { display: grid; gap: 7px; padding: 12px; border-radius: 12px; background: var(--surf-2); border: 1px solid var(--accent-border); }
.ib-county-head { display: flex; align-items: center; gap: 8px; }
.ib-county-head b { font: 800 14px/1.25 Outfit, system-ui, sans-serif; color: var(--txt-1); }
.ib-county-x { margin-left: auto; flex: none; width: 28px; height: 28px; border-radius: 8px; border: 1px solid var(--bdr-1); background: var(--surf-1);
  color: var(--txt-2); font-size: 17px; line-height: 1; cursor: pointer; }
.ib-county-v { font: 800 22px/1.1 Outfit, system-ui, sans-serif; color: var(--txt-1); border-left: 5px solid; padding-left: 9px; font-variant-numeric: tabular-nums; }
.ib-county-rows { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; font-size: 12px; color: var(--txt-3); }
.ib-county-rows b { color: var(--txt-1); text-align: right; font-variant-numeric: tabular-nums; }
.ib-select { min-height: 38px; line-height: 1.3; }
/* Safari draws native selects thin and ignores padding; a plain box with its own chevron gets real height. */
select.ib-select { -webkit-appearance: none; appearance: none; padding-right: 30px; text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%238b93a0' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; }
@media (max-width: 599px) {
  .ib-select { font-size: 16px; }   /* 16px: iOS won't zoom the page on focus */
  /* The builder hides the phone's tab strip and bottom tabs; zero their
     reserved heights so the panel starts right under the pull tab and runs
     to the bottom edge. --m-panel-h is restated because :root resolved it
     with the old heights (custom properties compute where declared). */
  body.ib-on { --m-tabbar-h: 0px; --m-catbar-h: 0px;
    --m-panel-h: calc(var(--m-zone-h) - env(safe-area-inset-bottom, 0px)); }
}
.ib-head { display: flex; align-items: center; gap: 10px; }
.ib-title { font: 800 15px/1.2 Outfit, system-ui, sans-serif; color: var(--txt-1); }
/* Desktop names the mode in the tab strip above; phones have no strip, so the panel names it. */
@media (min-width: 768px) { .ib-title { visibility: hidden; } }
.ib-exit { margin-left: auto; flex: none; font: 700 11.5px system-ui, sans-serif; padding: 7px 10px; border-radius: 8px; cursor: pointer;
  color: var(--txt-1); background: var(--surf-2); border: 1px solid var(--bdr-2); }
@media (hover: hover) { .ib-exit:hover { border-color: var(--accent-border); } }
.ib-intro { margin: 0; padding: 10px 12px; border-radius: 10px; background: var(--accent-soft); border: 1px solid var(--accent-border);
  color: var(--txt-1); font-size: 12.5px; line-height: 1.45; }
.ib-sec { margin-top: 4px; font: 800 10px/1 system-ui, sans-serif; letter-spacing: .12em; text-transform: uppercase; color: var(--txt-4); }
.ib-row { display: grid; grid-template-columns: 64px minmax(0, 1fr); align-items: center; gap: 8px; }
.ib-row > span { color: var(--txt-3); font-size: 12px; font-weight: 600; }
.ib-select { width: 100%; min-width: 0; font: 13px system-ui, sans-serif; padding: 8px 9px; border-radius: 8px;
  color: var(--txt-1); background: var(--surf-3); border: 1px solid var(--bdr-1); }
.ib-select:focus { outline: none; border-color: var(--accent-border); box-shadow: 0 0 0 3px var(--accent-soft); }
.ib-card { display: grid; gap: 7px; padding: 12px; border-radius: 12px; background: var(--surf-2); border: 1px solid var(--bdr-1); }
.ib-map-title { font: 800 14px/1.3 Outfit, system-ui, sans-serif; color: var(--txt-1); text-wrap: balance; }
.ib-bar { height: 10px; border-radius: 4px; }
.ib-ends { display: flex; justify-content: space-between; gap: 8px; font-weight: 700; font-size: 12px; color: var(--txt-1); font-variant-numeric: tabular-nums; }
.ib-ends-words { font-weight: 600; font-size: 10.5px; color: var(--txt-4); text-transform: uppercase; letter-spacing: .08em; margin-top: -4px; }
.ib-rev { align-self: center; display: inline-flex; align-items: center; gap: 6px; margin: 2px auto 0; padding: 4px 10px; border-radius: 999px; border: 1px solid var(--border-1, rgba(148,163,184,.3)); background: transparent; color: var(--txt-3); font-size: 11px; font-weight: 700; cursor: pointer; }
.ib-rev:hover { color: var(--txt-1); }
.ib-rev[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }
.ib-formula { font: 600 11.5px ui-monospace, Menlo, monospace; color: var(--txt-2); background: var(--surf-3); border-radius: 6px; padding: 6px 8px; }
.ib-note { font-size: 11px; line-height: 1.45; color: var(--txt-3); }
/* DM4: a range that crosses a method change (re-based estimates, a new census question). */
.ib-warn { font-size: 11.5px; line-height: 1.45; font-weight: 600; color: #fcd34d; background: rgba(251,191,36,.10); border: 1px solid rgba(251,191,36,.35); border-radius: 6px; padding: 6px 8px; }
.ib-warn i { margin-right: 3px; }
body.theme-light .ib-warn { color: #92400e; background: #fef3c7; border-color: #fcd34d; }
.ib-copy { justify-self: start; font: 700 12px system-ui, sans-serif; padding: 7px 11px; border-radius: 8px; cursor: pointer;
  color: var(--txt-1); background: var(--surf-3); border: 1px solid var(--bdr-2); }
.ib-ranks { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.ib-ranks b { font-size: 12px; color: var(--txt-1); }
.ib-ranks ol { margin: 4px 0 0; padding-left: 16px; display: grid; gap: 4px; }
.ib-ranks li { font-size: 11.5px; color: var(--txt-3); }
.ib-ranks li button { all: unset; cursor: pointer; font-weight: 600; color: var(--txt-1); text-decoration: underline; text-decoration-color: var(--bdr-2); }
.ib-ranks li span { display: block; font-variant-numeric: tabular-nums; }

/* Share sheet for the infographic builder (image only). */
#ib-share-modal { position: fixed; inset: 0; z-index: 10020; display: grid; place-items: center; padding: 16px;
  background: rgba(2, 6, 23, 0.6); backdrop-filter: blur(4px); }
.ib-share-card { width: min(560px, 100%); max-height: calc(100vh - 32px); overflow: auto; display: grid; gap: 12px;
  padding: 16px; border-radius: 16px; border: 1px solid var(--bdr-1); color: var(--txt-2); }
.ib-title-s { font: 800 16px/1.2 Outfit, system-ui, sans-serif; color: var(--txt-1); }
.ib-fmts { display: flex; gap: 6px; flex-wrap: wrap; }
.ib-fmts button { font: 700 12px system-ui, sans-serif; padding: 7px 11px; border-radius: 999px; cursor: pointer;
  color: var(--txt-2); background: var(--surf-3); border: 1px solid var(--bdr-1); }
.ib-fmts button.on { color: var(--txt-1); border-color: var(--accent-border); background: var(--accent-soft); }
.ib-preview { background: #0b0c0f; border-radius: 10px; padding: 8px; display: grid; place-items: center; }
.ib-preview img { max-width: 100%; max-height: 52vh; border-radius: 6px; display: block; }
.ib-share-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.ib-primary { font: 800 13px system-ui, sans-serif; padding: 9px 14px; border-radius: 8px; cursor: pointer;
  color: #1a1204; background: var(--accent-fill); border: 1px solid var(--accent-fill); }

/* ── RV2 (2026-10-13): criterion search ──────────────────────────────────────
   Lives in the sticky .rail-toolbar. Below 768px the toolbar used to be hidden
   (phones have All Off / Guide / Random in the panel header), so there it now
   shows only the search box. */
@media (max-width: 767px) {
    .rail-toolbar > .rail-header-btn { display: none !important; }
}
@media (max-width: 599px) {
    .rail-toolbar { background: var(--surf-1); border-bottom: 1px solid var(--bdr-1); }
    /* Phones: the panel header's magnifier opens the search floating at the
       top of the screen (floatMetricSearch), so the bar itself never shows
       here. 600-767px has no such header, so it stays visible there. */
    .rail-toolbar { display: none !important; }
}
.metric-search-wrap { position: relative; flex: 1 1 100%; min-width: 0; }
.metric-search-wrap > i { position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
    font-size: 11px; color: var(--txt-4); pointer-events: none; }
#metric-search { width: 100%; min-width: 0; padding: 7px 10px 7px 29px; border-radius: 8px;
    font: 12.5px system-ui, sans-serif; color: var(--txt-1); background: var(--surf-3); border: 1px solid var(--bdr-1);
    -webkit-appearance: none; appearance: none; }
#metric-search::placeholder { color: var(--txt-4); }
#metric-search:focus { outline: none; border-color: var(--accent-border); box-shadow: 0 0 0 3px var(--accent-soft); }
@media (max-width: 599px) { #metric-search { font-size: 16px; } }   /* iOS won't zoom on focus */
#metric-search-results { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 80; max-height: 320px;
    overflow-y: auto; border-radius: 10px; background: var(--surf-1); border: 1px solid var(--bdr-2, var(--bdr-1));
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28); padding: 4px; }
body.theme-light #metric-search-results { background: #fff; }
.metric-search-hit { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 10px;
    padding: 8px 9px; border-radius: 7px; text-align: left; cursor: pointer; background: none; border: 0; color: var(--txt-1); }
.metric-search-hit:hover, .metric-search-hit.active { background: var(--accent-soft); }
.metric-search-label { font-size: 12.5px; font-weight: 600; min-width: 0; }
.metric-search-kind { flex: none; font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--txt-4); }
.metric-search-none { padding: 10px; font-size: 12px; color: var(--txt-3); }
/* Where a search result lands: a brief accent ring. */
@keyframes search-flash { 0%, 60% { box-shadow: 0 0 0 3px var(--accent); } 100% { box-shadow: 0 0 0 3px transparent; } }
.search-flash { animation: search-flash 1.8s ease-out; border-radius: 10px; }
/* RV6: the "≈ Metro-wide" legend key may wrap inside the narrow desktop bar. */
.legend-shared { white-space: normal; text-align: center; line-height: 1.25; }
.tt-shared { max-width: 260px; white-space: normal; }
/* RV6: "N of your M criteria are in <group>…" */
.selection-summary-tilt { margin: 0 0 8px; padding: 7px 9px; border-radius: 8px; font-size: 10.5px; line-height: 1.4;
    color: var(--txt-2); background: var(--accent-soft); border: 1px solid var(--accent-border); }

/* ── RV5 (2026-10-13): distribution strip above a switched-on slider ─────── */
.metric-row-slider-wrap:has(.slider-hist:not(.hidden)) { flex-direction: column; align-items: stretch; gap: 3px; }
/* Inset by the thumb's radius so a bar sits under the thumb that selects it. */
.slider-hist { padding: 0 8px; }
.slider-hist svg { display: block; width: 100%; height: 16px; }
.slider-hist rect { fill: var(--txt-4); opacity: 0.55; }
.slider-hist rect.sh-fail { opacity: 0.16; }
.slider-hist .sh-med { stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.sh-note { margin-top: 2px; font-size: 9.5px; font-weight: 700; color: var(--txt-3); text-align: center; }
.sh-note.is-few { color: var(--bad, #dc2626); }

/* ── RV4 (2026-10-13): city names ────────────────────────────────────────── */
.cities-layer { font-family: Inter, system-ui, sans-serif; font-weight: 600; }
.cities-layer text { fill: #fff; stroke: rgba(15, 23, 42, 0.82); paint-order: stroke; stroke-linejoin: round; }
.cities-layer circle { fill: #fff; stroke: rgba(15, 23, 42, 0.85); stroke-width: inherit; }
.city-labels-btn.is-on, body.theme-light #zoom-controls .city-labels-btn.is-on { color: var(--accent) !important; }
#mobile-city-btn { display: none; }
@media (max-width: 599px) {
    #mobile-city-btn { display: flex !important; }
    body.mobile-panel-expanded #mobile-city-btn { display: none !important; }
}
body.theme-light #mobile-city-btn { color: var(--txt-2) !important; }
body.theme-light #mobile-city-btn.is-on { color: var(--accent) !important; }

/* 2026-10-14: phone search floats at the top of the screen (floatMetricSearch),
   above the keyboard, with its results dropping down over a dimmed map. */
#metric-search-float {
    position: fixed; inset: 0; z-index: 1300;
    background: rgba(15, 23, 42, 0.35);
    padding: calc(env(safe-area-inset-top, 0px) + 52px) 12px 0;
}
#metric-search-float .metric-search-wrap {
    padding: 8px; border-radius: 14px; background: var(--surf-1);
    border: 1px solid var(--bdr-1); box-shadow: 0 16px 40px rgba(0, 0, 0, 0.3);
}
body.theme-light #metric-search-float .metric-search-wrap { background: #fff; }
#metric-search-float .metric-search-wrap > i { left: 18px; }
#metric-search-float #metric-search-results { position: static; margin-top: 6px; max-height: 42vh; box-shadow: none; }
/* The icon centers on the input, not on input + results. */
#metric-search-float .metric-search-wrap > i { top: 25px; transform: none; }
/* Share panel: the city-names switch sits with the Tall / Wide choice, set apart a little. */
.ib-fmts .ib-cities-toggle { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
@media (max-width: 599px) { .ib-fmts .ib-cities-toggle { margin-left: 0; } }

/* Share panel on phones (2026-10-14, Nick: "the vertical space of this panel"):
   everything on one screen. One row of choices, a smaller preview (the saved
   file stays full size), one row of actions, short labels, no footnote. */
.ib-sh { display: none; }
@media (max-width: 599px) {
    .ib-lg { display: none; }
    .ib-sh { display: inline; }
    #ib-share-modal { padding: 10px; }
    .ib-share-card { gap: 10px; padding: 14px !important; }
    .ib-fmts { flex-wrap: nowrap; }
    .ib-fmts button { flex: 1 1 0; justify-content: center; text-align: center; padding: 8px 6px; }
    .ib-fmts .ib-cities-toggle { margin-left: 0; display: inline-flex; }
    .ib-preview { padding: 6px; }
    .ib-preview img { max-height: 46vh; }
    .ib-share-acts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
    .ib-share-acts button { width: 100%; padding: 10px 4px; text-align: center; }
    .ib-share-card > .ib-note { display: none; }
}

/* ── Versus maps categories (Phase VM, 2026-10-14) ───────────────────────────
   One tap between categories. Wraps in the sidebar; one sideways-scrolling
   row on phones so the sheet doesn't lose height to it. */
.ib-fams { display: flex; flex-wrap: wrap; gap: 6px; }
.ib-fam { display: inline-flex; align-items: center; gap: 6px; padding: 7px 10px; border-radius: 10px; cursor: pointer;
    font: 700 12px/1.1 system-ui, sans-serif; color: var(--txt-2); background: var(--surf-2); border: 1px solid var(--bdr-1); white-space: nowrap; }
.ib-fam i { font-size: 12px; color: var(--txt-4); }
/* Hover only where there's a real pointer: on phones, tapping a tab scrolls
   the row and the hover style stuck to whichever tab slid under the finger,
   leaving it half highlighted (Nick, 2026-10-14). */
@media (hover: hover) { .ib-fam:hover { color: var(--txt-1); border-color: var(--accent-border); } }
.ib-fam.on { color: var(--txt-1); background: var(--accent-soft); border-color: var(--accent-border); }
.ib-fam.on i { color: var(--accent); }
@media (max-width: 599px) {
    .ib-fams { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; margin: 0 -16px; padding: 2px 16px; scroll-padding: 16px; }
    .ib-fams::-webkit-scrollbar { display: none; }
    .ib-fam { flex: none; font-size: 13px; padding: 8px 11px; }
}
.ib-head .ib-surprise + .ib-exit { margin-left: 0; }
.ib-surprise i { color: var(--accent); }
/* Long translations of Exit / Surprise wrap instead of overflowing the panel. */
.ib-head { flex-wrap: wrap; justify-content: flex-end; }
.ib-head .ib-title { margin-right: auto; }
.ib-exit { white-space: normal; text-align: center; }

/* ☰ map switch: Criteria map / Versus maps, the active one checked. */
.hm-modes-head { padding: 4px 12px 2px; font: 800 9px/1 system-ui, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--txt-4); }
#hm-modes .hm-mode { position: relative; }
#hm-modes .hm-mode-name { flex: 1; }
#hm-modes .hm-mode-check { visibility: hidden; font-size: 10px; color: var(--accent); }
#hm-modes .hm-mode.is-active { color: var(--txt-1) !important; background: var(--accent-soft); }
#hm-modes .hm-mode.is-active .hm-mode-check { visibility: visible; }
#hm-modes .hm-mode .hm-ico { color: var(--txt-3); }
#hm-modes .hm-mode.is-active .hm-ico { color: var(--accent); }
/* Versus maps: "Texas counties only" under the title. */
.ib-state-tag { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700; color: var(--accent); }

/* Area picker (2026-10-14): a button with the native state list laid over it,
   in the toolbar row on desktop and the panel header on phones. No extra row. */
.area-btn, .mobile-area-btn { position: relative; overflow: hidden; }
.area-focus-select { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer;
    font-size: 16px; /* iOS: no zoom on open */ -webkit-appearance: none; appearance: none; }
.area-btn.is-on, .mobile-area-btn.is-on { color: var(--accent) !important; border-color: var(--accent-border); background: var(--accent-soft); }
.area-btn .area-btn-label { font-variant-numeric: tabular-nums; }
/* 2026-10-15: towns behind standout counties get an amber dot; smaller towns smaller type. */
.cities-layer g.city.is-standout circle { fill: #fbbf24; }
.cities-layer g.city.is-town text, .cities-layer g.city.is-standout text { font-size: 0.9em; }

/* ── Overlay map (Phase OM, 2026-10-15) ───────────────────────────────────── */
.ov-key { display: flex; align-items: center; gap: 14px; }
.ov-grid-wrap { display: grid; grid-template-columns: auto auto; grid-template-rows: auto auto; align-items: center; gap: 4px; flex: none; }
.ov-axis-a { grid-row: 1; grid-column: 1; writing-mode: vertical-rl; transform: rotate(180deg); font-size: 10px; font-weight: 700; color: var(--txt-3); max-height: 84px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ov-axis-b { grid-row: 2; grid-column: 2; font-size: 10px; font-weight: 700; color: var(--txt-3); max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ov-grid { grid-row: 1; grid-column: 2; display: grid; grid-template-columns: repeat(3, 24px); grid-auto-rows: 24px; gap: 2px; }
.ov-grid i { display: block; border-radius: 3px; }
.ov-score { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ov-score b { font: 800 26px/1.05 Outfit, system-ui, sans-serif; color: var(--txt-1); }
.ov-score span { font-size: 12px; line-height: 1.35; color: var(--txt-2); }
.ov-legend-words { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; font-size: 11.5px; color: var(--txt-2); }
.ov-legend-words span { display: flex; align-items: center; gap: 6px; min-width: 0; }
.ov-legend-words i { flex: none; width: 12px; height: 12px; border-radius: 3px; }
.ov-warn { font-size: 11.5px; line-height: 1.4; color: var(--txt-2); background: var(--accent-soft); border: 1px solid var(--accent-border); border-radius: 8px; padding: 7px 9px; }
.ov-warn i { color: var(--accent); }
.ov-caution { font-size: 11px; color: var(--txt-4); }
.ov-swap { display: inline-flex; align-items: center; gap: 6px; }
.ov-explore li button.on { color: var(--accent); }
.metric-info-overlay { display: flex; align-items: center; gap: 6px; margin-top: 8px; padding: 6px 9px; border-radius: 8px; cursor: pointer;
    font: 700 11.5px system-ui, sans-serif; color: var(--txt-1); background: var(--surf-3); border: 1px solid var(--bdr-1); }
.metric-info-overlay i { color: var(--accent); }

/* Historical map player (RELEASE_PLAN.md HM3, 2026-10-16) */
.ib-hist { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; align-items: center; column-gap: 14px;
  margin: 10px 0 4px; padding: 12px 14px; border-radius: 12px; background: var(--surface-2, rgba(148,163,184,.08)); }
.ib-hist-year { grid-row: 1 / span 2; font-family: Outfit, sans-serif; font-weight: 800; font-size: 40px; line-height: 1; font-variant-numeric: tabular-nums; }
.ib-hist-span { font-size: 12px; opacity: .7; }
.ib-hist-play { justify-self: start; margin-top: 6px; }
.ib-hist-out { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 4px 0 10px; }
.ib-hist-fmt { display: flex; gap: 6px; }
.ib-hist-msg { flex-basis: 100%; font-size: 12px; opacity: .8; min-height: 1em; }
.ib-hist-save { font-weight: 800; }
.ib-hist-year.ib-hist-dec { font-size: 26px; }

/* History/Change maps are staging-only until launch (RELEASE_PLAN HM6): copy
   about them carries .history-only and shows once infographic.js sets html.history-on. */
.history-only { display: none !important; }
html.history-on .history-only { display: revert !important; }
html.history-on .history-off-only { display: none !important; }

/* PJ3: Map this job by (jobs / pay / both). */
.occ-mapby { display: flex; flex-direction: column; gap: 4px; }
.occ-mapby-label { font-size: 9px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--txt-3); }
.occ-mapby-btns { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 4px; }
.occ-mapby-btn { font-size: 11px; font-weight: 700; padding: 6px 4px; border-radius: 7px; border: 1px solid var(--bdr-1); background: transparent; color: var(--txt-2); cursor: pointer; }
.occ-mapby-btn:hover { color: var(--txt-1); }
.occ-mapby-btn[aria-pressed="true"] { background: rgba(6, 182, 212, .16); border-color: rgba(6, 182, 212, .6); color: var(--txt-1); }
.occ-mapby-btn:focus-visible { outline: 2px solid rgba(6, 182, 212, .8); outline-offset: 1px; }

/* PJ2 (2026-10-18, Nick: tapping the job search on a phone zoomed the page and
   the list couldn't be picked). iOS zooms into any focused field with text
   under 16px, so on touch screens every text box, dropdown and number box is
   16px. The page itself never zooms on focus. */
@media (hover: none) and (pointer: coarse) {
  input[type="text"], input[type="search"], input[type="number"], input[type="email"], input:not([type]), select, textarea {
    font-size: 16px !important;
  }
  #occ-search, #occ2-search { padding-top: 8px; padding-bottom: 8px; }
}

/* ── LD: the front page (option C, chosen by Nick 2026-10-18) ───────────── */
:root { --lv-ink: #0f172a; --lv-paper: #ffffff; --lv-body: #334155; --lv-muted: #64748b; --lv-rule: rgba(15, 23, 42, .12); --lv-btn: #0f172a; --lv-btn-ink: #ffffff; }
body:not(.theme-light) { --lv-ink: #f8fafc; --lv-paper: #15171b; --lv-body: #cbd5e1; --lv-muted: #94a3b8; --lv-rule: rgba(255, 255, 255, .12); --lv-btn: #f8fafc; --lv-btn-ink: #0f172a; }
.lv { display: none; }
html[data-landing="c"] .lv-c { display: flex; }
html[data-landing] #landing-layer { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
.lv-lede { color: var(--lv-body); font-size: 16px; line-height: 1.6; margin: 0; max-width: 46ch; }
.lv-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.lv-btn { font: 600 15px/1 Inter, system-ui, sans-serif; padding: 13px 20px; border-radius: 8px; cursor: pointer; border: 1px solid transparent; }
.lv-btn-primary { background: var(--lv-btn); color: var(--lv-btn-ink); }
.lv-btn-primary:hover { opacity: .9; }
.lv-btn-quiet { background: transparent; color: var(--lv-ink); border-color: var(--lv-rule); }
.lv-btn-quiet:hover { border-color: var(--lv-muted); }
.lv-btn:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
.lv-sources, .lv-foot { color: var(--lv-muted); font-size: 12.5px; margin: 0; line-height: 1.5; }
.lv-over { color: var(--lv-muted); font-size: 11.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; margin: 0; }
.lv-mark { display: flex; align-items: center; gap: 10px; font: 800 13px Outfit, Inter, sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--lv-ink); }
.lv-mark img { width: 28px; height: 28px; border-radius: 7px; }
/* C: a plain title page, left-aligned, with the facts as a short table. */
html[data-landing="c"] #landing-layer { background: rgba(15, 23, 42, .45) !important; }
.lv-c { width: 100%; justify-content: center; }
.lv-c-card { background: var(--lv-paper); border-top: 3px solid var(--lv-ink); border-radius: 4px; box-shadow: 0 20px 60px rgba(0,0,0,.25); padding: 34px 36px 28px; max-width: 600px; width: 100%; display: flex; flex-direction: column; gap: 16px; max-height: 92vh; overflow-y: auto; }
.lv-c-h { font: 800 36px/1.05 Outfit, Inter, sans-serif; color: var(--lv-ink); margin: 0; }
.lv-c-sub { color: var(--lv-body); font-size: 17px; margin: -4px 0 0; }
.lv-c-table { margin: 4px 0; border-top: 1px solid var(--lv-rule); }
.lv-c-table div { display: grid; grid-template-columns: 120px 1fr; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--lv-rule); }
.lv-c-table dt { color: var(--lv-muted); font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; padding-top: 2px; }
.lv-c-table dd { color: var(--lv-ink); font-size: 15px; margin: 0; }
@media (max-width: 767px) {
  .lv-c-card { padding: 24px 20px 20px; }
  .lv-c-h { font-size: 30px; }
  .lv-c-table div { grid-template-columns: 88px 1fr; gap: 8px; padding: 7px 0; }
  .lv-c-table dt { font-size: 10.5px; }
  .lv-c-table dd { font-size: 14px; }
  .lv-c-sub { font-size: 15px; }
  .lv-actions .lv-btn { flex: 1 1 100%; padding: 12px 16px; }
}
/* LD option C in the site's own palette (Nick, 2026-10-18: "the dark and
   light mode does not fit the existing color scheme"): the card is the same
   panel as the rail (white in light mode, dark glass in dark), text and rules
   use the site's tokens, the top edge is the brand amber, and the main button
   is the header's slate. */
html[data-landing="c"] #landing-layer { background: rgba(15, 23, 42, 0.38) !important; }
html[data-landing="c"] body:not(.theme-light) #landing-layer { background: rgba(0, 0, 0, 0.5) !important; }
@media (max-width: 767px) {
  /* Phones: the card ends above the bottom tab bar and scrolls inside if it must. */
  html[data-landing="c"] #landing-layer { align-items: flex-start; padding-top: calc(12px + env(safe-area-inset-top, 0px)) !important; }
  html[data-landing="c"] .lv-c-card { max-height: calc(100dvh - 110px - env(safe-area-inset-bottom, 0px)); gap: 12px; }
  html[data-landing="c"] .lv-c-table div { padding: 6px 0; }
}
html[data-landing="c"] .lv-c-card {
  background: var(--surf-1); -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  border: 1px solid var(--bdr-1); border-top: 3px solid var(--accent-fill, var(--accent)); border-radius: 14px;
  box-shadow: var(--shadow-1, 0 20px 60px rgba(0,0,0,.25));
}
html[data-landing="c"] body.theme-light .lv-c-card { background: #ffffff; -webkit-backdrop-filter: none; backdrop-filter: none; }
html[data-landing="c"] body:not(.theme-light) .lv-c-card { background: rgba(30, 32, 38, 0.97); }
html[data-landing="c"] .lv-c .lv-over { color: var(--accent); }
html[data-landing="c"] .lv-c-h { color: var(--txt-1); }
html[data-landing="c"] .lv-c-sub { color: var(--txt-2); }
html[data-landing="c"] .lv-c-table { border-top-color: var(--bdr-1); }
html[data-landing="c"] .lv-c-table div { border-bottom-color: var(--bdr-1); }
html[data-landing="c"] .lv-c-table dt { color: var(--txt-4); }
html[data-landing="c"] .lv-c-table dd { color: var(--txt-1); }
html[data-landing="c"] .lv-c .lv-foot { color: var(--txt-3); }
html[data-landing="c"] .lv-c .lv-btn-primary { background: #334155; color: #f9fafb; border-color: #334155; }
html[data-landing="c"] body:not(.theme-light) .lv-c .lv-btn-primary { background: #475569; border-color: #64748b; }
html[data-landing="c"] .lv-c .lv-btn-primary:hover { background: #1e293b; opacity: 1; }
html[data-landing="c"] body:not(.theme-light) .lv-c .lv-btn-primary:hover { background: #526079; }
html[data-landing="c"] .lv-c .lv-btn-quiet { background: var(--surf-3); color: var(--txt-1); border-color: var(--bdr-2); }
html[data-landing="c"] body.theme-light .lv-c .lv-btn-quiet { background: #ffffff; }
html[data-landing="c"] .lv-c .lv-btn:focus-visible { outline: 2px solid var(--accent-fill, var(--accent)); }

.lv-c-county { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; margin: 0; padding: 4px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 700; color: var(--accent); background: var(--accent-soft, rgba(245, 158, 11, .15)); border: 1px solid var(--accent-border, rgba(180, 83, 9, .3)); }
.lv-c-county[hidden] { display: none; }
