/* ============================================================================
   CAMBRIDGE 360 — THE ONE TOKEN BLOCK   (design substrate, 2026-08-03)
   ----------------------------------------------------------------------------
   B6 fix. Before this, cambridge.css inherited styles.css's warm-cream Atlanta
   tokens (--ink #1a1714, --bg #f6f4f0, --accent #c0532c …) AND hard-coded ~105
   civic literals of its own. Two neutrals, two greens, two accents, ad-hoc
   radii — the root cause of "not cohesive".
   Every value below is sourced from a literal that was ALREADY in this file
   (the civic grammar of DESIGN.md); nothing is invented and none of the warm
   Atlanta values survive. Nothing below this block may contain a raw hex.
   The only permitted exceptions are DATA colours — a swatch that must match a
   published City map — and each one carries a comment saying so.
   ========================================================================== */
:root {
  /* ── NEUTRAL RAMP · one family, slate. Replaces both the warm Atlanta
     neutrals and the six near-duplicate greys this file used to carry. ── */
  --n-000: #ffffff;
  --n-025: #f7f9fb;
  --n-050: #f4f6f9;   /* page background (DESIGN.md bg) */
  --n-100: #eef2f6;   /* wash / inset surfaces */
  --n-150: #e3e8ee;   /* hairlines (DESIGN.md panel border) */
  --n-200: #cfd8e0;
  --n-250: #c6cdd5;
  --n-300: #b3bcc5;
  --n-400: #97a4b1;
  --n-500: #8593a2;
  --n-550: #7a8794;
  --n-600: #586b7d;   /* secondary text (DESIGN.md muted) */
  --n-700: #46596b;
  --n-800: #3d4854;
  --n-850: #26313b;
  --n-900: #1e2732;   /* primary text (DESIGN.md ink) */
  --n-1000: #000000;
  --ink-rgb: 30, 39, 50;      /* --n-900, for alpha compositing */
  --n-500-rgb: 133, 147, 162;
  --white-rgb: 255, 255, 255;

  /* ── TEAL · WAS the accent until 2026-08-16. NO LONGER CHROME.
     What still reads these tokens is only (a) semantic verdicts — pass /
     eligible / low-risk / approved — and (b) the Updates + Finding chart
     series. Neither is interface furniture, and both are a separate decision.
     Now that chrome has left teal, the verdicts are the only green in the
     shell and read better for it.
     Teal also remains the selection colour ON THE MAP CANVAS
     (VARIABLE_COLORS.selection, cambridge.js:183). Selection is blue in the
     DOM and teal on the canvas on purpose — see THE FORM LAW in the chrome
     block below. ── */
  --c-teal: #2b8577;
  --c-teal-deep: #1f5b52;
  --c-teal-wash: #e9f2f0;
  --teal-rgb: 43, 133, 119;
  --teal-deep-rgb: 31, 91, 82;
  --c-green-ok: #2f7a54;

  /* ── BLUE · civic information. Also the single income family (see
     VARIABLE_COLORS in cambridge.js — the JS ramp is these same steps). ── */
  --c-blue: #2f6db0;
  --c-blue-bright: #2f8fc0;
  --c-blue-mid: #54799a;
  --c-blue-deep: #1c5c84;
  --c-blue-ink: #2c3e5e;
  --c-blue-wash: #edf3f8;
  --blue-rgb: 47, 109, 176;
  --blue-deep-rgb: 28, 92, 132;

  /* ── CHROME ACCENT · Atlas Nr. 51 Spinellblau. THE interface accent since
     2026-08-16, replacing teal. This is a WCAG fix, not a taste change: teal
     #2b8577 measures 4.11:1 on the #f4f6f9 page field and fails the AA floor
     of 4.5 that the PDR commits to. Spinellblau measures 6.66:1 on the same
     field and 7.22:1 for white-on-fill (Mapbox contrast tool, 2026-08-16).

     No new hex was introduced. --c-blue-deep was ALREADY #1c5c84, and ten
     focus rings and checkboxes had drifted onto it on their own before this
     landed. The three names below exist so that "chrome" is greppable, and so
     that a later edit to the income family cannot silently restyle the
     interface.

     ── THE FORM LAW (read before using either blue) ──
     Chrome blue #1c5c84 and the income ramp's darkest step #1c4f84
     (VARIABLE_COLORS.income, cambridge.js:113/:125/:130) are 1.16:1 apart —
     the same colour to a viewer. That would break this file's own ONE COLOUR
     PER VARIABLE law (cambridge.js:89) if the two ever met. They never meet,
     because they are separated by FORM, not by hue:
       · chrome blue — a 2px underline, a focus ring, a filled control with
                       white text, or a wash at <= 12%. DOM chrome only.
       · income blue — a map fill or a legend swatch. Never chrome.
     Chrome blue is never PAINTED INTO the map as data: no fill, no stroke, no
     legend swatch. Where chrome must sit over the map at all — the parcel
     popup, the layer dock, the solar bar — it does so on its own opaque white
     card, and that card is what separates it from the income fills underneath
     (PDR 2.4, "nothing floats on the map").
     ONE DOCUMENTED EXCEPTION: the .hood-donut selection ring, which has no
     card. It is disclosed with its measurements at that rule, and it needs a
     form fix, not a hue fix.
     The map's OWN selection highlight (VARIABLE_COLORS.selection,
     cambridge.js:183) therefore stays TEAL and did not move: it is painted
     into the canvas on top of income blue, which is the one place chrome blue
     may not go. Selection in the DOM (rows, bars, chips) is chrome blue
     instead, because there it sits on top of a TEAL chart series and must not
     share that hue either. Same principle, opposite answer, different
     neighbours.
     The map's own selection highlight therefore stays TEAL: it sits directly
     on top of income blue, where blue-on-blue would be unreadable. Selection
     in the DOM (rows, bars, donut rings) is chrome blue instead, because there
     it sits on top of a TEAL chart series and must not share that hue either.
     Do NOT resolve this by moving the income ramp — its stops carry documented
     provenance and are reproduced in prototypes/reports.html and
     neighborhood-explorer.html.

     ── DO NOT "CLEAN UP" --c-teal-deep ──
     #1f5b52 is also a DATA value: it is the dark end of the assessed-value
     ramp, written as a literal at cambridge.js:828, :3466 and :3468. Retiring
     the token here would not touch those literals (correct), and repointing
     the token would silently restyle a published legend (not correct). ── */
  --c-chrome:      var(--c-blue-deep);    /* #1c5c84 Spinellblau (Nr. 51) */
  --c-chrome-wash: var(--c-blue-wash);    /* #edf3f8 */
  --chrome-rgb:    var(--blue-deep-rgb);  /* 28, 92, 132 */

  /* ── AMBER · alerts and highlights only (DESIGN.md r4-D7). --c-amber moved
     from #a9781f to Civic Burnt Orange #B45309 on 2026-08-16, for the same
     WCAG reason as the accent: #a9781f measures 3.60:1 on the page field and
     fails AA; #B45309 measures 4.64:1 and passes.
     The rest of the family deliberately did NOT move. --c-amber-deep #8a5e12
     is the text companion and already measures 5.26:1, so it was never the
     failure; -mid / -bright / -wash-2 are the SUN AND SOLAR gradient
     (.ss-track, .ss-sun), which is a data surface, not chrome. Heat stays
     orange-only and shares nothing with blue. ── */
  --c-amber: #B45309;
  --c-amber-deep: #8a5e12;
  --c-amber-mid: #d97706;
  --c-amber-bright: #fbbf24;
  --c-amber-wash: #fbf3e4;
  --c-amber-wash-2: #fef3c7;
  --c-orange-soft: #f29b6e;
  --amber-rgb: 180, 83, 9;   /* #B45309 Civic Burnt Orange */
  --amber-mid-rgb: 217, 119, 6;
  --amber-bright-rgb: 251, 191, 36;

  /* ── RISK ── */
  --c-risk: #c0504d;
  --c-risk-deep: #a5403d;
  --c-risk-soft: #ef5350;
  --c-risk-line: #e6c9c8;
  --c-risk-wash: #fbeaea;
  --risk-rgb: 192, 80, 77;

  /* ── VIOLET · under review / model flag ── */
  --c-violet: #6d3f9c;
  --c-violet-light: #9660be;
  --c-violet-soft: #8479a0;
  --c-violet-muted: #5b4a70;
  --c-violet-deep: #4a2a6b;
  --c-violet-wash: #e2d9ef;
  --c-violet-wash-2: #f6f3fa;
  --violet-rgb: 185, 140, 214;

  /* ── SPACING · one 4/8 scale, nothing off it ── */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;

  /* ── RADII · exactly two. (pill/circle are shapes, not sizes.) ── */
  --r-panel: 12px;
  --r-control: 8px;
  --r-pill: 999px;

  /* ── SHADOWS · nothing heavier than 0 1px 2px ── */
  --shadow-1: 0 1px 2px rgba(var(--ink-rgb), 0.06);
  --shadow-2: 0 1px 2px rgba(var(--ink-rgb), 0.14);
  --shadow-inset: inset 0 1px 2px rgba(var(--ink-rgb), 0.08);

  /* ── OVERRIDE the inherited styles.css warm-cream tokens. cambridge.css
     loads after styles.css, so these win everywhere on this page and the
     shared components stop speaking Atlanta. ── */
  --bg: var(--n-050);
  --surface: var(--n-000);
  --card-bg: var(--n-050);
  --ink: var(--n-900);
  --muted: var(--n-600);
  --line: var(--n-150);
  /* --primary is THE primary-control alias and moved to chrome with the rest
     of the furniture. Inside this file it feeds six call sites, all of them
     chrome (.ss-date.active, .pc-tab.active, .pc-source, .pc-source-kind,
     .vt-btn.active, .map-search button). It also reaches two styles.css
     components that render on this page — .btn-news:hover (Join / Log in) and
     .toast — which are chrome too, and would otherwise have been the last two
     teal controls in the shell. .btn-demo's --primary fill never applies here:
     cambridge.css:1134 overrides Donate to an outline (r4-D7). */
  --primary: var(--c-chrome);
  /* --green stays TEAL on purpose: after this migration its only readers are
     verdicts (.pc-status.status-ok, .rt-pass, .rt-io-good, .redev-low) plus
     two links still queued for round 2 (.pc-expand summary, .pc-share). */
  --primary-light: var(--c-teal);
  --green: var(--c-teal);
  /* --accent is the ALERT alias, not an accent in the chrome sense. All 17 of
     its readers are alert/fail/highlight surfaces, so the amber -> Burnt
     Orange swap carries them correctly and DESIGN.md's r4-D7 rule ("amber =
     alerts & highlights only, never the Donate button") survives untouched.
     The one reader that was NOT an alert — .rail-toggle:focus-visible — was
     moved to --c-chrome; see the note there. */
  --accent: var(--c-amber);
  --amber: var(--c-amber);
  --red: var(--c-risk);
  --sidebar-bg: var(--c-blue-ink);
  --shadow-sm: var(--shadow-1);
  --shadow-md: var(--shadow-1);
  --shadow-lg: var(--shadow-2);
  --shadow-glow-green: var(--shadow-1);
  --shadow-glow-amber: var(--shadow-1);
  --shadow-glow-red: var(--shadow-1);

  /* Lets the UA paint scrollbars, form controls and the canvas backstop to
     match. Without it a dark page keeps white scrollbars. */
  color-scheme: light;
}

/* ============================================================================
   DARK · the SAME token names, re-declared   (2026-08-17)
   ----------------------------------------------------------------------------
   Light is the product default and stays that way (DESIGN.md, 2026-07-17:
   "Light civic theme is the default; dark is a secondary variant only"). This
   scope is opt-in via <html data-theme="dark">, set before first paint by the
   inline script in cambridge.html and persisted at localStorage cam360.theme.

   Not one rule below the token blocks changed to make this work, and no token
   was renamed. That is the whole return on "THE ONE TOKEN BLOCK": the file
   already reads every colour through the neutral ramp and six semantic
   families, so dark is a re-declaration of the LEAVES. The aliases
   (--bg, --ink, --primary, --accent, --c-chrome …) are var() references and
   re-resolve on their own — repeating them here would create two places to
   forget. The three exceptions that DO need re-stating are the shadows; see
   the note at the bottom of this block.

   ── THE RAMP INVERTS, THE ROLES DO NOT ──
   In light, a higher --n-* number is darker. In dark it is lighter. What does
   NOT move is what each step MEANS: --n-000 is always the raised surface,
   --n-050 always the page recessed behind it, --n-900 always primary text,
   --n-600 always secondary text. Every rule in this file keeps working because
   every rule was written against the role, not the value.
     · --n-000 #3a4f5e  Atlas Nr. 50, the surface   (cards, header, rail)
     · --n-050 #324049  Atlas Nr. 60, the page      (sits BELOW the surface,
       exactly as light's #f4f6f9 sits below #ffffff)
     · --n-900 #e6ebee  primary text
     · --n-150           hairlines, #d2dadf at reduced alpha
   The two "rgb" companions invert with them, and this is deliberate:
   --white-rgb is not literally white, it is THE SURFACE — its nine call sites
   are all card fills at alpha 0.85-0.99, so pointing it at #3a4f5e carries
   every floating card into dark at once. --ink-rgb is THE TEXT: its 58 call
   sites are low-alpha washes and hairlines (0.03-0.16) plus a few dimmed text
   colours (0.47-0.75), and both readings stay correct once it is light.

   ── THE ACCENT IS DERIVED, NOT CHOSEN. DO NOT "SIMPLIFY" IT BACK. ──
   Spinellblau #1c5c84 is the light accent and it CANNOT come along: measured
   1.48:1 on #324049 (Mapbox contrast tool, 2026-08-16). Unusable, not merely
   dim.
   The trap on the way out is that this theme has TWO grounds, and the darker
   one flatters. #7db8e0 clears the page at 4.99:1 and FAILS the surface at
   3.99:1 — and the surface is where the chrome actually sits, on cards. So the
   rule is: THE ACCENT MUST CLEAR THE LIGHTER OF THE TWO GROUNDS.
   #95c6e9 clears both — 4.69:1 on #3a4f5e, 5.87:1 on #324049.
   Anyone tempted to fold this back to Spinellblau, or to stop at #7db8e0
   because it "passes", should re-measure against #3a4f5e first.

   ── THE ALERT IS DERIVED THE SAME WAY ──
   Civic Burnt Orange #B45309 measures 1.70:1 on #3a4f5e. Walking the same hue
   up in lightness: #d97706 = 2.68, #e8933f = 3.53, #f0a868 = 4.27 (still
   short), #f4b17c = 4.65 — the first step that clears. On the page it measures
   5.81:1. #f4b17c is HSL(26.5, 85%, 72%) against Burnt Orange's HSL(26, 90%,
   37%): the hue is held and only lightness moves, which is the same operation
   that took #1c5c84 (HSL 203, 65%, 31%) to #95c6e9 (HSL 205, 66%, 75%).
   It lands at 4.65 against the accent's 4.69 on the same ground, so neither
   chrome colour outshouts the other.

   ── EVERY OTHER FAMILY MIRRORS ITS LIGHT CONTRAST, IT DOES NOT INVENT ONE ──
   Measured on the surface it sits on (#3a4f5e dark, #ffffff light):
     token            dark              light            verdict
     --n-600          #bdcad5  5.11     #586b7d  5.51    AA both
     --n-500          #93a7b7  3.44     #8593a2  3.14    sub-AA in BOTH — this
                      step was never body text, it is a tertiary/decorative
                      grey, and dark is the better of the two
     --c-amber-deep   #f0c68e  5.36     #8a5e12  5.69    AA both
     --c-risk         #f4aba8  4.57     #c0504d  4.67    AA both
     --c-violet       #d9c2f0  5.25     #6d3f9c  7.39    AA both
     --c-green-ok     #7fd3a8  4.79     #2f7a54  5.21    AA both
     --c-teal         #75c8b8  4.35     #2b8577  4.45    sub-AA in BOTH, by
                      design: teal's AA failure is precisely why chrome left it
                      on 2026-08-16 (see the TEAL note above). Dark reproduces
                      the light shortfall rather than inventing a new one; if
                      teal is ever fixed, fix it in both scopes together.
   The rule this table encodes: NO DARK TOKEN MAY BE WORSE THAN ITS LIGHT
   COUNTERPART. Two are equal-and-still-short and say so out loud.

   ── --c-teal-deep IS SAFE TO RE-DECLARE HERE ──
   The TEAL note above warns that #1f5b52 is also the dark end of the
   assessed-value ramp. That ramp is written as LITERALS in cambridge.js
   (:828, :3466, :3468), not through this token — as that note itself records,
   "retiring the token here would not touch those literals". Re-pointing it in
   this scope therefore restyles the verdict chrome and leaves the published
   legend alone, which is the outcome we want.
   ========================================================================== */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* ── NEUTRAL RAMP · inverted. Same slate family, same roles. ── */
  --n-000: #3a4f5e;   /* SURFACE — Atlas Nr. 50 (cards, header, rail) */
  --n-025: #364a58;   /* surface, one step recessed */
  --n-050: #324049;   /* PAGE — Atlas Nr. 60 */
  --n-100: #2b3841;   /* wash / inset — one step below the page */
  /* HAIRLINES — Atlas #d2dadf at reduced alpha, kept as an alpha rather than
     flattened to a hex so it composites correctly over BOTH grounds, which are
     5 points of lightness apart. 0.22 not 0.18: four of the 42 call sites use
     this token as a fill (.hdr-sep, .bo-toggle, .cl-minbtn:hover, the map
     placeholder), and those need to read as well as the 38 borders do. */
  --n-150: rgba(210, 218, 223, 0.22);
  --n-200: #4e6373;
  --n-250: #566c7d;
  --n-300: #647b8d;
  --n-400: #7d93a4;
  --n-500: #93a7b7;   /* tertiary / decorative — 3.44:1, see table above */
  --n-550: #9db0bf;
  --n-600: #bdcad5;   /* SECONDARY TEXT — 5.11:1 on the surface */
  --n-700: #c4d0da;
  --n-800: #d2dbe3;
  --n-850: #dee5eb;
  --n-900: #e6ebee;   /* PRIMARY TEXT — 7.11:1 on surface, 8.90:1 on page */
  --n-1000: #ffffff;
  --ink-rgb: 230, 235, 238;   /* = --n-900. "ink" means text, not "dark". */
  --n-500-rgb: 147, 167, 183;
  --white-rgb: 58, 79, 94;    /* = --n-000. "white" means surface. */

  /* ── TEAL · verdicts + the Updates/Finding chart series ── */
  --c-teal: #75c8b8;
  --c-teal-deep: #4ea596;
  --c-teal-wash: #2c4a48;
  --teal-rgb: 117, 200, 184;
  --teal-deep-rgb: 78, 165, 150;
  --c-green-ok: #7fd3a8;

  /* ── BLUE · civic information. NOTE: the income ramp on the MAP does not
     read these — it is VARIABLE_COLORS in cambridge.js, and the map stays
     light. These are the DOM's blues only. ── */
  --c-blue: #7fb2e0;
  --c-blue-bright: #8ec8e8;
  --c-blue-mid: #9fb7cc;
  --c-blue-deep: #95c6e9;     /* THE DARK ACCENT — derived above, do not fold */
  --c-blue-ink: #cdd9e6;
  --c-blue-wash: #2c4256;
  --blue-rgb: 127, 178, 224;
  --blue-deep-rgb: 149, 198, 233;

  /* ── AMBER · alerts and highlights only (DESIGN.md r4-D7) ── */
  --c-amber: #f4b17c;         /* Civic Burnt Orange, lifted — derived above */
  --c-amber-deep: #f0c68e;
  --c-amber-mid: #e8a05a;
  --c-amber-bright: #fbd07a;
  --c-amber-wash: #43382a;
  --c-amber-wash-2: #4a3c25;
  --c-orange-soft: #f3ab84;
  --amber-rgb: 244, 177, 124;
  --amber-mid-rgb: 232, 160, 90;
  --amber-bright-rgb: 251, 208, 122;

  /* ── RISK ── */
  --c-risk: #f4aba8;
  --c-risk-deep: #f8c3c1;
  --c-risk-soft: #ef8b87;
  --c-risk-line: #6b4a4a;
  --c-risk-wash: #452e2e;
  --risk-rgb: 244, 171, 168;

  /* ── VIOLET · under review / model flag ── */
  --c-violet: #d9c2f0;
  --c-violet-light: #e4d4f5;
  --c-violet-soft: #b9abd0;
  --c-violet-muted: #a394bb;
  --c-violet-deep: #c0a3e0;
  --c-violet-wash: #3d3350;
  --c-violet-wash-2: #342c44;
  --violet-rgb: 217, 194, 240;

  /* ── SHADOWS · the one group that CANNOT ride the aliases. All three are
     built from rgba(var(--ink-rgb), a), and --ink-rgb is now LIGHT — leaving
     them alone would turn every drop shadow into a white halo. Re-stated on
     black, and deeper, because a 6%-black shadow is invisible on #324049. ── */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.30);
  --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.45);
  --shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

/* ── THE MAP STAYS LIGHT. THIS IS CARTOGRAPHY, NOT A COMPROMISE. ─────────────
   There is deliberately no Mapbox style swap in the dark scope, and there must
   not be one.

   1. EVERY SEQUENTIAL RAMP ENCODES MAGNITUDE BY LIGHTNESS AGAINST ITS GROUND.
      Flip the ground and the reading inverts. Income measures 1.06 -> 7.76
      ascending against a light basemap; against a dark one the same five stops
      measure 9.28 -> 1.27, i.e. DESCENDING — the top step, the one the whole
      layer is about, becomes the least visible thing on the map.
   2. RE-TUNING THE RAMPS IS NOT AVAILABLE EITHER. Five of the map's colours are
      the City of Cambridge's official CDD district colours, which DESIGN.md
      forbids restyling. A dark basemap would require exactly the restyle that
      rule prohibits.

   Two things in cambridge.js are written for a light basemap and are LEFT
   ALONE on purpose: the 7 hard-coded white text halos (:717, :942, :2161,
   :4747, :5080, :8712, :8820) and applyBasemapTint() (:8698). They are correct
   as they stand and only become wrong if someone breaks rule 1 above.

   The DOM chrome that floats over the map does follow the theme — the legend
   card, the layer dock, the parcel card. Those are interface, not cartography,
   and they read the tokens like everything else. What stays put inside them
   are the LEGEND SWATCHES: those are painted from VARIABLE_COLORS by JS and
   must keep matching the map exactly. ── */

/* Legend and dock swatches are map-data colours sitting on a card that just
   went dark. The fills may not move (they have to match the canvas), so the
   dark end of a ramp gets a hairline ring instead — it separates the swatch
   from the card without touching a single published value. */
:root[data-theme="dark"] .legend-dot,
:root[data-theme="dark"] .cl-sw {
  box-shadow: 0 0 0 1px rgba(210, 218, 223, 0.35);
}

/* ── ON-CHROME TEXT · why seven `color: white` became `color: var(--n-000)` ──
   In light both are #ffffff, so this changed nothing — verified pixel-identical.
   In dark it is the difference between working and not: every one of the seven
   sits on a FILL that inverts (--primary -> #95c6e9, --accent -> #f4b17c,
   --green -> #75c8b8, --ink -> #e6ebee), and white on a light fill measures
   1.5-1.7:1. --n-000 is "the surface", so it lands on #3a4f5e — 4.69:1 on the
   accent, the same pair the accent was derived for.
   The seven: .btn-news:hover and .btn-demo (absorbed shell), .toast, and four
   in the parcel card — .pc-close:hover, .pc-source-kind, .pc-btn-approve:hover,
   .pc-btn-flag:hover. Fourteen call sites in this file already read
   var(--n-000) for exactly this; these seven had simply not been converted. */

/* The toast's shadow is the one shadow NOT on the --shadow-* scale — its
   0 8px 24px geometry is inherited from styles.css and kept — so it does not
   ride the re-declaration above and needs re-stating for the same reason the
   scale did: built on --ink-rgb, which is now light, it renders as a white
   halo. */
:root[data-theme="dark"] .toast { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); }

/* Logo: both colourways ship. The ink mark is invisible on a #3a4f5e header,
   so the white one takes over — same artwork, same alpha channel, RGB only. */
:root[data-theme="dark"] .rail-orbit {
  background-image: url('assets/logo/city360-mark-white.png');
}

/* Theme cross-fade. The transition is live ONLY while .theme-anim sits on
   <html> (added by setTheme, removed 200ms later) — a standing transition on
   every element would tax every hover and every map-driven repaint for the
   sake of one deliberate click.
   Under prefers-reduced-motion this media query never matches, so the switch
   is instantaneous and nothing needs to opt out. */
@media (prefers-reduced-motion: no-preference) {
  html.theme-anim, html.theme-anim * {
    transition: background-color 180ms linear, border-color 180ms linear,
                color 180ms linear, fill 180ms linear !important;
  }
}

/* ============================================================================
   ABSORBED SHELL  ·  the last of styles.css, brought home (2026-08-17)
   ----------------------------------------------------------------------------
   cambridge.html used to load styles.css — 1,327 lines of the ATLANTA demo's
   warm-cream design system — purely for the handful of rules below. That file
   carries ~47 raw hexes and ~135 raw rgba() literals no token swap can reach,
   which is the root cause the PDR (§7.10) filed as "not cohesive", and it is
   why a dark mode would have been a two-file problem. The <link> is gone; this
   block is what it was actually contributing.

   ── WHAT WAS MEASURED, NOT ASSUMED ──
   Of the 371 selectors in styles.css, 23 matched anything in this page's DOM
   (probed live with querySelectorAll against every rule in the sheet). Of those
   23, three groups turned out to contribute NOTHING and were dropped rather
   than copied:
     · :root — all 19 of its custom properties are re-declared in the token
       block above, so not one value ever reached an element here.
     · .map-legend — all 11 declarations are overridden by our own .map-legend
       (see "MAP LEGEND" below); position/bottom/left/background/backdrop-filter/
       border/radius/padding/z-index/shadow/min-width, the whole rule.
     · .legend-title — same, all 5 declarations overridden.
   One further declaration was dropped on its own: styles.css gave .toolbar
   `background: rgba(246,244,240,0.85)`, Atlanta warm cream. .hdr360 has painted
   over it with var(--n-000) since this header existed, so it was dead pixels
   AND a foreign colour. Its sibling `backdrop-filter` is kept below.

   ── WHY THIS BLOCK SITS HERE, DIRECTLY UNDER :root ──
   styles.css loaded BEFORE cambridge.css, so every rule in it lost ties to
   every rule in this file. Keeping these at the TOP of this file reproduces
   that cascade exactly: .sidebar.rail still beats .sidebar, .hdr360 still
   beats .toolbar, .rail-foot still beats .sidebar-footer. Do not move this
   block downward, and do not append to it — new rules belong further down.

   Rewritten against the token block above: no raw hex, and the white alphas
   go through --white-rgb like the rest of the file.
   ========================================================================== */

/* Reset. The single highest-leverage thing styles.css was doing here: without
   it 1,488 of this page's 1,527 elements change box model or metrics. */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%; width: 100%;
  font-family: 'Manrope', sans-serif;
  background: var(--bg); color: var(--ink);
  overflow: hidden;
}

/* Shell frame. .sidebar.rail and .main re-state width/margin further down
   (252px, and 232px under 1250px); these are the base values they override. */
.sidebar {
  position: fixed; left: 0; top: 0; bottom: 0;
  width: 220px;
  background: var(--sidebar-bg);
  display: flex; flex-direction: column;
  z-index: 100;
  border-right: 1px solid rgba(var(--white-rgb), 0.04);
}
.sidebar-footer {
  padding: 16px 22px;
  font-size: 0.68rem;
  color: rgba(var(--white-rgb), 0.25);
  border-top: 1px solid rgba(var(--white-rgb), 0.06);
}
.main { margin-left: 220px; height: 100vh; display: flex; flex-direction: column; }

@media (max-width: 1024px) {
  .sidebar { transform: translateX(-100%); }
  .main { margin-left: 0; }
}

/* Header base. .hdr360 restyles nearly all of it; what survives from here is
   justify-content, flex-shrink and the backdrop-filter. */
.toolbar {
  height: 56px; padding: 0 24px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
  flex-shrink: 0;
  backdrop-filter: blur(12px);
}
.toolbar-right { display: flex; align-items: center; gap: 12px; }

/* Header buttons: .btn-news is Join / Log in, .btn-demo is Donate (which
   .btn-donate then turns into an outline — r4-D7, amber is for alerts). */
.btn-news {
  padding: 7px 16px;
  font-size: 0.78rem; font-weight: 600;
  background: var(--card-bg); color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 100px;
  cursor: pointer;
  font-family: 'Manrope', sans-serif;
  transition: all 0.2s;
}
.btn-news:hover { background: var(--primary); color: var(--n-000); border-color: var(--primary); }
.btn-demo {
  padding: 8px 20px;
  font-size: 0.78rem; font-weight: 600;
  background: var(--primary); color: var(--n-000);
  border: none;
  border-radius: 100px;
  cursor: pointer;
  font-family: 'Manrope', sans-serif;
  transition: opacity 0.2s;
}
.btn-demo:hover { opacity: 0.85; }

/* #map — this file re-states width/height/min-height at "LAYOUT" below; the
   radius and the shadow are the two that only ever came from here. 12px is
   --r-panel to the pixel. */
#map { width: 100%; height: 100%; border-radius: var(--r-panel); min-height: 400px; box-shadow: var(--shadow-lg); }

.mapboxgl-ctrl-logo { display: none !important; }

/* Toast. --primary is chrome Spinellblau, so the fill was already civic; the
   drop shadow was NOT — styles.css wrote it as rgba(27,58,45,0.3), Atlanta's
   dark green, the only literal in this block that had no token to land on.
   Geometry and alpha are kept to the number and only the hue moves onto ink,
   which is the smallest faithful translation available. The toast renders at
   opacity 0 until .show, so nothing on first paint depends on it.
   The label reads var(--n-000) rather than the literal `white` styles.css
   used: identical in light, and the only correct answer in dark, where
   --primary is a LIGHT blue and white-on-it measures 1.70:1. See the
   ON-CHROME TEXT note in the dark scope. */
.toast {
  position: fixed; bottom: 24px; right: 24px;
  background: var(--primary); color: var(--n-000);
  padding: 14px 22px;
  border-radius: 12px;
  font-size: 0.85rem; font-weight: 500;
  box-shadow: 0 8px 24px rgba(var(--ink-rgb), 0.3);
  transform: translateY(100px); opacity: 0;
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s;
  z-index: 9999;
  max-width: 380px;
  font-family: 'Manrope', sans-serif;
}
.toast.show { transform: translateY(0); opacity: 1; }
/* ═══ end absorbed shell ═══ */

/* ── NUMBER DISCIPLINE (research rule 7). Every number-bearing surface reads
   in lining tabular figures so columns align and a digit never changes width
   between two states. Paired with fmt() in cambridge.js, which is the only
   place a number is formatted. ── */
.pc-hero-val, .pc-kv strong, .pc-lk-row strong, .es-cap b, .es-rank, .es-donut b,
.stat-val, .dash-stat b, .dash-tile b, .tv-table td, .tv-table th,
.rules-matrix td, .rm-post, .rm-pre, .tl-row, .ul-val, .isp-key b, .cl-count,
.pm-bv, .ss-display, .ss-hour, .es-lgt, .bo-val, .dnut-val, .hood-detail td,
.hood-detail b, .stat b, .metric-val, .num {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* ============================================
   POLYMETRON · Cambridge demo · v2 (UX redesign)
   Map = hero. Floating parcel card. Solar slider.
   ============================================ */

/* ── LAYOUT: full-width map canvas ─────────── */
.view-dashboard.active { display: flex !important; flex-direction: column !important; }

.map-canvas {
  position: relative;
  flex: 1;
  margin: 16px 24px 24px;
  border-radius: var(--r-panel);
  overflow: hidden;
  background: var(--n-150);
  box-shadow: var(--shadow-lg);
}
#map { width: 100%; height: 100%; min-height: 480px; }

.map-watermark {
  position: absolute; top: 54px; left: 26px;
  font-size: 0.62rem; letter-spacing: 0.22em;
  color: rgba(var(--ink-rgb),0.12); font-weight: 700;
  pointer-events: none; z-index: 5;
}

/* NOTE: no backdrop-filter on any panel that floats OVER the map canvas.
   Chromium's backdrop promotion fails intermittently over a heavy WebGL scene
   (deck.gl + city 3D) under GPU memory pressure and paints the element as an
   OPAQUE WHITE RECTANGLE with the DOM fully intact — the "panel went blank"
   reports (2026-07-25, twice). Solid rgba backgrounds cannot fail that way.
   Panels off the canvas (.cpanel, .table-view, .rail-disc) may keep blur. */

/* ── MAP STATUS PILL (loading indicator) ───── */
.map-status {
  position: absolute; top: 18px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  background: rgba(var(--white-rgb),0.97); 
  border: 1px solid rgba(var(--ink-rgb),0.06); border-radius: var(--r-pill);
  padding: 7px 16px 7px 14px; z-index: 10;
  font-size: 0.74rem; font-weight: 600; color: var(--ink);
  box-shadow: var(--shadow-1);
  letter-spacing: 0.01em;
}
.map-status.error {
  background: rgba(var(--amber-rgb),0.1); color: var(--accent);
  border-color: rgba(var(--amber-rgb),0.25);
}

/* ── DEFAULT-STATE HINT CARD (top-right) ───── */
.map-hint {
  position: absolute; top: 18px; right: 24px;
  width: 280px; padding: 16px 18px;
  background: rgba(var(--white-rgb),0.97); 
  border: 1px solid rgba(var(--ink-rgb),0.06); border-radius: var(--r-panel);
  box-shadow: var(--shadow-1);
  z-index: 10;
  font-family: 'Manrope', sans-serif;
}
.hint-eyebrow {
  font-size: 0.62rem; letter-spacing: 0.14em; font-weight: 700;
  color: var(--c-chrome); margin-bottom: 6px;
}
.hint-title {
  font-family: 'Instrument Serif', serif;
  font-size: 1.15rem; line-height: 1.2; color: var(--ink);
  margin-bottom: 4px;
}
.hint-sub {
  font-size: 0.75rem; color: var(--muted); line-height: 1.45;
  margin-bottom: 10px;
}
.hint-tryline {
  font-size: 0.72rem; color: var(--n-800); line-height: 1.5;
  padding-top: 10px; border-top: 1px dashed rgba(var(--ink-rgb),0.08);
}
/* Moves with .hint-eyebrow above: this card IS the parcel pane's empty state
   (see the note at .pc-hint), and leaving one teal word inside an otherwise
   Spinellblau card would read as a defect rather than a distinction. */
.hint-tryline em { color: var(--c-chrome); font-style: normal; font-weight: 700; }

/* ── MAP LEGEND (bottom-left) ──────────────── */
.map-legend {
  position: absolute; bottom: 110px; left: 24px;
  backdrop-filter: none; -webkit-backdrop-filter: none;  /* overrides styles.css blur — see note above */
  background: rgba(var(--white-rgb),0.97); 
  border: 1px solid rgba(var(--ink-rgb),0.06); border-radius: var(--r-panel);
  padding: 10px 14px; z-index: 10;
  box-shadow: var(--shadow-1);
  min-width: 170px;
}
.legend-title {
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.14em;
  color: var(--muted); margin-bottom: 6px;
}
.legend-items { display: flex; flex-direction: column; gap: 3px; }
.legend-item {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.72rem; color: var(--ink);
}
.legend-dot {
  width: 8px; height: 8px; border-radius: 50%;
  flex-shrink: 0;
}

/* ── SOLAR TIME · A TRANSPORT BAR IN THE BOTTOM BAND'S RIGHT CELL ──────────
   History, because it has moved three times and each move was a correction:
     · a 440x331 column centred on the canvas — 24.7% of the map, 35.9% with
       the build-out block open, sitting on the map's optical centre, which is
       exactly where flyToAndSelect() lands the parcel you searched for;
     · then (2026-08-07) one 56px horizontal strip absolutely positioned on the
       canvas's bottom edge — far better, but still ON the map;
     · now (2026-08-13) IN THE BOTTOM BAND, as .map-footer, beside the legend.
       Rocker: "what is currently floating on the map, we put down there. So
       their shadow tool is always with us… because the shadow is so important,
       I would put the shadow analysis in there."

   So: position:static. The `position:absolute; left/right/bottom:12px` block
   that lived here is GONE, and with it .map-canvas.has-solarbar — with nothing
   over the canvas the Mapbox scale/attribution controls have nothing to step
   over, and --solarbar-h has no reader left.

   56 = 1 border + 4 pad + 46 control + 4 pad + 1 border. Every control is the
   bar's full inner height — a 46px hit area, clear of DESIGN.md's 44px floor —
   while painting only its middle 32px: `background-clip: content-box` over a
   7px vertical padding does that without a fat bar. Focus rings use
   outline-offset:-7px so the ring lands on the paint, not the hit area.

   The card look changes with the position: --r-panel (the radius the legend and
   the selection strip beside it already wear) and no shadow, because a drop
   shadow is what a floating thing needs and this is docked. Solid --n-000
   rather than a 96% white, for the same reason: nothing shows through it now. */
.solar-slider {
  position: static; inset: auto; transform: none; width: auto; max-width: none;
  min-height: 56px; padding: 4px 12px;
  display: flex; flex-wrap: wrap; align-items: center; align-content: center;
  column-gap: 10px; row-gap: 6px;
  background: var(--n-000, #fff);
  border: 1px solid var(--n-150, #e3e8ee); border-radius: var(--r-panel);
  box-shadow: none;
  z-index: auto;
}
/* 46 + 4+4 padding + 2 border = the 56 above, exactly, in border-box.
   The controls are 46px tall (DESIGN.md's floor is 44) and paint only their
   middle 32px, so the bar reads thin while the targets stay big. */
.solar-slider > * { height: 46px; }
/* The scrubber is the ONLY item allowed to wrap. Its 300px basis is the point
   below which a single row stops being honest: squeezing the rail to 40px to
   keep one line is worse than two lines. Below that the bar becomes
   [dates · all hours · play] over [scrubber · readout · ⋯] — ~108px, still a
   third of the old panel, still nowhere near the map's centre. Done with
   flex-basis rather than a media query on purpose: the bar's width depends on
   the rail being open, the right panel being open AND the viewport, and a
   viewport query can only see the third of those. That is precisely the bug in
   the `max-width: calc(100vw - 360px)` rule this replaced. */
.ss-scrub { flex: 1 1 300px; min-width: 0; display: flex; align-items: center; gap: 10px; }

/* Date chips — segmented 4-up. The grey group pill is a pseudo-element so the
   buttons can keep the full 46px hit area inside a 32px-tall visual group. */
.ss-dates {
  position: relative; flex: 0 0 auto;
  display: flex;
}
.ss-dates::before {
  content: ''; position: absolute; left: 0; right: 0; top: 7px; bottom: 7px;
  background: var(--n-100, #eef2f6); border-radius: var(--r-control);
  pointer-events: none;
}
.ss-date {
  position: relative; z-index: 1; height: 46px;
  padding: 7px 9px; border: 0; background: transparent; background-clip: content-box;
  border-radius: var(--r-control); cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.75rem; font-weight: 600;
  color: var(--n-600, #586b7d); white-space: nowrap;
  transition: background-color 0.15s, color 0.15s;
}
.ss-date:hover { color: var(--n-900, #1e2732); background: var(--n-150, #e3e8ee); background-clip: content-box; }
.ss-date.active {
  background: var(--c-chrome); background-clip: content-box;
  color: var(--n-000, #fff); font-weight: 700;
}

/* Butterfly mode — every hour 8:00–16:00 as one overlaid fan. */
.ss-bf {
  flex: 0 0 auto;
  padding: 7px 12px; border: 0; background: var(--n-100, #eef2f6); background-clip: content-box;
  border-radius: var(--r-control); cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.75rem; font-weight: 600;
  color: var(--n-700, #46596b); white-space: nowrap;
}
.ss-bf:hover { background: var(--n-150, #e3e8ee); background-clip: content-box; }
.ss-bf[aria-pressed="true"] {
  background: var(--c-blue-deep, #1c5c84); background-clip: content-box; color: var(--n-000, #fff);
}
.solar-slider.bf-on .ss-track { opacity: 0.35; pointer-events: none; }

/* Play — a 64px TRANSPORT BUTTON, not a full-width CTA. Spinellblau is the
   primary accent; spending it full-bleed inside a map overlay declared this
   the most important action on screen. Amber while running (the existing
   aria-pressed swap), which is the one place amber is allowed: a live-state
   highlight. */
.ss-play {
  flex: 0 0 auto; min-width: 64px;
  padding: 7px 12px; border: 0; background: var(--c-chrome); background-clip: content-box;
  border-radius: var(--r-control); cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.75rem; font-weight: 700;
  color: var(--n-000, #fff); white-space: nowrap;
}
.ss-play:hover { filter: brightness(1.12); }
.ss-play[aria-pressed="true"] { background: var(--c-amber-deep, #8a5e12); background-clip: content-box; }

/* Scrubber — the widest thing on the bar, which is right: it is the control
   people actually drag. Ticks ride ON the gradient rail, so they cost zero
   height (they used to hang 12px below it). */
.ss-track { flex: 1 1 auto; min-width: 60px; position: relative; height: 46px;
  display: flex; align-items: center; }
.ss-range {
  width: 100%; margin: 0;
  -webkit-appearance: none; appearance: none;
  height: 20px; border-radius: var(--r-pill);
  background: linear-gradient(to right,
    var(--c-blue-ink) 0%,        /* pre-dawn deep blue */
    var(--c-orange-soft) 12%,       /* sunrise warm */
    var(--c-amber-bright) 30%,       /* morning amber */
    var(--c-amber-wash-2) 50%,       /* noon high */
    var(--c-amber-bright) 70%,       /* afternoon amber */
    var(--c-orange-soft) 88%,       /* sunset warm */
    var(--c-blue-ink) 100%       /* dusk deep blue */
  );
  outline: none; cursor: pointer;
  box-shadow: var(--shadow-inset);
}
.ss-ticks {
  position: absolute; left: 0; right: 0; top: 13px; height: 20px;
  pointer-events: none;
}
/* 11px, full opacity — DESIGN.md's caption floor. Was 0.56rem (≈9px) at
   opacity .6, which broke both the size floor and 4.5:1. Ink on the amber
   rail is ~7:1; the white halo carries it over the deep-blue ends. */
.ss-tick {
  position: absolute; top: 50%; transform: translateX(-50%) translateY(-50%);
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
  color: var(--n-900, #1e2732);
  text-shadow: 0 0 3px rgba(var(--white-rgb),0.95), 0 0 2px rgba(var(--white-rgb),0.95);
}

/* Sun thumb — amber gradient + soft glow */
.ss-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 24px; height: 24px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 35%, var(--c-amber-wash-2) 0%, var(--c-amber-bright) 45%, var(--c-amber-mid) 100%);
  border: 2px solid white;
  cursor: grab;
  box-shadow: 0 0 0 1px rgba(var(--amber-mid-rgb), 0.4), var(--shadow-2);
  transition: transform 0.12s, box-shadow 0.12s;
}
.ss-range::-webkit-slider-thumb:hover {
  transform: scale(1.14);
  box-shadow: 0 0 0 1px rgba(var(--amber-mid-rgb), 0.6), var(--shadow-2);
}
.ss-range::-webkit-slider-thumb:active { cursor: grabbing; transform: scale(1.06); }

.ss-range::-moz-range-thumb {
  width: 24px; height: 24px; border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, var(--c-amber-wash-2) 0%, var(--c-amber-bright) 45%, var(--c-amber-mid) 100%);
  border: 2px solid white;
  cursor: grab;
  box-shadow: 0 0 0 1px rgba(var(--amber-mid-rgb), 0.4), var(--shadow-2);
}

/* Readout — one 13px inline slot. It was a 1.12rem Instrument Serif two-line
   hero; tabular-nums so it cannot jitter the layout while you scrub. */
.ss-display {
  flex: 0 0 auto; min-width: 134px; height: 46px;
  display: flex; align-items: center; justify-content: flex-end; text-align: right;
  font-family: 'Manrope', sans-serif; font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--n-800, #3d4854);
  white-space: nowrap; letter-spacing: 0.01em;
}

/* "⋯" — speed and record, the two things nobody sets mid-task. */
.ss-more-wrap { position: relative; flex: 0 0 auto; display: flex; }
.ss-more-btn {
  width: 36px; height: 46px; padding: 7px 0;
  border: 0; background: var(--n-100, #eef2f6); background-clip: content-box;
  border-radius: var(--r-control); cursor: pointer;
  font-size: 1rem; line-height: 1; color: var(--n-700, #46596b);
}
.ss-more-btn:hover { background: var(--n-150, #e3e8ee); background-clip: content-box; }
.ss-more-btn[aria-expanded="true"] { background: var(--n-200, #cfd8e0); background-clip: content-box; }
.ss-pop {
  position: absolute; bottom: calc(100% + 10px); right: 0; z-index: 3;
  width: 236px; padding: 12px;
  background: var(--n-000, #fff); border: 1px solid var(--n-150, #e3e8ee);
  border-radius: var(--r-panel); box-shadow: var(--shadow-2);
}
.ss-pop[hidden] { display: none; }
.ss-pop-row { display: flex; align-items: center; gap: 8px; }
.ss-pop-row label {
  font-size: 11px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--n-600, #586b7d); flex: 0 0 auto;
}
.ss-pop-row input[type="range"] { flex: 1 1 auto; min-width: 0; accent-color: var(--c-chrome); height: 18px; }
/* A bare speed slider with no readout — you could not tell what you had set. */
.ss-speed-val { flex: 0 0 auto; font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--n-800, #3d4854); min-width: 34px; text-align: right; }
.ss-rec {
  display: block; width: 100%; margin-top: 10px;
  font-family: 'Manrope', sans-serif; font-size: 12px; font-weight: 700;
  border: 1px solid var(--n-150, #e3e8ee); background: var(--n-000, #fff); color: var(--c-risk);
  padding: 8px; border-radius: var(--r-control); cursor: pointer;
}
.ss-rec:hover { background: var(--c-risk-wash, #fbeaea); }
.ss-rec.on { background: var(--c-risk); border-color: var(--c-risk); color: var(--n-000, #fff); }
.ss-rec:disabled { opacity: 0.5; cursor: default; }
/* 11px floor — was 0.56rem (≈9px). */
.ss-recnote { font-size: 11px; color: var(--n-600, #586b7d); line-height: 1.45; margin-top: 8px; }
.ss-recnote:empty { display: none; }

/* Visible focus everywhere. The -12px offset draws the ring on the 32px
   visual rather than around the invisible 56px hit area. */
.solar-slider button:focus-visible {
  outline: 2px solid var(--c-chrome); outline-offset: -7px;
}
.ss-pop button:focus-visible, .ss-pop input:focus-visible {
  outline: 2px solid var(--c-chrome); outline-offset: 2px;
}
.ss-range:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 3px; }

/* The day sweep is user-initiated and has a pause control, so it is not
   auto-playing motion; the decorative transitions still stand down. */
@media (prefers-reduced-motion: reduce) {
  .solar-slider *, .ss-range::-webkit-slider-thumb { transition: none !important; }
  .ss-range::-webkit-slider-thumb:hover { transform: none; }
}

/* ── FLOATING PARCEL CARD (top-right, on click) */
.parcel-card {
  position: absolute; top: 18px; right: 24px;
  width: 420px;
  max-height: calc(100% - 36px);
  background: rgba(var(--white-rgb),0.97); backdrop-filter: blur(24px);
  border: 1px solid rgba(var(--ink-rgb),0.06); border-radius: var(--r-panel);
  box-shadow: var(--shadow-2);
  z-index: 20;
  display: flex; flex-direction: column;
  font-family: 'Manrope', sans-serif;
  overflow: hidden;
  animation: pcSlideIn 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes pcSlideIn {
  from { opacity: 0; transform: translateX(12px) scale(0.97); }
  to   { opacity: 1; transform: translateX(0) scale(1); }
}

.pc-close {
  position: absolute; top: 12px; right: 12px;
  width: 28px; height: 28px; border-radius: 50%;
  background: rgba(var(--ink-rgb),0.05); border: none;
  font-size: 1.3rem; cursor: pointer; color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  font-family: sans-serif; z-index: 2;
  transition: all 0.15s;
}
.pc-close:hover { background: var(--ink); color: var(--n-000); }

/* Header */
.pc-header { padding: 18px 22px 14px; padding-right: 50px; }
.pc-address {
  font-family: 'Instrument Serif', serif;
  font-size: 1.3rem; font-weight: 400; color: var(--ink);
  line-height: 1.2;
}
.pc-meta {
  font-size: 0.76rem; color: var(--muted);
  margin-top: 4px; letter-spacing: 0.01em;
}

/* Hero metric */
.pc-hero {
  margin: 0 22px 14px; padding: 12px 2px 14px;
  border-bottom: 1px solid var(--line, rgba(var(--ink-rgb),0.1));
}
.pc-hero-label {
  font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase;
  font-weight: 700; color: var(--muted);
}
.pc-hero-value {
  font-size: 1.6rem; font-weight: 700; color: var(--ink);
  letter-spacing: -0.01em; font-variant-numeric: tabular-nums;
  white-space: nowrap; line-height: 1.15; margin-top: 3px;
}
.pc-hero-note { font-size: 0.66rem; color: var(--muted); margin-top: 3px; }

/* §19.52 status — one calm row: status icon + verdict, color only on the icon */
.pc-status {
  margin: 0 22px 14px; padding: 11px 13px;
  border: 1px solid var(--line, rgba(var(--ink-rgb),0.1)); border-radius: var(--r-panel);
  background: var(--n-000); display: flex; gap: 11px; align-items: flex-start;
}
.pc-status-ic { width: 20px; height: 20px; flex: 0 0 auto; margin-top: 1px;
  fill: none; stroke: var(--muted); stroke-width: 1.6;
  stroke-linejoin: round; stroke-linecap: round; }
.pc-status.status-fail .pc-status-ic { stroke: var(--accent); }
.pc-status.status-ok   .pc-status-ic { stroke: var(--green); }
.pc-status-body { min-width: 0; }
#pc-status-text {
  font-size: 0.74rem; font-weight: 700; letter-spacing: 0.01em;
  color: var(--ink); display: block;
}
.pc-status-detail { font-size: 0.72rem; color: var(--muted);
  margin-top: 3px; line-height: 1.45; }

/* Tabs */

.pc-tabs {
  display: flex; gap: 0;
  border-bottom: 1px solid rgba(var(--ink-rgb),0.08);
  padding: 0 22px;
}
.pc-tab {
  padding: 11px 13px;
  font-size: 0.9rem; font-weight: 600; letter-spacing: 0.01em;
  background: none; border: none;
  color: var(--muted); cursor: pointer;
  border-bottom: 2px solid transparent;
  font-family: 'Manrope', sans-serif;
  transition: color 0.15s, border-color 0.2s;
}
.pc-tab:hover { color: var(--ink); }
.pc-tab.active {
  color: var(--ink); border-bottom-color: var(--primary);
}

/* Tab content — grows with the viewport and scrolls */
.pc-content {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 14px 22px 16px;
  max-height: calc(100vh - 300px);
}
.pc-pane { display: none; animation: paneFade 0.2s ease; }
.pc-pane.active { display: block; }
@keyframes paneFade { from { opacity: 0; } to { opacity: 1; } }

.pc-kv {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 7px 0; border-bottom: 1px solid rgba(var(--ink-rgb),0.05);
  font-size: 0.86rem;
}
.pc-kv:last-of-type { border-bottom: none; }
.pc-kv span { color: var(--muted); }
.pc-kv strong { color: var(--ink); font-weight: 600; }
.pc-kv-strong { padding-top: 8px; margin-top: 4px; border-top: 1px solid rgba(var(--ink-rgb),0.08); }
.pc-kv-strong:first-of-type { border-top: 1px solid rgba(var(--ink-rgb),0.08); }

.pc-expand {
  margin-top: 12px;
  font-size: 0.72rem;
}
.pc-expand summary {
  cursor: pointer; padding: 6px 0;
  color: var(--green); font-weight: 600;
  list-style: none; outline: none;
}
/* The rule above sets `outline: none` and never replaced it, so all three folds
   in this card were operable from the keyboard with NO visible focus indicator
   — against DESIGN.md's "visible focus rings everywhere". Same ring the rest of
   the shell uses (.method-about > summary, .t360[data-view]); nothing changes
   at rest. */
.pc-expand summary:focus-visible { outline: 2px solid var(--c-chrome);
  outline-offset: 2px; border-radius: var(--r-control); }
.pc-expand summary::-webkit-details-marker { display: none; }
.pc-expand summary::before {
  content: '+ '; font-weight: 700; margin-right: 2px;
}
.pc-expand[open] summary::before { content: '− '; }
.pc-expand-body {
  padding: 8px 12px;
  background: rgba(var(--ink-rgb),0.03);
  border-radius: var(--r-control);
  color: var(--n-800); line-height: 1.55;
}
.pc-expand-body code {
  font-family: 'SF Mono', Menlo, monospace; font-size: 0.7rem;
  background: rgba(var(--ink-rgb),0.06); padding: 1px 4px; border-radius: var(--r-control);
}

.pc-note {
  margin-top: 12px; padding: 12px 14px;
  background: rgba(var(--amber-rgb),0.05);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-control) var(--r-control) 0;
  font-size: 0.74rem; color: var(--n-800); line-height: 1.55;
}
.pc-note strong { color: var(--accent); }

/* Sources */
.pc-source {
  display: block; padding: 10px 12px; margin-bottom: 6px;
  background: var(--card-bg); border: 1px solid var(--line);
  border-radius: var(--r-control); text-decoration: none;
  transition: all 0.15s;
}
.pc-source:hover {
  border-color: var(--primary); background: white;
  transform: translateX(-2px);
  box-shadow: var(--shadow-1);
}
.pc-source-kind {
  display: inline-block; padding: 2px 7px;
  background: var(--primary); color: var(--n-000);
  font-size: 0.58rem; font-weight: 700; letter-spacing: 0.08em;
  border-radius: var(--r-control); margin-bottom: 5px;
}
.pc-source-title {
  display: block; font-size: 0.78rem; color: var(--ink);
  font-weight: 500; line-height: 1.4;
}

/* Footer (actions) */
.pc-footer {
  display: flex; gap: 8px;
  padding: 12px 22px;
  border-top: 1px solid rgba(var(--ink-rgb),0.08);
  background: rgba(var(--ink-rgb),0.02);
}
.pc-btn {
  flex: 1; padding: 9px 12px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 0.78rem; font-weight: 600;
  border: 1px solid; background: white;
  border-radius: var(--r-control); cursor: pointer;
  font-family: 'Manrope', sans-serif;
  transition: all 0.18s;
}
.pc-btn:hover { transform: translateY(-1px); }
.pc-btn:active { transform: translateY(0); }
.pc-btn-approve {
  color: var(--green); border-color: rgba(var(--teal-rgb),0.4);
}
.pc-btn-approve:hover {
  background: var(--green); color: var(--n-000); border-color: var(--green);
}
.pc-btn-flag {
  color: var(--accent); border-color: rgba(var(--amber-rgb),0.5);
}
.pc-btn-flag:hover {
  background: var(--accent); color: var(--n-000); border-color: var(--accent);
}

.pc-log {
  padding: 8px 22px 12px;
  font-size: 0.66rem; color: var(--muted);
  max-height: 60px; overflow-y: auto;
}
.pc-log-entry {
  padding: 3px 0; border-bottom: 1px dashed rgba(var(--ink-rgb),0.06);
}
.pc-log-entry:last-child { border-bottom: none; }

/* Layer-key block inside the Solar tab — clarifies ShadeMap vs impact heatmap */
.pc-layer-key {
  margin: 12px 0 10px;
  padding: 10px 12px;
  background: rgba(var(--ink-rgb),0.03);
  border-radius: var(--r-control);
  font-size: 0.7rem;
}
.pc-lk-row {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: 6px 8px;
  align-items: baseline;
  padding: 4px 0;
}
.pc-lk-row strong { color: var(--ink); font-weight: 600; grid-column: 2; }
.pc-lk-detail {
  grid-column: 2; color: var(--muted);
  font-size: 0.65rem; line-height: 1.4;
  margin-top: -1px;
}
.pc-lk-swatch {
  width: 12px; height: 12px; border-radius: var(--r-control);
  grid-row: 1; grid-column: 1;
  margin-top: 2px;
}
.pc-lk-context {
  background: linear-gradient(135deg, rgba(var(--ink-rgb),0.65), rgba(var(--ink-rgb),0.4));
  border: 1px solid rgba(var(--ink-rgb),0.55);
}
.pc-lk-impact {
  background: linear-gradient(135deg, var(--c-risk-soft), var(--c-amber-bright));
  border: 1px solid rgba(var(--amber-rgb),0.55);
}

/* Pre / Post envelope mini-legend (inside Envelope tab) */
.pc-mini-legend {
  display: flex; gap: 10px; margin-bottom: 12px;
  padding: 8px 10px;
  background: rgba(var(--ink-rgb),0.03);
  border-radius: var(--r-control);
  font-size: 0.66rem; color: var(--muted);
  letter-spacing: 0.04em;
}
.mini-chip { display: inline-flex; align-items: center; gap: 5px; }
.chip-swatch {
  display: inline-block; width: 10px; height: 10px; border-radius: var(--r-control);
  border: 1.5px solid;
}
.chip-pre  { background: rgba(var(--n-500-rgb),0.18); border-color: rgba(var(--n-500-rgb),0.7); }
.chip-post { background: rgba(var(--blue-rgb),0.18);  border-color: rgba(var(--blue-rgb),0.9); }

/* ── RESPONSIVE — collapse parcel card on narrow ── */
@media (max-width: 1280px) {
  .map-canvas { margin: 12px 16px 16px; }
  .parcel-card { width: 340px; }
  /* The `.solar-slider { width: 480px; max-width: calc(100vw - 320px) }` that
     lived here was deleted 2026-08-07: it WIDENED the panel as the map got
     narrower, so coverage rose on small screens. The bar has no width of its
     own at all now — it fills .map-footer, the bottom band's right cell, and
     the band's own 1100px breakpoint is what handles narrow. */
}
@media (max-width: 1024px) {
  .parcel-card { width: 320px; }
  .map-hint { width: 240px; }
}

/* ── HIDE old dark-panel styles that no longer apply ── */
.detail-panel.dark-panel { display: none !important; }
.dash-split { display: none !important; }

/* ── Rules tab: pre/post matrix + crucial-rules watch + redev alert ── */
.rules-caption { font-size: 0.82rem; color: var(--muted); line-height: 1.5; margin-bottom: 10px; }
.rules-caption strong { color: var(--ink); }

.rules-watch { margin-bottom: 14px; padding: 0; }
.rw-title {
  font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase;
  font-weight: 700; color: var(--muted); margin-bottom: 6px;
}
.rw-row { display: grid; grid-template-columns: 62px 1fr; gap: 10px; align-items: baseline;
  padding: 6px 0; font-size: 0.78rem; color: var(--n-800); line-height: 1.45;
  border-top: 1px solid var(--line, rgba(var(--ink-rgb),0.08)); }
.rw-tag {
  font-size: 0.56rem; letter-spacing: 0.06em; font-weight: 700; text-transform: uppercase;
  color: var(--muted); }

.rules-matrix { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
.rules-matrix .rm-head th {
  text-align: left; font-size: 0.6rem; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 700; padding: 4px 6px; border-bottom: 1px solid var(--line);
}
.rules-matrix td { padding: 5px 6px; border-bottom: 1px solid rgba(var(--ink-rgb),0.05); vertical-align: top; }
.rules-matrix .rm-k { color: var(--muted); width: 34%; }
.rules-matrix .rm-pre { color: var(--n-500); width: 30%; }
.rules-matrix .rm-post { color: var(--ink); font-weight: 600; }
.rules-matrix tr.rm-changed .rm-post { color: var(--accent); }
.rules-matrix tr.rm-changed .rm-k { color: var(--ink); font-weight: 600; }
.rules-foot { margin-top: 8px; font-size: 0.72rem; color: var(--muted); line-height: 1.5; }
.rules-foot strong { color: var(--n-500); }

.redev-alert { margin-top: 14px; padding: 12px 13px; border-radius: var(--r-panel);
  border: 1px solid var(--line, rgba(var(--ink-rgb),0.1)); background: var(--n-000); }
.redev-head { font-size: 0.78rem; color: var(--ink); display: flex; align-items: center; gap: 9px; font-weight: 600; }
.redev-ic { width: 18px; height: 18px; flex: 0 0 auto; fill: none; stroke: var(--muted);
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.redev-high .redev-ic     { stroke: var(--accent); }
.redev-elevated .redev-ic { stroke: var(--c-amber); }
.redev-low .redev-ic      { stroke: var(--green); }
.redev-level { font-weight: 800; letter-spacing: 0.03em; }
.redev-high .redev-level     { color: var(--accent); }
.redev-elevated .redev-level { color: var(--c-amber); }
.redev-low .redev-level      { color: var(--green); }
.redev-mg { margin-left: auto; font-size: 0.54rem; letter-spacing: 0.09em; text-transform: uppercase;
  font-weight: 700; color: var(--n-400); }
.redev-reasons { margin: 9px 0 0; padding-left: 16px; }
.redev-reasons li { font-size: 0.76rem; color: var(--n-800); line-height: 1.5; margin: 3px 0; }
.redev-rule { margin-top: 9px; font-size: 0.64rem; color: var(--muted); font-style: italic; line-height: 1.45; }
.redev-sale { margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--line, rgba(var(--ink-rgb),0.08));
  font-size: 0.76rem; line-height: 1.5; color: var(--ink); }
.redev-sale.redev-sale-off { color: var(--muted); font-style: italic; }

/* ── Inclusionary-eligible (>5,000 SF) highlight toggle in the legend ── */
.legend-toggle {
  display: flex; align-items: center; gap: 8px;
  margin-top: 8px; padding-top: 8px;
  border-top: 1px solid rgba(var(--ink-rgb),0.08);
  cursor: pointer; user-select: none;
}
.legend-toggle input { accent-color: var(--c-blue-deep); margin: 0; cursor: pointer; width: 13px; height: 13px; }
.lt-swatch { width: 12px; height: 12px; border-radius: var(--r-control); background: rgba(var(--blue-deep-rgb),0.34); border: 1px solid var(--c-blue-deep); flex: 0 0 auto; }
.lt-text { font-size: 0.72rem; color: var(--ink); line-height: 1.2; }
.lt-text small { display: block; font-size: 0.6rem; color: var(--muted); margin-top: 1px; letter-spacing: 0.01em; }

/* ── C-1 character-vs-capacity charts (Hero Finding panel) ── */
.c1-profile { margin-bottom: 16px; }
.c1-eyebrow { font-size: 0.6rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700; color: var(--accent); margin-bottom: 8px; }
.c1-stats { display: flex; gap: 8px; margin-bottom: 14px; }
.c1-stat { flex: 1; background: rgba(var(--ink-rgb),0.03); border: 1px solid rgba(var(--ink-rgb),0.06); border-radius: var(--r-control); padding: 8px 6px; text-align: center; }
.c1-stat b { display: block; font-family: 'Instrument Serif', serif; font-size: 1.5rem; color: var(--ink); line-height: 1; font-variant-numeric: tabular-nums; }
.c1-stat span { font-size: 0.58rem; color: var(--muted); }
.c1-stat-alarm { background: rgba(var(--amber-rgb),0.06); border-color: rgba(var(--amber-rgb),0.18); }
.c1-stat-alarm b { color: var(--accent); }
.c1-chart { margin-bottom: 14px; }
.c1-chart-h { font-size: 0.72rem; font-weight: 700; color: var(--ink); margin-bottom: 7px; display: flex; justify-content: space-between; align-items: baseline; }
.c1-chart-h small { font-size: 0.62rem; font-weight: 400; color: var(--muted); }
.c1-bar-row { display: grid; grid-template-columns: 52px 1fr 30px; align-items: center; gap: 8px; margin: 3px 0; }
.c1-bar-lab { font-size: 0.65rem; color: var(--muted); text-align: right; }
.c1-bar-track { display: block; height: 11px; background: rgba(var(--ink-rgb),0.05); border-radius: var(--r-control); overflow: hidden; }
.c1-bar-fill { display: block; height: 100%; min-width: 2px; background: var(--n-400); border-radius: var(--r-control); transition: width .6s ease; }
.c1-bar-fill.c1-emph { background: var(--accent); }
.c1-bar-val { font-size: 0.63rem; color: var(--ink); font-variant-numeric: tabular-nums; text-align: right; }
.c1-capacity { margin-top: 8px; font-size: 0.68rem; color: var(--c-blue-deep); background: rgba(var(--blue-deep-rgb),0.08); border-left: 3px solid var(--c-blue-deep); padding: 6px 10px; border-radius: 0 var(--r-control) var(--r-control) 0; }
.c1-capacity b { color: var(--c-blue-deep); }
.c1-punch { font-size: 0.75rem; font-weight: 600; color: var(--accent); line-height: 1.45; padding: 10px 12px; background: rgba(var(--amber-rgb),0.06); border-radius: var(--r-control); margin-bottom: 4px; }

/* historic/conservation toggle swatch */
.lt-swatch-historic { background: rgba(var(--amber-rgb),0.16); border: 1px dashed var(--c-amber-deep); }

/* ── 2D / 3D view toggle (top-left) ── */
.view-toggle {
  position: absolute; top: 16px; left: 24px; z-index: 11;
  display: flex; gap: 2px;
  background: rgba(var(--white-rgb),0.97); 
  border: 1px solid rgba(var(--ink-rgb),0.06); border-radius: var(--r-control); padding: 3px;
  box-shadow: var(--shadow-1);
}
.vt-btn {
  padding: 5px 12px; font-size: 0.74rem; font-weight: 600;
  border: none; background: none; color: var(--muted); cursor: pointer;
  border-radius: var(--r-control); font-family: 'Manrope', sans-serif; transition: background .15s, color .15s;
}
.vt-btn:hover { color: var(--ink); }
.vt-btn.active { background: var(--primary); color: var(--n-000); }

/* ── Setback envelope schematic (Envelope tab) ── */
.pc-setback { margin: 6px 0; padding: 8px 0 4px; }
.sb-title { font-size: 0.72rem; font-weight: 600; color: var(--ink); margin-bottom: 6px; }
.sb-title small { font-weight: 400; color: var(--muted); font-size: 0.62rem; }
.sb-svg { width: 100%; max-width: 250px; height: auto; display: block; }
.sb-lot { fill: rgba(var(--ink-rgb),0.04); stroke: var(--n-300); stroke-width: 1.2; }
.sb-build { fill: rgba(var(--blue-deep-rgb),0.14); stroke: var(--c-blue-deep); stroke-width: 1.3; stroke-dasharray: 3 2; }
.sb-lab { font-size: 8.5px; fill: var(--muted); text-anchor: middle; font-family: 'Manrope', sans-serif; font-weight: 600; }
.sb-build-lab { fill: var(--c-blue-deep); }

/* ── Rule-trace recommendation scorecard (the differentiator) ── */
.pc-ruletrace { margin: 0 22px 12px; padding: 0; }
.rt-head { display: flex; align-items: baseline; gap: 9px; margin-bottom: 8px; }
.rt-title { font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; color: var(--muted); }
.rt-conf { margin-left: auto; font-size: 0.64rem; color: var(--muted); }
.rt-summary { display: flex; gap: 16px; align-items: center; }
.rt-stat { display: inline-flex; align-items: center; gap: 6px; font-size: 0.76rem; font-weight: 600; color: var(--ink); }
.rt-stat svg { width: 14px; height: 14px; flex: 0 0 auto; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.rt-stat.rt-pass svg { stroke: var(--green); }
.rt-stat.rt-fail svg { stroke: var(--accent); }
.rt-stat.rt-na svg { stroke: var(--muted); }

.rt-details { margin-top: 9px; }
.rt-details summary { cursor: pointer; font-size: 0.72rem; color: var(--accent); font-weight: 600; list-style: none; outline: none; }
.rt-details summary::-webkit-details-marker { display: none; }
.rt-details summary::before { content: '+ '; font-weight: 700; }
.rt-details[open] summary::before { content: '\2212 '; }
.rt-list { margin-top: 8px; display: flex; flex-direction: column; gap: 5px;
  border-top: 1px solid var(--line, rgba(var(--ink-rgb),0.08)); padding-top: 8px; }
.rt-row { display: grid; grid-template-columns: 14px 1fr; gap: 3px 8px; font-size: 0.76rem; padding-bottom: 2px; }
.rt-ic { width: 13px; height: 13px; grid-row: 1 / span 2; align-self: start; margin-top: 2px; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.rt-pass .rt-ic { stroke: var(--green); } .rt-fail .rt-ic { stroke: var(--accent); } .rt-na .rt-ic { stroke: var(--muted); }
.rt-k { color: var(--ink); font-weight: 600; grid-column: 2; }
.rt-n { color: var(--muted); font-size: 0.68rem; grid-column: 2; line-height: 1.35; }
/* benefits / trade-offs — labeled lines, color only on the sign glyph */
.rt-io { display: flex; gap: 8px; align-items: baseline; margin-top: 10px;
  font-size: 0.76rem; color: var(--n-800); line-height: 1.5; }
.rt-io-sign { flex: 0 0 auto; font-weight: 800; font-size: 0.8rem; line-height: 1; margin-top: 1px; }
.rt-io-good .rt-io-sign { color: var(--green); }
.rt-io-bad .rt-io-sign { color: var(--accent); }
.rt-io-lab { font-size: 0.58rem; letter-spacing: 0.08em; text-transform: uppercase;
  font-weight: 700; color: var(--muted); margin-right: 2px; }

/* ── City Layers panel (registry-generated) ── */
/* Superseded by the .city-layers rule further down (the dock rebuild). Only the
   geometry props are kept dead here deliberately — the old `overflow-y: auto`
   was removed because a second overflow declaration on this element is what
   made the scroll-container bug hard to see. */
.city-layers { width: 218px; max-height: 46vh; padding: 10px 12px 8px; }
.cl-group { border-top: 1px solid rgba(var(--ink-rgb),0.07); }
.cl-group:first-of-type { border-top: 0; }
.cl-group summary {
  cursor: pointer; list-style: none; outline: none;
  font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase;
  font-weight: 700; color: var(--muted); padding: 7px 0 5px;
  display: flex; align-items: center; gap: 6px;
}
.cl-group summary::-webkit-details-marker { display: none; }
.cl-group summary::before { content: '▸'; font-size: 0.6rem; transition: transform .15s; }
.cl-group[open] summary::before { transform: rotate(90deg); }
.cl-item { display: flex; align-items: center; gap: 8px; padding: 4px 0 5px 4px; cursor: pointer; user-select: none; }
.cl-item input { accent-color: var(--c-blue-deep); margin: 0; width: 13px; height: 13px; cursor: pointer; flex: 0 0 auto; }
.cl-sw { width: 12px; height: 12px; border-radius: var(--r-control); flex: 0 0 auto; }
.cl-sw-none { background: rgba(var(--ink-rgb),0.08); }
.cl-tx { font-size: 0.74rem; color: var(--ink); line-height: 1.15; }
.cl-tx small { display: block; font-size: 0.6rem; color: var(--muted); margin-top: 1px; }
.cl-legend { display: flex; flex-wrap: wrap; gap: 3px 10px; padding: 2px 0 7px 25px; }
.cl-lg { font-size: 0.62rem; color: var(--muted); display: inline-flex; align-items: center; gap: 5px; }
.cl-lg i { width: 9px; height: 9px; border-radius: var(--r-control); border: 1px solid rgba(var(--ink-rgb),0.12); }

/* ── Address search (top-left, beside the view toggle) ── */
.map-search {
  position: absolute; top: 16px; left: 148px; z-index: 11;
  display: flex; align-items: center; gap: 7px;
  background: rgba(var(--white-rgb),0.97); 
  border: 1px solid rgba(var(--ink-rgb),0.06); border-radius: var(--r-control);
  padding: 5px 6px 5px 11px; width: 264px;
  box-shadow: var(--shadow-1);
  color: var(--muted);
}
.map-search input {
  flex: 1; border: none; background: none; outline: none; min-width: 0;
  font-family: 'Manrope', sans-serif; font-size: 0.78rem; color: var(--ink);
}
.map-search input::placeholder { color: var(--muted); }
.map-search button {
  border: none; background: var(--primary); color: var(--n-000); cursor: pointer;
  width: 24px; height: 24px; border-radius: var(--r-control); font-size: 0.8rem; line-height: 1;
  flex: 0 0 auto;
}
.map-search button:hover { filter: brightness(1.15); }
.map-search input:focus-visible { outline: 2px solid var(--c-blue-deep); outline-offset: 2px; border-radius: var(--r-control); }
@media (max-width: 1100px) { .map-search { width: 200px; } }

/* ── Map / Table mode toggle + accessible parcel table (R1 #5) ── */
.mode-toggle { left: auto; right: 24px; top: 62px; }
.table-view[hidden] { display: none; }   /* `display:flex` below would defeat [hidden] */
.table-view {
  position: absolute; inset: 16px 24px 16px 24px; z-index: 18;
  background: rgba(var(--white-rgb),0.99);
  border: 1px solid rgba(var(--ink-rgb),0.08); border-radius: var(--r-panel);
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: var(--shadow-2);
}
.tv-bar { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid rgba(var(--ink-rgb),0.08); }
.tv-bar input {
  flex: 0 1 320px; font-family: 'Manrope', sans-serif; font-size: 0.82rem;
  padding: 7px 12px; border: 1px solid rgba(var(--ink-rgb),0.14); border-radius: var(--r-control); outline: none; color: var(--ink);
}
.tv-bar input:focus-visible { outline: 2px solid var(--c-blue-deep); outline-offset: 1px; }
.tv-count { font-size: 0.72rem; color: var(--muted); }
.tv-scroll { flex: 1; overflow: auto; }
.tv-table { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
.tv-table thead th {
  position: sticky; top: 0; background: var(--n-050); z-index: 1;
  text-align: left; border-bottom: 1px solid rgba(var(--ink-rgb),0.12); padding: 0;
}
.tv-table thead button {
  width: 100%; text-align: left; padding: 9px 12px; border: none; background: none; cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.66rem; letter-spacing: 0.08em;
  text-transform: uppercase; font-weight: 700; color: var(--muted);
}
.tv-table thead button:hover { color: var(--ink); }
.tv-table thead button:focus-visible { outline: 2px solid var(--c-blue-deep); outline-offset: -2px; }
.tv-table tbody td { padding: 7px 12px; border-bottom: 1px solid rgba(var(--ink-rgb),0.05); color: var(--ink); }
.tv-table tbody tr { cursor: pointer; }
.tv-table tbody tr:hover { background: rgba(var(--blue-deep-rgb),0.06); }
.tv-table tbody tr:focus-visible { outline: 2px solid var(--c-blue-deep); outline-offset: -2px; background: rgba(var(--blue-deep-rgb),0.06); }
.tv-num { font-variant-numeric: tabular-nums; text-align: right; }
.tv-elig { color: var(--c-green-ok); font-weight: 700; text-align: center; }
.tv-foot { padding: 9px 16px; font-size: 0.68rem; color: var(--muted); border-top: 1px solid rgba(var(--ink-rgb),0.08); }

/* ── Share link on the parcel card (R1 #6 deep links) ── */
.pc-share {
  margin-top: 7px; padding: 3px 0; border: none; background: none; cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.68rem; font-weight: 600;
  color: var(--green); text-align: left;
}
.pc-share:hover { text-decoration: underline; }
.pc-share:focus-visible { outline: 2px solid var(--c-blue-deep); outline-offset: 2px; border-radius: var(--r-control); }

/* setback consequence note (cofounder r3: 10 ft gap + roof greenspace) */
.sb-note { margin-top: 7px; font-size: 0.72rem; color: var(--n-800); line-height: 1.5;
  padding: 8px 11px; background: rgba(var(--amber-rgb),0.07); border-left: 3px solid var(--c-amber);
  border-radius: 0 var(--r-control) var(--r-control) 0; }
.sb-note b { color: var(--c-amber-deep); }

/* r3 corrections: ordinance version, capacity note, model-generated alert tag */
.ord-ver { margin-left: 12px; font-size: 0.66rem; letter-spacing: 0.05em; color: var(--muted);
  border-left: 1px solid rgba(var(--ink-rgb),0.15); padding-left: 12px; }
.cap-note { margin: 6px 0 8px; font-size: 0.66rem; color: var(--muted); font-style: italic; }
.redev-mg { margin-left: auto; font-size: 0.56rem; letter-spacing: 0.06em; text-transform: uppercase;
  font-weight: 700; color: var(--muted); background: rgba(var(--ink-rgb),0.06); padding: 2px 7px; border-radius: var(--r-control); }
.redev-rule { margin-top: 7px; font-size: 0.66rem; color: var(--muted); font-style: italic; }

/* ── Cambridge 360 light rail (mockup shell) ── */
.sidebar.rail { background: var(--n-000); border-right: 1px solid var(--n-150); color: var(--n-900); }
.rail-brand { display: flex; gap: 10px; align-items: center; padding: 16px 16px 12px; border-bottom: 1px solid var(--n-100); }
/* THE REAL MARK, not an approximation of it (2026-08-16). This was a
   conic-gradient ring — amber -> teal -> blue behind a radial mask — that had
   been standing in for the actual City 360 logo: a three-node network glyph
   inside concentric orbital arcs. The gradient was the last piece of chrome
   still carrying the retired teal, so replacing it and recolouring it were the
   same job; it got replaced.
   The only logo files in the repo were WHITE-on-transparent
   (next/public/city360/logo/*-white.png), which is invisible on this light
   header — that is why they could never simply be dropped in. The ink file is
   a COLOURWAY of that same artwork, not a redraw: the original alpha channel
   is untouched and only RGB was repainted to Atlas ink #393f44. The square
   mark was cropped out of the portrait lockup at the natural gap between the
   glyph and the "CITY 360" wordmark (alpha rows 762-845), then centred.
   MARK ONLY, never the lockup: the header is a ~48px horizontal strip, the
   lockup is portrait 900x1178, and its wordmark would duplicate the
   "CITY 360: CAMBRIDGE" text sitting 10px to its right.
   Painted as a background-image rather than an <img> because it is decorative
   and because a background cannot reflow: the header has zero pixels of slack
   at 1440, so the box keeps its exact former size and the 1395px floor does
   not move. url() resolves against this stylesheet, so it also works for the
   prototypes/ pages that load cambridge.css from a subdirectory. */
.rail-orbit { width: 26px; height: 26px; flex: 0 0 auto;
  background: url('assets/logo/city360-mark-ink.png') center / contain no-repeat; }
.rail-wm { font-weight: 800; font-size: 0.72rem; letter-spacing: 0.05em; color: var(--n-900); }
.rail-bar { color: var(--c-amber); } .rail-wu { color: var(--n-600); font-weight: 600; }
.rail-prod { font-size: 0.6rem; color: var(--n-500); letter-spacing: 0.08em; text-transform: uppercase; margin-top: 2px; }
.rail-scroll { flex: 1; overflow-y: auto; padding: 10px 12px; }
.rail-h { font-size: 0.6rem; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 700;
  color: var(--n-500); margin: 12px 4px 4px; }
.rail-h small { text-transform: none; letter-spacing: 0; font-weight: 400; }
.rail-h:first-child { margin-top: 2px; }
.rail-item { display: block; width: 100%; text-align: left; padding: 7px 10px; border: none;
  background: none; border-radius: var(--r-control); font-family: 'Manrope', sans-serif; font-size: 0.78rem;
  color: var(--n-800); cursor: pointer; }
.rail-item:hover { background: var(--n-050); }
.rail-item.active { background: var(--c-chrome-wash); color: var(--c-chrome); font-weight: 700; }
.rail-src { padding: 4px 4px 0; }
.rail-src-row { display: flex; justify-content: space-between; align-items: baseline;
  padding: 4px 6px; font-size: 0.68rem; color: var(--n-800); border-bottom: 1px solid var(--n-100); }
.rail-src-row small { color: var(--n-500); font-size: 0.6rem; }
.rail-src-note { font-size: 0.6rem; color: var(--n-500); padding: 6px; }
.rail-foot { color: var(--n-500) !important; border-top: 1px solid var(--n-100) !important; font-size: 0.62rem !important; }

/* header tabs */
.tabs360 { display: inline-flex; gap: 4px; margin-right: 10px; }
.t360 { padding: 6px 4px 8px; font-size: 0.8rem; font-weight: 500; color: var(--n-600);
  position: relative; }
.t360.on { color: var(--n-900); font-weight: 700; }
.t360.on::after { content: ''; position: absolute; left: 4px; right: 4px; bottom: 0;
  height: 2px; border-radius: var(--r-control); background: var(--c-chrome); }
/* .off is NO LONGER USED BY cambridge.html. 2026-08-18 took both its wearers
   off it: About became a real view, and Projects took ordinary tab ink plus a
   lock glyph and the word "Locked". --n-300 measures 1.92:1 on the white
   header (1.93:1 on the dark one), so anything wearing it read as broken
   whether it was or not — and a `cursor: help` tooltip on a span with no
   tabindex is unreachable from the keyboard by construction.
   Kept only because prototypes/reports.html and neighborhood-explorer.html link
   this stylesheet and still mark their Projects item this way — those pages have
   no locked dialog to open, so there is nothing there to enable yet. */
.t360.off { color: var(--n-300); cursor: help; }
/* real, clickable views (Zoning / Analytics / About) vs the .off ones */
.t360[data-view] { cursor: pointer; }
.t360[data-view]:hover { color: var(--n-900); }
.t360[data-view]:focus-visible { outline: 2px solid var(--c-blue-deep); outline-offset: 2px; border-radius: var(--r-control); }
.cp-tab[hidden] { display: none; }

/* ═══════════════════════════════════════════════
   CAMBRIDGE 360 v2 — mockup-parity shell
   header brand + search · icon rail · right cpanel
   ═══════════════════════════════════════════════ */

/* ── Fixed full-width header ── */
.hdr360 { position: fixed; top: 0; left: 0; right: 0; height: 58px; z-index: 220;
  background: var(--n-000); border-bottom: 1px solid var(--n-150); display: flex; align-items: center;
  gap: 14px; padding: 0 18px; }
.hdr-brand { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
/* 30px is the SHIPPED size of the brand box — the base rule's 26px never
   applies on this page. Kept to the pixel so the header does not reflow. The
   radial mask that used to punch the gradient ring's hole is gone: the real
   mark has a solid hub circle at its centre, and masking would delete it. */
.hdr360 .rail-orbit { width: 30px; height: 30px; }
.hdr-sep { width: 1px; height: 30px; background: var(--n-150); margin: 0 4px; }
.hdr-prod-name { font-weight: 800; font-size: 0.86rem; color: var(--n-900); letter-spacing: 0.01em; }
.hdr-prodwrap .rail-prod { margin-top: 1px; }
@media (max-width: 1280px) { .hdr-sep, .hdr-prodwrap { display: none; } }

/* search moves into the header */
.hdr360 .map-search { position: static; flex: 0 1 320px; width: auto; margin: 0 auto;
  box-shadow: none; background: var(--n-050); border-color: var(--n-150); }
.hdr360 .toolbar-right { flex: 0 0 auto; gap: 8px; }
/* r4-D7: Donate is a secondary outline — amber is reserved for alerts */
.btn-donate { background: none; border: 1px solid var(--n-150); color: var(--n-900); }
.btn-donate:hover { background: var(--n-050); opacity: 1; }

/* ── Rail sits under the header ── */
.sidebar.rail { top: 58px; }
.main { padding-top: 58px; }

.rail-analyze { margin: 12px 12px 2px; padding: 10px 12px; background: var(--n-050);
  border: 1px solid var(--n-150); border-radius: var(--r-panel); }
.ra-label { font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase;
  font-weight: 800; color: var(--n-900); }
.rail-analyze .ra-sub { display: block; margin-top: 3px; font-size: 0.62rem; color: var(--n-600); }

/* rail items get icons */
.rail-item { display: flex; align-items: center; gap: 9px; }
.rail-item svg { width: 15px; height: 15px; flex: 0 0 auto; fill: var(--n-500); }
.rail-item.active svg { fill: var(--c-chrome); }
.rail-item span { flex: 1; }
.rail-item.soon { color: var(--n-400); cursor: default; }
.rail-item.soon svg { fill: var(--n-250); }
.rail-item.soon em { font-style: normal; font-size: 0.56rem; letter-spacing: 0.08em;
  text-transform: uppercase; font-weight: 700; color: var(--n-400); background: var(--n-050);
  padding: 2px 6px; border-radius: var(--r-control); }
.rail-item.soon:hover { background: none; }

/* ── Content row: map + right contextual panel ── */
.content-row { display: flex; flex: 1; min-height: 0; }
.content-row .map-canvas { margin: 14px 14px 18px 20px; }

.cpanel { width: 336px; flex: 0 0 auto; margin: 14px 20px 18px 0;
  background: var(--n-000); border: 1px solid var(--n-150); border-radius: var(--r-panel);
  display: flex; flex-direction: column; overflow: hidden; box-shadow: var(--shadow-md); }
.cp-tabs { display: flex; align-items: center; gap: 2px; padding: 8px 10px;
  border-bottom: 1px solid var(--n-100); flex: 0 0 auto; }
.cp-tab { border: none; background: none; padding: 6px 12px; border-radius: var(--r-control);
  font-family: 'Manrope', sans-serif; font-size: 0.76rem; font-weight: 700;
  color: var(--n-600); cursor: pointer; }
.cp-tab:hover { background: var(--n-050); }
.cp-tab.active { background: rgba(var(--chrome-rgb),0.1); color: var(--c-chrome); }
.cp-collapse { margin-left: auto; border: none; background: none; color: var(--n-500);
  font-size: 0.9rem; cursor: pointer; padding: 4px 8px; border-radius: var(--r-control); }
.cp-collapse:hover { background: var(--n-050); color: var(--n-900); }
.cp-body { flex: 1; min-height: 0; overflow-y: auto; }
.cp-pane { display: none; padding: 14px 16px 18px; }
.cp-pane.active { display: block; }
body.cp-hidden .cpanel { display: none; }

.cp-open { position: absolute; top: 18px; right: 18px; z-index: 12;
  border: 1px solid var(--n-150); background: rgba(var(--white-rgb),0.94); color: var(--n-900);
  font-family: 'Manrope', sans-serif; font-size: 0.72rem; font-weight: 700;
  padding: 7px 12px; border-radius: var(--r-pill); cursor: pointer;
  box-shadow: var(--shadow-1); }
.cp-open:hover { background: var(--n-000); }

/* section heads inside panes */
.cp-h { font-size: 0.64rem; letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: 800; color: var(--n-600); margin: 2px 2px 10px; }
.cp-h-gap { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--n-100); }

/* map alert (model-generated, with rule) */
.cp-alert { background: rgba(var(--amber-rgb),0.08); border: 1px solid rgba(var(--amber-rgb),0.3);
  border-radius: var(--r-panel); padding: 11px 12px; margin-bottom: 12px; }
.cp-alert-head { display: flex; align-items: center; gap: 8px; font-size: 0.74rem;
  font-weight: 800; color: var(--c-amber-deep); }
.cp-alert-body { font-size: 0.74rem; line-height: 1.5; color: var(--n-800); margin-top: 6px; }
.cp-alert-rule { font-size: 0.62rem; font-style: italic; color: var(--c-amber-deep); margin-top: 6px; }
.cp-alert-go { margin-top: 8px; border: none; background: var(--c-amber); color: var(--n-000);
  font-family: 'Manrope', sans-serif; font-size: 0.7rem; font-weight: 700;
  padding: 6px 11px; border-radius: var(--r-control); cursor: pointer; }
.cp-alert-go:hover { opacity: 0.88; }

/* news rows with initials chips */
.cp-news { display: flex; gap: 10px; align-items: flex-start; padding: 9px 6px;
  border-bottom: 1px solid var(--n-100); text-decoration: none; border-radius: var(--r-control); }
.cp-news:hover { background: var(--n-025); }
.cp-chip { width: 30px; height: 30px; border-radius: var(--r-control); flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.62rem; font-weight: 800; color: var(--n-000); letter-spacing: 0.02em; }
.cp-chip-cc { background: var(--c-blue); } .cp-chip-pd { background: var(--c-chrome); }
.cp-chip-cd { background: var(--c-risk); } .cp-chip-rt { background: var(--c-amber); }
.cp-news-text { min-width: 0; }
.cp-news-text strong { display: block; font-size: 0.74rem; line-height: 1.35;
  color: var(--n-900); font-weight: 700; }
.cp-news-text small { display: block; font-size: 0.62rem; color: var(--n-500); margin-top: 2px; }

.cp-weekly { display: block; width: 100%; margin-top: 10px; padding: 9px 12px;
  border: 1px dashed var(--n-250); background: var(--n-025); border-radius: var(--r-control);
  font-family: 'Manrope', sans-serif; font-size: 0.7rem; font-weight: 600;
  color: var(--n-800); cursor: pointer; text-align: left; }
.cp-weekly:hover { border-color: var(--c-chrome); color: var(--c-chrome); }

/* honest metric cards — value · label · scope note, no sparklines */
.cp-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.cpm { border: 1px solid var(--n-150); border-radius: var(--r-panel); padding: 10px 11px;
  display: flex; flex-direction: column; gap: 2px; }
.cpm-v { font-size: 1.22rem; font-weight: 800; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; }
.cpm-teal { color: var(--c-teal-deep); } .cpm-blue { color: var(--c-blue); }
.cpm-amber { color: var(--c-amber-deep); } .cpm-risk { color: var(--c-risk); font-size: 1.02rem; }
.cpm-l { font-size: 0.68rem; font-weight: 700; color: var(--n-900); }
.cpm-s { font-size: 0.6rem; color: var(--n-500); line-height: 1.35; }
.cp-mnote { font-size: 0.62rem; color: var(--n-500); font-style: italic; margin-top: 8px; }

/* parcel pane: hint becomes the empty state, card fills the pane */
.cpanel .map-hint { position: static; width: auto; box-shadow: none;
  backdrop-filter: none; background: var(--n-025); border: 1px dashed var(--n-250); }
.cpanel .parcel-card { position: static; width: auto; max-height: none;
  box-shadow: none; border: none; border-radius: 0; backdrop-filter: none;
  background: none; animation: none; overflow: visible; }
.cpanel .pc-content { max-height: none; overflow-y: visible; }
.cpanel .pc-header { padding-left: 2px; padding-top: 4px; }
.cpanel .pc-close { top: 0; right: 0; }

/* finding pane */
.cp-find { font-size: 0.8rem; line-height: 1.6; color: var(--n-800); }
.cp-find p { margin: 0 0 10px; }
.cp-find ul { padding-left: 18px; margin: 10px 0; }
.cp-find-close { font-weight: 700; color: var(--c-teal-deep); }
.cp-find-foot { font-size: 0.62rem; color: var(--n-500); border-top: 1px solid var(--n-100);
  padding-top: 10px; margin-top: 12px; }

@media (max-width: 1150px) { .cpanel { width: 300px; } }

/* ═══ Layer dock v2 — scroll list · eye · lock · chips · minimize (r2) ═══ */
/* overflow:clip, NOT hidden. `hidden` makes this box a scroll CONTAINER: it
   shows no scrollbar, but the browser still scrolls it to reveal a focused
   descendant. Clicking a layer row low in the list therefore scrolled the whole
   panel — the header slid out of view and an empty region opened below the
   footer, with no scrollbar to get back. That is the "panel goes blank when I
   turn a layer on" report (measured: clicking the last row set scrollTop to 931
   and opened a 932px void). `clip` is not a scroll container, so focus-reveal
   cannot move it; .cl-list below still scrolls normally and every row stays
   reachable. */
.city-layers { width: 252px; max-height: min(64vh, calc(100vh - 230px));
  display: flex; flex-direction: column; padding: 0; overflow: clip; }
.cl-head { display: flex; align-items: center; justify-content: space-between;
  padding: 9px 12px 7px; border-bottom: 1px solid var(--n-100); flex: 0 0 auto; }
.cl-head .legend-title { margin: 0; }
.cl-minbtn { border: none; background: var(--n-050); color: var(--n-600); width: 22px; height: 22px;
  border-radius: var(--r-control); cursor: pointer; font-size: 0.95rem; line-height: 1; }
.cl-minbtn:hover { background: var(--n-150); color: var(--n-900); }
.city-layers.cl-minimized { max-height: none; }
.city-layers.cl-minimized .cl-body { display: none; }
.cl-body { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.cl-chips { display: flex; flex-wrap: wrap; gap: 4px; padding: 8px 10px 2px; flex: 0 0 auto; }
.cl-chip { border: 1px solid var(--n-150); background: var(--n-000); color: var(--n-600); font-size: 0.6rem;
  font-weight: 700; padding: 3px 8px; border-radius: var(--r-pill); cursor: pointer;
  font-family: 'Manrope', sans-serif; }
.cl-chip.active { background: rgba(var(--chrome-rgb),0.1); border-color: var(--c-chrome); color: var(--c-chrome); }
.cl-tools { display: flex; gap: 5px; padding: 7px 10px; flex: 0 0 auto; }
.cl-bulk { border: 1px solid var(--n-150); background: var(--n-000); color: var(--n-800); font-size: 0.62rem;
  font-weight: 700; padding: 4px 8px; border-radius: var(--r-control); cursor: pointer;
  font-family: 'Manrope', sans-serif; flex: 0 0 auto; }
.cl-bulk:hover { background: var(--n-050); }
.cl-search { flex: 1; min-width: 0; border: 1px solid var(--n-150); border-radius: var(--r-control);
  padding: 4px 8px; font-family: 'Manrope', sans-serif; font-size: 0.66rem; color: var(--ink);
  background: var(--n-025); outline: none; }
.cl-search:focus { border-color: var(--c-chrome); }
/* ── SUN SHADOWS · the GPU-shade kill switch ────────────────────────────────
   Above .cl-list rather than inside it, because it is not a layer: it is a
   property of the light, and applyPreset() clears every row inside the list on
   topic entry. A wash + top/bottom hairline separates it from the rows without
   giving it a card of its own — it is a setting, not a fifth chip group.
   No raw hex below: every value is a :root token with a fallback, per the
   file's discipline. */
.cl-sun { flex: 0 0 auto; padding: 7px 12px 8px; background: var(--n-025, #f7f9fb);
  border-top: 1px solid var(--n-100, #eef2f6); border-bottom: 1px solid var(--n-100, #eef2f6); }
.cl-sun-t { display: flex; align-items: flex-start; gap: 7px; cursor: pointer;
  font-size: 0.7rem; font-weight: 700; color: var(--n-800, #3d4854); }
.cl-sun-t input { margin: 2px 0 0; flex: 0 0 auto; accent-color: var(--c-chrome); }
.cl-sun-t small { display: block; font-weight: 500; font-size: 0.58rem;
  color: var(--n-500, #8593a2); line-height: 1.35; }
/* The live state line. Reads as running commentary, not as a warning — the
   three reasons it prints (off, sun down, below zoom 16) are all normal. */
.cl-sun-note { margin: 5px 0 0 23px; font-size: 0.57rem; line-height: 1.4;
  color: var(--n-600, #586b7d); }
/* The standing caveat sits under the live line at lower contrast, the same
   relationship the .cl-legend rows have to their .cl-row above them. `overstates`
   is bolded in the markup and must stay legible at this size. */
.cl-sun-caveat { color: var(--n-550, #7a8794); }
.cl-sun-caveat b { color: var(--n-800, #3d4854); font-weight: 700; }

.cl-list { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 8px 4px; }
.cl-row { display: flex; align-items: center; gap: 7px; padding: 5px 2px; border-radius: var(--r-control); }
.cl-row:hover { background: var(--n-025); }
.cl-row.locked { background: rgba(var(--amber-rgb),0.06); }
.cl-eyew { display: flex; cursor: pointer; flex: 0 0 auto; }
.cl-eyew input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.cl-eye { width: 16px; height: 16px; }
.cl-eye .eo, .cl-eye .ec { fill: var(--n-500); }
.cl-eyew input:checked + .cl-eye .eo { fill: var(--c-blue-deep); }
.cl-eyew input:checked + .cl-eye .ec { display: none; }
.cl-eyew input:not(:checked) + .cl-eye .eo { display: none; }
.cl-eyew input:focus-visible + .cl-eye { outline: 2px solid var(--c-blue-deep); border-radius: var(--r-control); }
.cl-row .cl-tx { flex: 1; min-width: 0; }
.cl-lock { border: none; background: none; padding: 2px; cursor: pointer; flex: 0 0 auto;
  border-radius: var(--r-control); }
.cl-lock svg { width: 13px; height: 13px; display: block; }
.cl-lock .lk-open { fill: var(--n-250); } .cl-lock .lk-shut { display: none; fill: var(--c-amber); }
.cl-lock[aria-pressed="true"] .lk-open { display: none; }
.cl-lock[aria-pressed="true"] .lk-shut { display: block; }
.cl-lock:hover { background: var(--n-100); }
.cl-foot { padding: 5px 12px 8px; font-size: 0.56rem; color: var(--n-500); border-top: 1px solid var(--n-100);
  flex: 0 0 auto; }
.cl-legend { padding-left: 31px; }

/* ═══ Project popup — status · units · before/proposed compare ═══ */
/* deck.gl's overlay canvas is inserted into the map container and was painting
   over popups (they render but appear "buried" under the 3D model). Lift the
   whole popup above the canvas + the floating panels. */
.mapboxgl-popup { z-index: 60; }
.mapboxgl-popup-content { font-family: 'Manrope', sans-serif; border-radius: var(--r-panel);
  box-shadow: var(--shadow-2); padding: 14px 14px 12px; }
.pj-pop { width: 264px; }
.pj-addr { font-weight: 800; font-size: 0.88rem; color: var(--n-900); }
.pj-sub { font-size: 0.66rem; color: var(--n-500); margin: 2px 0 7px; }
.pj-status { display: inline-block; color: var(--n-000); font-size: 0.6rem; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase; padding: 3px 8px; border-radius: var(--r-control);
  margin-bottom: 8px; }
.pj-cmp, .pj-one { position: relative; width: 100%; height: 150px; border-radius: var(--r-control);
  overflow: hidden; background: var(--n-100); margin-bottom: 8px; }
.pj-cmp img, .pj-one img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; }
.pj-top { position: absolute; inset: 0; }
.pj-tag { position: absolute; top: 6px; background: rgba(var(--ink-rgb),0.72); color: var(--n-000);
  font-size: 0.54rem; font-weight: 700; letter-spacing: 0.08em; padding: 2px 6px;
  border-radius: var(--r-control); pointer-events: none; }
.pj-cmp input[type=range] { position: absolute; left: 0; right: 0; bottom: 4px; width: 100%;
  margin: 0; accent-color: var(--n-000); cursor: ew-resize; height: 18px; }
.pj-kv { font-size: 0.74rem; color: var(--n-900); }
.pj-dates { font-size: 0.64rem; color: var(--n-600); margin-top: 3px; }
/* Ordinance era — which zoning regime this site actually belongs to. Stated
   before the photos so a pre-2025 filing can never read as an Ord 2025-1 outcome. */
.pj-era { margin: 7px 0; padding: 6px 8px; border-radius: var(--r-control); font-size: 0.64rem;
  line-height: 1.4; color: var(--n-800); background: var(--n-100); border-left: 3px solid var(--n-250); }
.pj-era b { display: block; font-size: 0.63rem; letter-spacing: .04em;
  text-transform: uppercase; margin-bottom: 2px; }
.pj-era-pre { background: var(--n-100); border-left-color: var(--n-800); }
.pj-era-pre b { color: var(--n-800); }
.pj-era-straddle { background: var(--c-amber-wash); border-left-color: var(--c-amber); }
.pj-era-straddle b { color: var(--c-amber-deep); }
.pj-era-post { background: var(--c-blue-wash); border-left-color: var(--c-blue); }
.pj-era-post b { color: var(--c-blue-deep); }
.pj-era-unknown b { color: var(--n-600); }
.pj-era-ev { display: block; margin-top: 4px; font-size: 0.58rem; color: var(--n-550); }
.pj-parcel { display: block; width: 100%; margin-top: 9px; border: none; background: var(--c-chrome);
  color: var(--n-000); font-family: 'Manrope', sans-serif; font-size: 0.7rem; font-weight: 700;
  padding: 7px 10px; border-radius: var(--r-control); cursor: pointer; }
.pj-parcel:hover { opacity: 0.9; }
.pj-src { font-size: 0.58rem; color: var(--n-500); margin-top: 7px; }
.pj-src a { color: var(--c-blue); }


/* ═══ r4: first-visit orientation bar (D5) + parcel guide state ═══ */
.intro-bar { display: flex; align-items: center; gap: 10px; padding: 7px 20px;
  background: rgba(var(--chrome-rgb),0.08); border-bottom: 1px solid var(--n-150);
  font-size: 0.78rem; color: var(--n-900); flex: 0 0 auto; }
.intro-bar strong { color: var(--c-chrome); }
.intro-bar button { margin-left: auto; border: none; background: none; color: var(--n-500);
  font-size: 1.05rem; cursor: pointer; padding: 2px 8px; border-radius: var(--r-control); }
.intro-bar button:hover { background: var(--n-100); color: var(--n-900); }
.intro-bar[hidden] { display: none; }
.hint-steps { margin: 8px 0 10px; display: flex; flex-direction: column; gap: 7px; }
.hs { display: flex; gap: 9px; font-size: 0.75rem; color: var(--n-800); line-height: 1.45; }
.hs b { flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%; background: var(--c-chrome);
  color: var(--n-000); font-size: 0.62rem; display: flex; align-items: center; justify-content: center; }


/* ═══ Permit activity + modeled outlook (Updates) ═══ */
.act-bars { display: flex; align-items: flex-end; gap: 2px; height: 64px; }
.act-bar { flex: 1; background: var(--c-teal); border-radius: var(--r-control) var(--r-control) 0 0; min-width: 2px; }
.act-bar.fc { background: rgba(var(--teal-rgb),0.12); border: 1px dashed var(--c-teal); border-bottom: none; }
.act-x { display: flex; justify-content: space-between; font-size: 0.56rem; color: var(--n-500); margin-top: 3px; }


/* ═══ QA round: theme legend · table exit · toggle above table ═══ */
.theme-legend { position: absolute; bottom: 16px; right: 16px; left: auto; top: auto; z-index: 14;
  background: rgba(var(--white-rgb),0.96); border: 1px solid var(--n-150); border-radius: var(--r-panel);
  padding: 8px 11px; max-width: 250px; max-height: 42vh; overflow-y: auto;
  box-shadow: var(--shadow-1);
  display: flex; flex-direction: column; gap: 4px; }

/* Rows flow top-to-bottom then wrap into a new COLUMN, capped at 3 rows tall.
   A 7-entry legend was 7 rows deep and ate the bottom of the map; it is now
   3 rows by 3 columns. Height is bounded by the cap, not by the longest layer. */
.tl-rows { display: grid; grid-auto-flow: column; grid-template-rows: repeat(3, auto);
  gap: 1px var(--s-4); justify-content: start; }
/* One or two entries should not open a second column. */
.tl-group:has(.tl-rows > :nth-child(-n+2):last-child) .tl-rows { grid-auto-flow: row; }
.theme-legend[hidden] { display: none; }

/* ── TYPE, ONE STEP UP (Rocker 2026-08-03: "the legend is too small like that
   in proportion… legibility is the key thing here"). One step, ~9%, applied to
   the whole tl- family at once so the legend keeps its internal proportions —
   bumping only the rows would have made the group headers read as captions of
   the swatches instead of headings over them. The swatch grows with the text
   for the same reason: a symbol that no longer matches its label's weight is
   the thing she was actually pointing at. Sizes stay on the file's ladder
   (0.55 / 0.58 / 0.6 / 0.63 / 0.66 / 0.68 / 0.72). The bottom strip's height
   caps are raised by the same ~9% — see the L3 legend-strip rules near the end
   of this file, or the strip starts clipping rows it used to show. ── */
.tl-title { font-size: 0.66rem; letter-spacing: 0.1em;
  font-weight: 800; color: var(--n-600); margin-bottom: 2px; }
.tl-row { display: inline-flex; align-items: center; gap: 6px; font-size: 0.72rem; color: var(--n-800); }
.tl-row i { width: 13px; height: 13px; border-radius: var(--r-control); border: 1px solid rgba(var(--ink-rgb),0.12); flex: 0 0 auto; }

/* ── LEGEND GROUPS (B4). One group per active layer, so every symbol on the
   map is decoded and it is obvious which layer owns which swatch. ── */
.tl-title .tl-count { font-weight: 700; color: var(--n-400); margin-left: var(--s-2);
  letter-spacing: 0.04em; font-variant-numeric: tabular-nums lining-nums; }
.tl-groups { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); align-items: flex-start; }
.tl-group { display: flex; flex-direction: column; gap: 2px; min-width: 0;
  padding-left: var(--s-3); border-left: 1px solid var(--n-150); }
.tl-group:first-child { padding-left: 0; border-left: none; }
.tl-gname { font-size: 0.63rem; letter-spacing: 0.06em;
  font-weight: 800; color: var(--n-900); margin-bottom: 1px; }
.tl-gsub { font-size: 0.6rem; color: var(--n-400); font-style: italic; max-width: 240px;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.tl-group .tl-chip { align-self: flex-start; margin-top: 2px; }
.mode-toggle { z-index: 45; }
.tv-close { margin-left: auto; border: 1px solid var(--n-150); background: var(--n-000); color: var(--n-900);
  font-family: 'Manrope', sans-serif; font-size: 0.72rem; font-weight: 700;
  padding: 6px 12px; border-radius: var(--r-control); cursor: pointer; }
.tv-close:hover { background: var(--n-050); }


/* ═══ D1 parcel popup + D2 info tab ═══ */
.parcel-pop .mapboxgl-popup-content { padding: 12px 13px 11px; border-radius: var(--r-panel); }
.pp-addr { font-weight: 800; font-size: 0.86rem; color: var(--n-900); }
.pp-sub { font-size: 0.66rem; color: var(--n-500); margin: 2px 0 8px; }
.pp-tax { display: flex; justify-content: space-between; align-items: baseline; font-size: 0.72rem; color: var(--n-600); }
.pp-tax strong { color: var(--c-teal-deep); font-size: 0.82rem; font-variant-numeric: tabular-nums; }
.pp-verdict { margin-top: 7px; font-size: 0.64rem; font-weight: 700; padding: 4px 8px; border-radius: var(--r-control); line-height: 1.3; }
.pp-ok { background: rgba(var(--teal-rgb),0.1); color: var(--c-teal-deep); }
.pp-warn { background: rgba(var(--risk-rgb),0.1); color: var(--c-risk-deep); }
.pp-full { display: block; width: 100%; margin-top: 9px; border: none; background: var(--c-chrome);
  color: var(--n-000); font-family: 'Manrope', sans-serif; font-size: 0.72rem; font-weight: 700;
  padding: 7px 10px; border-radius: var(--r-control); cursor: pointer; }
.pp-full:hover { opacity: 0.9; }
.inf-permits { margin-top: 10px; }
.inf-load, .inf-empty { font-size: 0.72rem; color: var(--n-500); font-style: italic; padding: 4px 0; }
.inf-plabel { font-size: 0.6rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 800;
  color: var(--n-600); margin-bottom: 6px; display: flex; justify-content: space-between; }
.inf-plabel span { letter-spacing: 0; text-transform: none; font-weight: 400; color: var(--n-500); }
.inf-permit { border-left: 2px solid var(--n-150); padding: 4px 0 5px 9px; margin-bottom: 5px; }
.inf-prow { display: flex; justify-content: space-between; font-size: 0.74rem; color: var(--n-900); }
.inf-prow strong { font-weight: 700; }
.inf-prow span { color: var(--n-500); font-size: 0.66rem; }
.inf-pmeta { font-size: 0.64rem; color: var(--n-600); margin-top: 1px; }

.pc-share + .pc-share { margin-left: 10px; }
a.pc-share { text-decoration: none; display: inline-block; }

/* ═══════════════════════════════════════════════════════════════════════════
   TESTED FINDINGS (findings.js) — hero F3 + supporting ledger, provenance
   chips, methodology, Projects locked state. Palette matches the civic idioms
   already in this file (teal var(--c-teal) / dark-teal var(--c-teal-deep) · ink var(--n-900) · muted
   var(--n-500)/var(--n-600) · border var(--n-150) · hairline var(--n-100)).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── provenance chips — the four-tier legend; `tested` is our new tier ── */
.prov-chip { display: inline-block; font-size: 0.52rem; font-weight: 800; letter-spacing: 0.09em;
  text-transform: uppercase; padding: 2px 6px; border-radius: var(--r-control); line-height: 1.4;
  vertical-align: middle; white-space: nowrap; border: 1px solid transparent; }
.prov-observed { color: var(--n-600); background: rgba(var(--ink-rgb),0.06); }
.prov-tested   { color: var(--c-teal-deep); background: rgba(var(--teal-rgb),0.09); border-color: rgba(var(--teal-rgb),0.55); }
.prov-modeled  { color: var(--c-amber-deep); background: rgba(var(--amber-rgb),0.12); }
.prov-illus    { color: var(--n-600); background: var(--n-050); border: 1px dashed var(--n-250); }

/* ── findings root / loading ── */
.findings-root { }
.fnd-loading { font-size: 0.72rem; color: var(--n-500); font-style: italic; padding: 8px 2px; }

/* ── hero (F3) ── */
.fnd-hero { }
.fnd-kicker { display: flex; align-items: center; gap: 8px; margin: 2px 0 9px; }
.fnd-kicker-txt { font-size: 0.58rem; font-weight: 800; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--n-500); }
.fnd-hero-head { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400;
  font-size: 1.5rem; line-height: 1.12; letter-spacing: 0.005em; color: var(--n-900); margin: 0 0 6px; }
.fnd-hero-sub { font-size: 0.72rem; color: var(--n-600); line-height: 1.45; margin-bottom: 12px; }

/* ── hero chart — axes + zero baseline, single teal hue, no decorative sparkline ── */
.fnd-chart { margin: 2px 0 10px; }
.fnd-chart-head { font-size: 0.56rem; letter-spacing: 0.08em;
  font-weight: 700; color: var(--n-500); margin-bottom: 14px; }
.fnd-plot { position: relative; height: 132px; padding-top: 18px; box-sizing: border-box;
  border-bottom: 1.5px solid var(--n-200); }        /* solid line = the zero baseline */
.fnd-bars { display: flex; gap: 3px; height: 100%; align-items: stretch; }
.fnd-col { flex: 1 1 0; min-width: 0; height: 100%; display: flex; flex-direction: column;
  justify-content: flex-end; align-items: center; }
.fnd-bar { width: 100%; max-width: 20px; min-height: 2px; background: var(--c-teal);
  border-radius: var(--r-control) var(--r-control) 0 0; }
.fnd-bar.partial { background: rgba(var(--teal-rgb),0.14); border: 1px dashed var(--c-teal); border-bottom: none; }
.fnd-bar-val { font-size: 0.58rem; font-weight: 800; color: var(--c-teal-deep); line-height: 1;
  margin-bottom: 3px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.fnd-baseaxis { display: flex; align-items: center; justify-content: space-between; margin-top: 3px; }
.fnd-zero { font-size: 0.56rem; font-weight: 700; color: var(--n-500); font-variant-numeric: tabular-nums; }
.fnd-partkey { display: inline-flex; align-items: center; gap: 5px; font-size: 0.54rem; color: var(--n-500); }
.fnd-partkey-sw { width: 11px; height: 8px; display: inline-block; box-sizing: border-box;
  border: 1px dashed var(--c-teal); border-bottom: none; background: rgba(var(--teal-rgb),0.14); }
.fnd-xaxis { display: flex; gap: 3px; margin-top: 2px; }
.fnd-xt { flex: 1 1 0; min-width: 0; text-align: center; font-size: 0.54rem; color: var(--n-400);
  font-variant-numeric: tabular-nums; }
.fnd-xt-strong { color: var(--n-600); font-weight: 700; }

.fnd-caveat { font-size: 0.62rem; line-height: 1.45; color: var(--n-600); background: var(--n-050);
  border-left: 2px solid var(--n-250); padding: 6px 9px; border-radius: 0 var(--r-control) var(--r-control) 0; margin: 2px 0 9px; }
.fnd-caveat-soft { color: var(--n-500); }
.fnd-method { font-size: 0.58rem; color: var(--n-500); font-style: italic; line-height: 1.4;
  font-variant-numeric: tabular-nums; }

/* ── supporting ledger row (NOT a 4-card grid) ── */
.fnd-ledger-h { font-size: 0.6rem; letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: 800; color: var(--n-600); margin: 16px 2px 6px; padding-top: 14px;
  border-top: 1px solid var(--n-100); }
.fnd-ledger { display: flex; flex-direction: column; }
.fnd-row { padding: 10px 0; border-bottom: 1px solid var(--n-100); }
.fnd-row:last-child { border-bottom: none; padding-bottom: 2px; }
.fnd-row-name { font-size: 0.74rem; font-weight: 700; color: var(--n-900); line-height: 1.3;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; margin-bottom: 4px; }
.fnd-row-stat { font-size: 0.72rem; color: var(--n-800); line-height: 1.45; font-variant-numeric: tabular-nums; }
.fnd-row-stat b { color: var(--c-teal-deep); font-weight: 800; }
.fnd-row-detail { color: var(--n-500); font-size: 0.66rem; }
.fnd-row-caveat { font-size: 0.6rem; color: var(--n-600); line-height: 1.4; margin-top: 5px;
  padding-left: 8px; border-left: 2px solid var(--n-150); }
.fnd-row-method { font-size: 0.58rem; color: var(--n-500); font-style: italic; margin-top: 5px;
  font-variant-numeric: tabular-nums; }

/* ── honest data-unavailable state ── */
.fnd-unavail { border: 1px dashed var(--n-250); background: var(--n-025); border-radius: var(--r-panel);
  padding: 12px; margin: 6px 0; }
.fnd-unavail-t { font-size: 0.72rem; font-weight: 700; color: var(--n-600); }
.fnd-unavail-w { font-size: 0.64rem; color: var(--n-500); line-height: 1.45; margin-top: 3px; }

/* ── methodology (About) — collapsed by default, low-key ── */
.method-about { margin-top: 16px; border-top: 1px solid var(--n-100); padding-top: 10px; }
.method-about > summary { font-size: 0.62rem; letter-spacing: 0.1em;
  font-weight: 800; color: var(--n-600); cursor: pointer; list-style: none; padding: 4px 2px;
  display: flex; align-items: center; gap: 6px; }
.method-about > summary::-webkit-details-marker { display: none; }
.method-about > summary::before { content: '\25B8'; font-size: 0.7rem; color: var(--n-500); }
.method-about[open] > summary::before { content: '\25BE'; }
.method-about > summary:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 2px; border-radius: var(--r-control); }
.method-body { padding: 4px 2px 2px; }
.method-h { font-size: 0.56rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 800;
  color: var(--n-500); margin: 12px 0 6px; }
.method-legend, .method-sources { list-style: none; padding: 0; margin: 0; }
.method-legend li { display: flex; gap: 8px; align-items: baseline; font-size: 0.66rem;
  color: var(--n-800); line-height: 1.45; margin-bottom: 7px; }
.method-legend li .prov-chip { flex: 0 0 auto; }
.method-sources li { display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  font-size: 0.66rem; color: var(--n-800); padding: 4px 0; border-bottom: 1px solid var(--n-050); }
.method-sources li small { color: var(--n-500); font-size: 0.6rem; white-space: nowrap; }
.method-foot { font-size: 0.62rem; color: var(--n-600); line-height: 1.5; margin-top: 10px; }
.method-foot code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 0.92em;
  background: var(--n-050); padding: 1px 5px; border-radius: var(--r-control); color: var(--n-800); }

/* ── Projects header tab: ENABLED, with a lock affordance ──────────────────
   It used to carry .off, i.e. color: var(--n-300) — 1.92:1 on the white header,
   which reads as broken. It is not broken: it opens a complete locked-coalition
   dialog (findings.js) with a real schema, an honest empty state, focus
   management and Escape-to-close.
   So it now takes the ordinary unselected-tab ink (--n-600): measured 5.51:1
   light and 5.11:1 dark, against .off's 1.92:1 and 1.93:1. It states its LOCKED
   state the way the PRD requires a
   status to be stated — icon, label AND text, never colour alone: a padlock
   glyph, the label "Projects", and the word "Locked", which is part of the
   accessible name and is therefore announced. No new token, no new hex. */
#tab-projects { cursor: pointer; display: inline-flex; align-items: baseline; gap: 5px; }
#tab-projects:hover { color: var(--n-900); }
#tab-projects:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 2px; border-radius: var(--r-control); }
.t360-lock { width: 11px; height: 11px; flex: 0 0 auto; fill: currentColor;
  align-self: center; opacity: 0.85; }
.t360-lockword { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--n-600); border: 1px solid var(--n-200);
  border-radius: var(--r-control); padding: 1px 5px; }
#tab-projects:hover .t360-lockword { border-color: var(--c-chrome); color: var(--c-chrome); }

/* ── Projects LOCKED empty-state dialog (built by findings.js) ── */
.pj-lock-overlay { position: fixed; inset: 0; z-index: 400; display: flex; align-items: center;
  justify-content: center; padding: 20px; background: rgba(var(--ink-rgb),0.42); }
.pj-lock-overlay[hidden] { display: none; }
.pj-lock-modal { width: 100%; max-width: 640px; max-height: 84vh; display: flex; flex-direction: column;
  background: var(--n-000); border: 1px solid var(--n-150); border-radius: var(--r-panel);
  box-shadow: var(--shadow-2); overflow: hidden; font-family: 'Manrope', sans-serif; }
.pj-lock-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 16px 18px; border-bottom: 1px solid var(--n-100); }
.pj-lock-eyebrow { font-size: 0.56rem; letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: 800; color: var(--n-500); }
.pj-lock-title { font-size: 0.98rem; font-weight: 800; color: var(--n-900); margin-top: 3px; }
.pj-lock-close { border: none; background: none; font-size: 1.25rem; line-height: 1; color: var(--n-500);
  cursor: pointer; padding: 2px 8px; border-radius: var(--r-control); }
.pj-lock-close:hover { background: var(--n-050); color: var(--n-900); }
.pj-lock-close:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 2px; }
.pj-lock-scroll { overflow: auto; padding: 8px 8px 0; }
.pj-lock-table { width: 100%; border-collapse: collapse; }
.pj-lock-table th { text-align: left; font-size: 0.56rem; letter-spacing: 0.05em;
  font-weight: 800; color: var(--n-600); padding: 8px 10px; border-bottom: 1.5px solid var(--n-150); white-space: nowrap; }
.pj-lock-empty { text-align: center; padding: 30px 20px 26px; }
.pj-lock-ic { width: 34px; height: 34px; fill: var(--n-300); margin-bottom: 10px; }
.pj-lock-empty-t { font-size: 0.82rem; font-weight: 700; color: var(--n-800); line-height: 1.5;
  max-width: 430px; margin: 0 auto; }
.pj-lock-empty-s { font-size: 0.68rem; color: var(--n-500); line-height: 1.5; margin: 8px auto 0; max-width: 430px; }
.pj-lock-actions { padding: 14px 18px; border-top: 1px solid var(--n-100); display: flex; justify-content: flex-end; }
.pj-lock-login { font-family: 'Manrope', sans-serif; font-size: 0.76rem; font-weight: 700; padding: 9px 16px;
  border-radius: var(--r-control); border: 1px solid var(--n-150); background: var(--n-050); color: var(--n-400); cursor: not-allowed; }

/* ── left-rail footer: coalition attribution above the independence disclaimer ── */
.rail-attrib { color: var(--n-600) !important; font-weight: 700; margin-bottom: 3px; }
.rail-disc { color: var(--n-500); }

@media (prefers-reduced-motion: reduce) { .pj-lock-overlay { backdrop-filter: none; } }

/* ── review 2026-07-24: true-scale setback diagram street line ── */
.sb-street { stroke: var(--n-500); stroke-width: 2.5; stroke-linecap: round; stroke-dasharray: 6 4; }


/* ═══ City 3D / tree attribute popups — ArcGIS-style field table ═══ */
.ags-pop-title { font: 800 0.82rem/1.3 'Manrope', sans-serif; color: var(--n-900); }
.ags-pop-sub { font-size: 0.65rem; color: var(--n-500); margin: 2px 0 8px; }
.ags-tbl { width: 100%; border-collapse: collapse; }
.ags-tbl tr { border-top: 1px solid var(--n-100); }
.ags-tbl tr:first-child { border-top: 0; }
.ags-tbl th { text-align: left; font-weight: 600; color: var(--n-550); font-size: 0.67rem;
  padding: 4px 10px 4px 0; white-space: nowrap; vertical-align: top; }
.ags-tbl td { text-align: right; font-weight: 700; color: var(--n-900); font-size: 0.7rem;
  padding: 4px 0; font-variant-numeric: tabular-nums; }
.ags-pop-note { font-size: 0.6rem; color: var(--n-500); margin-top: 8px; font-style: italic;
  border-top: 1px solid var(--n-100); padding-top: 6px; }

/* ═══ Glossary ═══ */
.gloss { margin: 0 0 18px; }
.gl-head { font-family: 'Instrument Serif', Georgia, serif; font-size: 1.15rem; color: var(--n-900); }
.gl-sub { font-size: 0.66rem; color: var(--n-550); margin: 2px 0 10px; }
.gl-group { font-size: 0.55rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--n-400); margin: 12px 0 4px; }
.gl-t { border-bottom: 1px solid var(--n-100); }
.gl-t summary { cursor: pointer; padding: 6px 0; list-style: none; display: flex;
  flex-direction: column; gap: 1px; }
.gl-t summary::-webkit-details-marker { display: none; }
.gl-t summary b { font-size: 0.72rem; color: var(--n-900); }
.gl-t summary span { font-size: 0.62rem; color: var(--n-550); }
.gl-t[open] summary b { color: var(--c-blue-deep); }
.gl-body { font-size: 0.65rem; line-height: 1.55; color: var(--n-700); padding: 2px 0 9px; }
.gl-fig { margin-top: 6px; font-size: 0.64rem; color: var(--c-teal-deep); background: var(--c-teal-wash);
  border-radius: var(--r-control); padding: 5px 8px; }
.gl-fig b { font-variant-numeric: tabular-nums; }
.gl-src { margin-top: 5px; font-size: 0.55rem; color: var(--n-400); font-style: italic; }
.gl-foot { margin-top: 12px; padding-top: 7px; border-top: 1px solid var(--n-100);
  font-size: 0.55rem; color: var(--n-400); font-style: italic; line-height: 1.5; }

/* ═══ (H+L) rule diagram — redraws the CDD memo figure on this lot ═══ */
.hl-diagram, .hl-block { margin: 12px 0 4px; }
.hl-h { font-size: 0.58rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--n-700); margin-bottom: 6px; }
.hl-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.hl-col { background: var(--n-025); border: 1px solid var(--n-150); border-radius: var(--r-control); padding: 6px; }
.hl-cap { font-size: 0.58rem; font-weight: 800; margin-bottom: 2px; }
.hl-cap-old { color: var(--n-550); }
.hl-cap-new { color: var(--c-blue-deep); }
.hl-svg { width: 100%; height: auto; display: block; }
.hl-ground { stroke: var(--n-300); stroke-width: 1; }
.hl-lotline { stroke: var(--c-risk); stroke-width: 1.2; stroke-dasharray: 3 2; }
.hl-dim { stroke: var(--n-550); stroke-width: 0.8; }
.hl-set { stroke: var(--c-risk); stroke-width: 1.2; }
.hl-bldg.hl-old { fill: rgba(var(--n-500-rgb),.35); stroke: var(--n-550); stroke-width: 1.2; }
.hl-bldg.hl-new { fill: rgba(var(--blue-rgb),.28); stroke: var(--c-blue-bright); stroke-width: 1.2; }
.hl-t { font-size: 6.5px; fill: var(--n-600); font-family: inherit; font-weight: 700; }
.hl-anchor-m { text-anchor: middle; }
.hl-anchor-s { text-anchor: start; }
.hl-setlbl { fill: var(--c-risk); }
.hl-math { font-size: 0.58rem; color: var(--n-700); margin-top: 4px; line-height: 1.4; }
.hl-math b { color: var(--n-900); }
.hl-bind { display: block; font-size: 0.54rem; color: var(--n-400); }
.hl-foot { font-size: 0.55rem; line-height: 1.45; color: var(--n-400); margin-top: 6px; font-style: italic; }
.hl-bk { background: var(--n-025); border: 1px solid var(--n-150); border-radius: var(--r-control); padding: 8px 10px; }
.hl-bkrow { display: flex; align-items: baseline; gap: 7px; margin-bottom: 2px; font-size: 0.62rem; color: var(--n-600); }
.hl-bkrow b { font-size: 0.82rem; font-weight: 800; color: var(--c-blue-ink); font-variant-numeric: tabular-nums; }

/* ═══ Three-state envelope toggle (old / new no-affordable / new affordable) ═══ */
.env-toggle { display: flex; flex-wrap: wrap; gap: 5px; margin: 0 0 7px; }
.env-chip { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--n-200);
  background: var(--n-025); color: var(--n-400); border-radius: var(--r-panel); padding: 4px 9px;
  font: inherit; font-size: 0.6rem; font-weight: 700; cursor: pointer; }
.env-chip.on { background: var(--n-000); color: var(--n-850); border-color: var(--n-400); }
.env-chip i { width: 9px; height: 9px; border-radius: var(--r-control); border: 1.5px solid; opacity: .45; }
.env-chip.on i { opacity: 1; }
.env-chip i.es-pre { border-color: var(--n-550); background: rgba(var(--n-500-rgb),.25); }
.env-chip i.es-br  { border-color: var(--c-risk-deep); background: rgba(var(--risk-rgb),.22); }
.env-chip i.es-in  { border-color: var(--c-violet-light); background: rgba(var(--violet-rgb),.25); }
.env-note { margin: 0 0 9px; font-size: 0.62rem; }
.en-row { display: flex; gap: 8px; align-items: baseline; margin-bottom: 1px; }
.en-k { flex: 0 0 62px; font-weight: 800; color: var(--n-700); font-size: 0.58rem;
  text-transform: uppercase; letter-spacing: .05em; }
.en-v { color: var(--n-850); }
.en-v i { color: var(--n-400); font-style: normal; }
.en-zone { font-weight: 800; color: var(--n-550); }
.en-sub { margin: 2px 0 7px 70px; font-size: 0.58rem; line-height: 1.45; color: var(--n-550); }
.en-punch { color: var(--c-amber-deep); background: var(--c-amber-wash); border-left: 2px solid var(--c-amber);
  border-radius: 0 var(--r-control) var(--r-control) 0; padding: 5px 7px; margin-left: 70px; }
/* Old rule / new rule: the heights stay open, the derivation folds (Rocker
   2026-08-13, "compress it and unfold it… like the setback envelope"). The two
   .en-sub blocks were aligned to the 70px .en-k gutter of the rows above them;
   inside the fold there is no gutter to align to, so the indent comes off. */
.en-expand { margin-top: 6px; }
.en-expand .en-sub, .en-expand .en-punch { margin-left: 0; }
.en-expand .en-sub:last-child { margin-bottom: 0; }
/* --n-550 measured 3.48:1 on the fold's body in light and 3.54:1 in dark, both
   under the 4.5 the PDR commits to. --n-600 is the file's existing
   secondary-text token and takes it to 5.22 / 4.73. Scoped to the fold — the
   same token is read by unrelated decorative text elsewhere. */
.en-expand .en-sub { color: var(--n-600); }

/* ═══ Multi-site build-out + facade shadow control ═══ */
.buildout-ctl { margin: 0 0 10px; padding: 9px 11px; border-radius: var(--r-control);
  background: var(--n-100); border: 1px solid var(--n-200); }
.bo-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.bo-title { font-size: 0.55rem; font-weight: 800; letter-spacing: 0.09em; color: var(--n-700); }
.bo-toggle { display: inline-flex; background: var(--n-150); border-radius: var(--r-control); padding: 2px; }
.bo-btn { border: 0; background: transparent; cursor: pointer; font: inherit;
  font-size: 0.62rem; font-weight: 700; color: var(--n-600); padding: 4px 10px; border-radius: var(--r-control); }
.bo-btn.active { background: var(--n-000); color: var(--c-blue-deep); box-shadow: var(--shadow-2); }
/* Scenario row: by-right vs inclusionary. Subordinate to Today / Max allowed —
   it only refines what "max" means, so it reads one step quieter. */
.bo-sub { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 7px; }
.bo-sublabel { font-size: 0.53rem; font-weight: 800; letter-spacing: 0.09em;
  color: var(--n-550); text-transform: uppercase; }
.bo-btn-sm { font-size: 0.58rem; padding: 3px 8px; }
.bo-readout { margin-top: 7px; }
.bo-row { display: flex; align-items: baseline; gap: 7px; margin-bottom: 3px; }
.bo-row b { font-size: 0.92rem; font-weight: 800; color: var(--c-blue-ink);
  font-variant-numeric: tabular-nums; min-width: 44px; }
.bo-row span { font-size: 0.62rem; color: var(--n-600); }
.bo-note { font-size: 0.56rem; line-height: 1.5; color: var(--n-550); margin-top: 5px;
  border-top: 1px solid var(--n-150); padding-top: 5px; }
.bo-warn { font-size: 0.62rem; color: var(--c-amber-deep); background: var(--c-amber-wash); border-radius: var(--r-control);
  padding: 6px 8px; }

/* ═══ Model prediction block in the parcel card ═══
   Violet is the model's colour everywhere in this app (the map flag rings use
   var(--c-violet)). It must never borrow the blue/red of an observed filing. */
.pc-model { margin: 0 22px 14px; padding: 11px 12px; border-radius: var(--r-control);
  background: var(--c-violet-wash-2); border: 1px solid var(--c-violet-wash); border-left: 3px solid var(--c-violet); }
.pm-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pm-tag { font-size: 0.53rem; font-weight: 800; letter-spacing: 0.1em;
  color: var(--n-000); background: var(--c-violet); padding: 3px 6px; border-radius: var(--r-control); }
.pm-score { font-size: 1.05rem; font-weight: 800; color: var(--c-violet-deep);
  font-variant-numeric: tabular-nums; }
.pm-title { font-size: 0.72rem; font-weight: 700; color: var(--c-violet-deep); margin: 6px 0 8px; }
.pm-held { font-size: 0.62rem; line-height: 1.45; color: var(--c-violet-muted); margin-bottom: 8px;
  background: var(--c-violet-wash); border-radius: var(--r-control); padding: 6px 7px; }
.pm-held small { color: var(--c-violet-soft); }
.pm-filed { font-size: 0.62rem; line-height: 1.45; color: var(--c-amber-deep); background: var(--c-amber-wash);
  border-left: 3px solid var(--c-amber); border-radius: 0 var(--r-control) var(--r-control) 0; padding: 6px 8px;
  margin-bottom: 9px; }
.pm-filed b { color: var(--c-amber-deep); }
.pm-bar { display: grid; grid-template-columns: 1fr 62px 58px; align-items: center;
  gap: 7px; margin-bottom: 4px; }
.pm-bl { font-size: 0.6rem; color: var(--c-violet-muted); }
.pm-btrack { height: 6px; background: var(--c-violet-wash); border-radius: var(--r-control); overflow: hidden; }
.pm-btrack i { display: block; height: 100%; background: var(--c-violet); border-radius: var(--r-control); }
.pm-bv { font-size: 0.6rem; font-weight: 700; color: var(--c-violet-deep); text-align: right;
  font-variant-numeric: tabular-nums; }
.pm-bv small { color: var(--c-violet-soft); font-weight: 600; }
.pm-conf { font-size: 0.62rem; color: var(--c-violet-muted); margin-top: 8px; }
.pm-conf ul { margin: 3px 0 0; padding-left: 15px; }
.pm-conf li { margin-bottom: 1px; }
.pm-caveat { font-size: 0.58rem; line-height: 1.5; color: var(--c-violet-muted); margin-top: 9px;
  border-top: 1px solid var(--c-violet-wash); padding-top: 7px; }

/* ═══ Official zoning legend (reproduces the CDD printed legend) ═══ */
/* Zoning legend runs HORIZONTALLY (2026-07-31). ~70 districts stacked in a
   268px column ran off the bottom of the window and buried the provenance
   caveats at the end — which are the part that must not be missed, since some
   of those colours are ours and not the city's. Now: each family is a block,
   its swatches sit two rows deep, and the blocks scroll sideways. */
.theme-legend.tl-zoning { max-width: min(880px, 68vw); max-height: none; }
.tl-zscroll { display: flex; gap: 16px; overflow-x: auto; overflow-y: hidden;
  padding: 2px 2px 8px; scrollbar-width: thin; }
.tl-zscroll::-webkit-scrollbar { height: 8px; }
.tl-zscroll::-webkit-scrollbar-thumb { background: var(--n-200); border-radius: var(--r-pill); }
.tl-zgroup { flex: 0 0 auto; }
.tl-zgrid { display: grid; grid-auto-flow: column; grid-template-rows: repeat(2, auto);
  gap: 2px 14px; }
.tl-zgroup .tl-src-warn { margin-top: 6px; max-width: 300px; white-space: normal; }
.tl-zgrid .tl-zrow { white-space: nowrap; }
.tl-fam { font-size: 0.62rem; letter-spacing: 0.09em;
  font-weight: 800; color: var(--n-400); margin: 7px 0 2px; }
.tl-fam:first-of-type { margin-top: 3px; }
.tl-zrow { font-size: 0.68rem; line-height: 1.35; }
/* min-width tracks the code's own type — it is a column, and a district code
   that outgrows its gutter collides with the name it is labelling. */
.tl-code { display: inline-block; min-width: 50px; font-weight: 800; color: var(--n-900);
  font-size: 0.66rem; letter-spacing: 0.02em; }
.tl-src { font-size: 0.6rem; color: var(--n-400); font-style: italic; margin-top: 8px;
  border-top: 1px solid var(--n-100); padding-top: 5px; }
/* Districts we had to colour ourselves — never allowed to read as the city's */
.tl-fam-ours { color: var(--c-amber-deep); }
.tl-src-warn { color: var(--c-amber-deep); font-style: normal; background: var(--c-amber-wash);
  border-top: 0; border-left: 3px solid var(--c-amber); border-radius: 0 var(--r-control) var(--r-control) 0;
  padding: 5px 7px; margin-top: 5px; }


/* ═══ Basemap block inside the layers dock — collapsible so it can never
       squeeze the scrollable layer list out of view ═══ */
#bm3d-ctl { flex: 0 0 auto; }
#bm3d-ctl > summary { cursor: pointer; list-style: none; font-weight: 800;
  letter-spacing: 0.06em; font-size: 0.6rem;
  color: var(--n-600); padding: 2px 0 4px; display: flex; align-items: center; gap: 6px; }
#bm3d-ctl > summary::-webkit-details-marker { display: none; }
#bm3d-ctl > summary::after { content: '▸'; margin-left: auto; font-size: 0.75rem; opacity: .6; }
#bm3d-ctl[open] > summary::after { content: '▾'; }
#bm3d-ctl label { display: block; margin: 2px 0; font-size: 0.68rem; }

/* Collapsed legends for the layers that are on but not the subject. Clicking a
   chip promotes that layer to the full legend. Count on the chip so you know
   how much is hidden before you click — a silent collapse is the same failure
   as a silent truncation. */
.tl-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 7px;
  padding-top: 6px; border-top: 1px solid var(--n-100); }
.tl-chip { font: inherit; font-size: 0.66rem; font-weight: 700; cursor: pointer;
  border: 1px solid var(--n-150); background: var(--n-000); color: var(--n-600);
  padding: 3px 8px; border-radius: var(--r-pill); font-family: 'Manrope', sans-serif; }
.tl-chip:hover { background: var(--n-050); color: var(--n-900); border-color: var(--n-200); }
.tl-chip b { font-weight: 800; color: var(--n-400); margin-left: 2px; }

/* ── DASHBOARD TAB ─────────────────────────────────────────────────────────
   Three columns per the approved mockup: selected neighborhood left, the live
   map and the cross-neighborhood views centre, the index and reading notes
   right. Falls to one column under 1180px, where three columns would put the
   map in a ~300px box and stop it being a map. */
.dash { padding: 16px 20px 40px; overflow-y: auto; }
.dash-head { display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 14px; }
.dash-title { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400;
  font-size: 1.7rem; margin: 0; color: var(--n-900); }
.dash-sub { font-size: 0.76rem; color: var(--n-600); margin: 2px 0 0; }
.dash-pick { display: flex; align-items: center; gap: 8px; }
.dash-pick label { font-size: 0.6rem; letter-spacing: 0.08em;
  font-weight: 800; color: var(--n-400); }
.dash-pick select { font-family: 'Manrope', sans-serif; font-size: 0.78rem; font-weight: 700;
  color: var(--n-900); border: 1px solid var(--n-150); border-radius: var(--r-control); padding: 6px 10px;
  background: var(--n-000); }
.dash-console { display: grid; grid-template-columns: 320px minmax(340px, 0.95fr) 320px;
  gap: 14px; align-items: start; }
.dash-col { display: grid; gap: 14px; }
.dash-col > .card, .dash-col .card { background: var(--n-000); border: 1px solid var(--n-150);
  border-radius: var(--r-panel); padding: 14px 16px; }
.dash-col .card h3, .dash .card h3 { font-size: 0.7rem; letter-spacing: 0.07em; color: var(--n-600); margin: 0 0 10px; }
.dash-mapcard { padding: 0 !important; overflow: hidden; }
.dash-maphead { display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px 8px; }
.dash-maphead h3 { margin: 0; }
/* Rocker 2026-08-03, said four times: the map is "squished" — "make the map
   double as high as it is". She is reading a PROPORTION, not a multiplier, and
   the arithmetic says a literal 920px is the wrong answer. The centre column is
   732px wide here (1400px of .dash content — 1440 viewport, rail closed by
   setView for a full view, 20px padding each side — split 320 / minmax(340,
   0.95fr) / 320 with two 14px gaps). So 460px reads as 1.59:1, and her
   complaint is exact. 560px puts it at 1.31:1, which is the most this can take
   and still leave the legend band above the fold: at 1440x900 the canvas is
   842px under the 58px header, and header + dash head + card head + 560 + band
   comes to ~830. At 920px the map alone would outrun the viewport and push the
   grey band she asked for off-screen, which defeats the point. Do not raise the
   cap without re-doing this sum. The clamp gives short viewports the old 460px
   rather than a map that is the entire scroll. */
.dash-mapslot { position: relative; height: clamp(460px, 64vh, 560px); }
.dash-mapslot #map { position: absolute; inset: 0; width: 100%; height: 100%; }
.dash-chip { display: inline-block; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; padding: 2px 7px; border-radius: var(--r-pill);
  background: rgba(var(--teal-rgb),0.12); color: var(--c-teal-deep); }
.dash-chip.mod { background: rgba(var(--blue-rgb),0.12); color: var(--c-blue-deep); }
.dash-hr { border: 0; border-top: 1px solid var(--n-100); margin: 10px 0; }
.dash-flag { font-size: 0.72rem; color: var(--c-amber-deep); background: var(--c-amber-wash); border-left: 3px solid var(--c-amber);
  border-radius: 0 var(--r-control) var(--r-control) 0; padding: 8px 10px; margin: 0; }
.dash-note { font-size: 0.7rem; color: var(--n-600); margin: 8px 0 0; line-height: 1.5; }
.dash-empty { font-size: 0.8rem; color: var(--n-600); }
/* index ring */
.dash-ring { display: flex; align-items: center; gap: 14px; }
.dash-dial { width: 92px; height: 92px; border-radius: 50%; flex: 0 0 auto; display: grid;
  place-items: center; background: conic-gradient(var(--c-teal) 0 var(--p), var(--n-100) var(--p) 100%); }
.dash-dial b { width: 68px; height: 68px; border-radius: 50%; background: var(--n-000); display: grid;
  place-items: center; font-family: 'Instrument Serif', Georgia, serif; font-size: 1.45rem;
  font-weight: 400; color: var(--n-900); }
.dash-parts { flex: 1; min-width: 0; }
.dash-mini { display: grid; grid-template-columns: 82px 1fr 30px; gap: 7px; align-items: center;
  font-size: 0.68rem; margin-bottom: 4px; color: var(--n-800); }
.dash-mini .t { height: 7px; background: var(--n-050); border-radius: var(--r-pill); overflow: hidden; }
.dash-mini .t i { display: block; height: 100%; background: var(--c-blue-mid); border-radius: var(--r-pill); }
.dash-mini .n { text-align: right; color: var(--n-600); font-variant-numeric: tabular-nums; }
/* donuts, vertical in the narrow left column */
.dash-pies { display: grid; gap: 16px; }
.dash-pie { display: grid; grid-template-columns: 96px 1fr; gap: 12px; align-items: center; }
.dash-pie h4 { grid-column: 1 / -1; font-size: 0.64rem; letter-spacing: 0.05em; color: var(--n-400); margin: 0 0 2px; }
.dash-donut { width: 96px; height: 96px; border-radius: 50%; position: relative; }
.dash-donut::after { content: ''; position: absolute; inset: 24px; background: var(--n-000); border-radius: 50%; }
.dash-key { font-size: 0.68rem; display: grid; gap: 2px; }
.dash-key span { display: flex; align-items: center; gap: 5px; color: var(--n-800); }
.dash-key i { width: 9px; height: 9px; border-radius: var(--r-control); flex: 0 0 auto; }
.dash-key b { margin-left: auto; color: var(--n-600); font-weight: 600; font-variant-numeric: tabular-nums; }
/* matrix */
.dash-matrix { overflow-x: auto; }
.dash-matrix table { border-collapse: collapse; font-size: 0.7rem; width: 100%; min-width: 560px; }
.dash-matrix th { text-align: left; font-weight: 700; color: var(--n-600); padding: 5px 6px;
  font-size: 0.62rem; letter-spacing: 0.04em; white-space: nowrap; }
.dash-matrix td { padding: 0; border: 2px solid var(--n-000); }
.dash-matrix td span { display: block; padding: 6px 7px; border-radius: var(--r-control);
  font-variant-numeric: tabular-nums; }
.dash-matrix tbody th { font-weight: 600; color: var(--n-900); text-transform: none; font-size: 0.7rem; }
.dash-matrix tr.sel th { font-weight: 800; }
/* SELECTION over data. Chrome, not encoding — and moving it off teal improves
   the encoding: a selection highlight must never share a hue with a series in
   the chart it sits on, and .dbar-track/.act-bar are still teal. */
.dash-matrix tr.sel td span { outline: 2px solid var(--c-chrome); outline-offset: -2px; }
.dash-spider { height: 120px; border: 1px dashed var(--n-150); border-radius: var(--r-control); display: flex;
  align-items: center; justify-content: center; text-align: center; padding: 10px;
  font-size: 0.7rem; color: var(--n-400); }
.dash-sources { margin-top: 16px; background: var(--n-000); border: 1px solid var(--n-150);
  border-radius: var(--r-panel); padding: 14px 16px; }
.dash-srcs { list-style: none; padding: 0; margin: 0 0 8px; font-size: 0.7rem; color: var(--n-600);
  line-height: 1.6; }
.dash-srcs a { color: var(--c-blue); font-weight: 700; }
@media (max-width: 1180px) { .dash-console { grid-template-columns: 1fr; } }

/* ── ABOUT TAB ─────────────────────────────────────────────────────────────
   Built from the same .card the dashboard uses, so About inherits the surface,
   border, radius and heading treatment rather than inventing any. Nothing here
   declares a colour or a type step that is not already in this file: the
   provenance legend, the dated source rows and the footnote are the existing
   .method-* classes, MOVED here out of the Findings pane's <details> rather
   than copied, so the two cannot drift apart on the next edit. */
/* THREE columns, not two. Two put the body copy on a 692px measure at 1440 —
   about 120 characters a line, which is where a reader loses the return sweep.
   Three lands it near 78.
   Multi-column rather than grid: these eight cards range from 314px to 1264px
   tall, and hand-assigning them to three grid columns left one column 2.3x the
   height of another whichever way they were dealt. Multicol balances the
   columns itself, and `break-inside: avoid` is what keeps a card whole. */
.about-grid { columns: 3; column-gap: 16px; }
.about-grid > .card { break-inside: avoid; margin: 0 0 16px; display: block; }
/* 0.9rem = 14.4px. This is the ONE surface in the product that is long-form
   prose rather than a dense instrument panel, and DESIGN.md commits to a 14px
   body floor — writing an accessibility statement in 11.5px type would have
   disproved itself in its own first paragraph. 0.9rem is an existing step in
   this file, not a new one. The label/value rows below stay smaller because
   they are metadata, not prose. */
.about-p { font-size: 0.9rem; color: var(--n-600); line-height: 1.6; margin: 0 0 11px; }
.about-view .method-legend li,
.about-view .method-sources li { font-size: 0.8rem; }
/* --n-500 measures 2.90:1 on the About field. That is the class of failure this
   page is meant to disclose, not commit — and every one of these values IS the
   evidence (a source's date, the tracker's snapshot). --n-600 takes it to
   5.09:1. Scoped to .about-view so the Findings pane is untouched. */
.about-view .method-sources li small { font-size: 0.72rem; color: var(--n-600); }
.about-view .method-foot { font-size: 0.8rem; }
.about-view .card h3 { font-size: 0.74rem; }
.about-p:last-child { margin-bottom: 0; }
.about-p strong { color: var(--n-900); font-weight: 700; }
.about-p code, .about-list code { font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 0.92em; background: var(--n-050); padding: 1px 5px;
  border-radius: var(--r-control); color: var(--n-800); }
.about-p a, .about-list a, .method-sources a { color: var(--c-chrome); font-weight: 700; }
.about-list { list-style: none; padding: 0; margin: 0; }
.about-list li { font-size: 0.9rem; color: var(--n-600); line-height: 1.6;
  padding: 0 0 10px 15px; position: relative; }
.about-list li::before { content: ''; position: absolute; left: 2px; top: 9px;
  width: 4px; height: 4px; border-radius: 50%; background: var(--n-300); }
.about-list li strong { color: var(--n-900); font-weight: 700; }
.about-list li:last-child { padding-bottom: 0; }
/* The provenance chips read inline in the Findings pointer, where they sit in a
   sentence rather than at the head of a list row. */
.method-foot .prov-chip { vertical-align: baseline; }
.about-view .method-legend { margin-top: 4px; }
.about-view .cp-weekly:focus-visible,
.method-body .cp-weekly:focus-visible { outline: 2px solid var(--c-chrome);
  outline-offset: 2px; }
/* [data-tracker="unlocated"] is written by syncTrackerCounts() and left EMPTY
   when every tracked address geocoded. Hidden either way, so the card never
   shows a stranded gap where a disclosure would have been. */
.about-p:empty { display: none; }
.about-unloc { border-left: 2px solid var(--n-200); padding-left: 9px; }
@media (max-width: 1180px) { .about-grid { columns: 2; } }
@media (max-width: 820px)  { .about-grid { columns: 1; } }

/* Bars and KPI blocks used by the dashboard renderer. These class names came
   over from the mockup with the markup but their styles did not, so the cards
   rendered as run-together text ("Population7,361") until this landed. */
.kpi { display: flex; flex-direction: column; gap: 2px; }
.kpi-k { font-size: 0.66rem; letter-spacing: 0.05em; color: var(--n-600); }
.kpi-v { font-family: 'Instrument Serif', Georgia, serif; font-size: 1.5rem; color: var(--n-900);
  line-height: 1.15; }
.kpi-vs { display: block; font-size: 0.68rem; color: var(--n-600); font-style: normal; }
.dbar-row { display: grid; grid-template-columns: 96px 1fr 62px; gap: 9px; align-items: center;
  margin-bottom: 5px; font-size: 0.72rem; color: var(--n-800); }
.dbar-lab { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dbar-track { height: 9px; background: var(--n-050); border-radius: var(--r-pill); overflow: hidden; }
.dbar-track i { display: block; height: 100%; background: var(--c-blue-mid); border-radius: var(--r-pill); }
.dbar-val { text-align: right; font-variant-numeric: tabular-nums; color: var(--n-600); }
.dbar-row.sel .dbar-val, .dbar-row.sel .dbar-lab { color: var(--n-900); font-weight: 800; }
.dbar-row.sel .dbar-track i { background: var(--c-chrome); }
.dbars { margin-top: 2px; }

/* ── EXPLORE STRIP ─────────────────────────────────────────────────────────
   Rocker asked for the analytics at the bottom of the map AND a separate tab
   (2026-07-30, 20:30 "yeah, both"). This is the map-linked half: it changes as
   you click, and it stays brief so the map remains the subject. Built as a
   sibling row under the canvas — the floating panels are not touched. */
.map-col { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }
.content-row .map-col > .map-canvas { margin: 14px 14px 10px 20px; flex: 1; min-height: 0; }
.explore-strip { margin: 0 14px 16px 20px; background: var(--n-000); border: 1px solid var(--n-150);
  border-radius: var(--r-panel); flex: 0 0 auto;
  /* r4-D1: the strip stays brief. It scrolls rather than shoving the map up. */
  max-height: 208px; overflow-y: auto; }
.es-main { display: grid; grid-template-columns: 160px 1fr auto; gap: 18px; align-items: center;
  padding: 10px 14px; }
.es-sel { display: flex; flex-direction: column; min-width: 0; }
.es-sel b { font-size: 0.86rem; color: var(--n-900); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.es-hood { font-size: 0.72rem; color: var(--n-600); }
.es-k { font-size: 0.58rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--n-400);
  font-weight: 800; }
.es-charts { display: flex; gap: 22px; flex-wrap: wrap; align-items: flex-start; }
.es-hint { font-size: 0.74rem; color: var(--n-400); }
.es-c { min-width: 120px; }
.es-c h5 { margin: 0 0 5px; font-size: 0.58rem; letter-spacing: 0.05em;
  color: var(--n-400); font-weight: 800; }
.es-cap { font-size: 0.66rem; color: var(--n-600); margin-top: 4px; }
.es-cap b { color: var(--n-900); font-size: 0.78rem; }
.es-vs { position: relative; height: 12px; background: var(--n-100); border-radius: var(--r-pill); }
.es-vs i { display: block; height: 100%; background: var(--c-blue-mid); border-radius: var(--r-pill); }
.es-vs u { position: absolute; top: -3px; width: 2px; height: 18px; background: var(--n-900); }
.es-stack { display: flex; height: 12px; border-radius: var(--r-pill); overflow: hidden; background: var(--n-100); }
.es-stack i { display: block; height: 100%; }
/* Every stacked bar carries its own key — a colour without a label is not a
   reading (DESIGN.md: colour chips always paired with text). */
.es-key { display: flex; flex-wrap: wrap; gap: 2px var(--s-2); margin-top: var(--s-1);
  font-size: 0.6rem; color: var(--n-600); max-width: 260px; }
.es-key span { display: inline-flex; align-items: center; gap: var(--s-1); }
.es-key i { width: 8px; height: 8px; border-radius: var(--r-control); flex: 0 0 auto; }
.es-key b { color: var(--n-900); font-variant-numeric: tabular-nums lining-nums; }
/* The seven-bracket detail a donut click routes here. The strip is a strip
   (DESIGN.md r4-D1: it stays brief), so the seven rows run two-up and the whole
   card is height-capped — nothing is dropped, it just does not push the map
   off the screen. The standing read lives in the Neighborhood analytics tab. */
.es-c-wide { flex: 1 1 360px; min-width: 300px; max-width: 460px; }
.es-c-wide .isp { width: auto; }
.es-c-wide .isp-h, .es-c-wide .isp-med, .es-c-wide .isp-bar, .es-c-wide .isp-key { display: none; }
.es-c-wide .isp-sub { margin-top: 0; }
.es-c-wide .isp-rows { display: grid; grid-template-columns: 1fr 1fr; gap: 2px var(--s-4); }
.es-c-wide .isp-note { font-size: 0.6rem; margin-top: var(--s-2);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.es-donut { width: 42px; height: 42px; border-radius: 50%; position: relative; display: grid;
  place-items: center; }
.es-donut::after { content: ''; position: absolute; inset: 10px; background: var(--n-000); border-radius: 50%; }
.es-donut b { position: relative; z-index: 1; font-size: 0.6rem; font-weight: 800; color: var(--n-900); }
.es-dims { display: flex; gap: 5px; flex-wrap: wrap; }
.es-dims button { font-family: 'Manrope', sans-serif; font-size: 0.66rem; font-weight: 700;
  border: 1px solid var(--n-150); background: var(--n-000); color: var(--n-600); padding: 4px 10px;
  border-radius: var(--r-pill); cursor: pointer; }
.es-dims button.on { background: rgba(var(--chrome-rgb),0.1); border-color: var(--c-chrome); color: var(--c-chrome); }
.es-foot { display: flex; align-items: center; gap: 14px; padding: 8px 14px; border-top: 1px solid var(--n-100);
  background: var(--n-025); border-radius: 0 0 var(--r-panel) var(--r-panel); }
.es-legend { display: flex; gap: 12px; flex-wrap: wrap; flex: 1; font-size: 0.66rem; color: var(--n-800); }
.es-more { margin-left: auto; font-family: 'Manrope', sans-serif; font-size: 0.68rem; font-weight: 700;
  color: var(--c-blue); background: none; border: 0; cursor: pointer; }
@media (max-width: 1200px) { .es-main { grid-template-columns: 1fr; } }
.es-rank { font-family: 'Instrument Serif', Georgia, serif; font-size: 1.5rem; color: var(--n-900);
  line-height: 1.1; }
.es-rank small { font-family: 'Manrope', sans-serif; font-size: 0.62rem; font-weight: 700;
  color: var(--n-400); margin-left: 5px; }
.es-lgt { font-size: 0.62rem; font-weight: 800; color: var(--n-600); margin-right: 4px; }

/* B6 — units lost vs proposed. Two bars per neighborhood, never one: charting
   only the losses would answer the question in a way that misleads. */
.ul-tot { display: flex; gap: 16px; flex-wrap: wrap; font-size: 0.72rem; color: var(--n-600);
  padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid var(--n-100); }
.ul-tot b { color: var(--n-900); font-size: 0.86rem; }
.ul-net b { color: var(--c-teal-deep); }
.ul-row { display: grid; grid-template-columns: 118px 1fr 78px; gap: 9px; align-items: center;
  margin-bottom: 5px; font-size: 0.7rem; }
.ul-lab { color: var(--n-800); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ul-bars { display: flex; flex-direction: column; gap: 2px; }
.ul-bars i { display: block; height: 7px; border-radius: var(--r-pill); min-width: 2px; }
i.ul-lost { background: var(--c-risk); }
i.ul-prop { background: var(--c-blue-mid); }
.ul-val { text-align: right; font-variant-numeric: tabular-nums; color: var(--n-600); }
.ul-key { display: flex; gap: 14px; margin-top: 8px; font-size: 0.66rem; color: var(--n-600); }
.ul-key span { display: inline-flex; align-items: center; gap: 5px; }
.ul-key i { width: 11px; height: 7px; border-radius: var(--r-pill); display: inline-block; }


/* ═══════════════════════════════════════════════════════════════════════════
   P0 · NOTHING FLOATS ON THE MAP  (Rocker 2026-07-31, 5:20 and again at 8:24)
   "I don't want anything in the map. I want to see the map 100% unobscured…
    on the bottom, under the map, is where the legend goes. On the left side
    you have all the layers… on the right side is the information."

   Three rules, enforced here rather than by discipline:
     · .map-canvas carries the GL canvas and the Mapbox north arrow + scale bar.
       Nothing else. (.table-view is exempt — it REPLACES the map, it is the
       keyboard/screen-reader path, and it is never up at the same time.)
     · every control docks into the left rail, compressed, one scroll;
     · the legend is a full-width strip UNDER the map, spanning the map column.
   The elements below were `position:absolute` inside .map-canvas. They are the
   SAME elements with the SAME ids — renderLayersPanel / renderThemeLegend /
   setupSolarSlider all still find them — only re-parented and un-positioned.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The rail now carries the layer dock (252px of content), so it needs the width. */
.sidebar.rail { width: 252px; }
.main { margin-left: 252px; }
@media (max-width: 1250px) { .sidebar.rail { width: 232px; } .main { margin-left: 232px; } }

/* L2 — compressed. Her words: "make that compress a little bit so we need to
   scroll more". Tighter rows, tighter group headers, one scroll for the lot. */
.rail-scroll { padding: 8px 10px 14px; }
.rail-item { padding: 5px 9px; gap: 8px; font-size: 0.74rem; line-height: 1.25; }
.rail-h { margin: 11px 0 4px; }
.rail-src-row { padding: 3px 4px; }

/* Map view: the 3D/top-down and map/table toggles, docked. */
.rail-mapview { display: flex; flex-direction: column; gap: 5px; padding: 0 2px 2px; }
.rail-scroll .view-toggle,
.rail-scroll .mode-toggle {
  position: static; inset: auto; transform: none; z-index: auto;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 3px;
  background: var(--n-050); border: 1px solid var(--n-150); border-radius: var(--r-control); padding: 3px;
  box-shadow: none;
}
.rail-scroll .vt-btn { padding: 6px 8px; font-size: 0.72rem; text-align: center; }
.rail-scroll .vt-btn.active { background: var(--c-chrome); color: var(--n-000); }

/* Layer dock, docked. `overflow: clip` is KEPT deliberately — see the note by
   the .city-layers rule above; `hidden` would re-create the focus-scroll bug.
   With max-height gone the rail is the single scroll container, so .cl-list no
   longer needs its own (nested scrollers inside a scrolling rail are a trap). */
.rail-scroll .city-layers {
  position: static; inset: auto; z-index: auto;
  width: auto; max-height: none; margin: 0;
  background: var(--n-000); border: 1px solid var(--n-150); border-radius: var(--r-panel);
  box-shadow: none; min-width: 0;
}
.rail-scroll .cl-list { overflow-y: visible; max-height: none; flex: 0 0 auto; }
.rail-scroll .cl-head { position: sticky; top: 0; background: var(--n-000); z-index: 2; border-radius: var(--r-panel) var(--r-panel) 0 0; }
.rail-scroll #bm3d-ctl { font-size: 0.66rem; }

/* The `.rail-scroll .solar-slider` docked variant that lived here was deleted
   2026-08-07. The rail dock was tried 2026-08-03 and reverted 2026-08-05 (the
   rail is 20px collapsed by default and opening it steals map width); the CSS
   was left behind styling nothing. The solar bar is now a 56px transport strip
   on the map's bottom edge — see .solar-slider at the top of this file.
   The build-out block DID move into the rail on 2026-08-07, so it keeps its
   narrow-column wrap. */
.rail-scroll .bo-head, .rail-scroll .bo-sub { flex-wrap: wrap; gap: 4px; }
.rail-scroll .buildout-ctl { margin: 2px 0 0; padding: 8px 9px; }
.rail-scroll .bo-toggle { flex-wrap: wrap; }

/* L3 — THE LEGEND STRIP. Full width of the map column, directly under the map,
   above the analytics strip. Reason it is not a rail panel: a collapsed side
   legend cannot be referenced while you are reading the map, which is the whole
   point of a legend. The rail keeps per-row swatches; this is canonical.

   2026-08-03 — IT IS NOW A GREY BAND, AND THE SAME BAND ON BOTH MAP SURFACES.
   Her words: "where the legend starts needs to be sitting lower… if you move
   the legend all the way to the grey beneath it, that's where I want it. I want
   more space for the map." So it stops being a white card floating on a white
   column: full-bleed, no radius, --n-100 (the token already reserved for wash /
   inset surfaces) with a hairline on top. The hairline is what makes the map
   above it read as ENDING — a band with no edge just looks like the map ran out
   of pixels. --n-100 rather than --n-050 because on the analytics tab the host
   is a white card, and the page grey is too close to white to register there.

   .dash-mapcard is the second host. The analytics tab shows the SAME map, so it
   gets the same legend rather than a second design; the card's overflow:hidden
   clips the band's square corners back to the card radius.
   :is() keeps the specificity identical to the old `.map-col >` selector, so
   nothing else in the cascade moves. */
:is(.map-col, .dash-mapcard) > .theme-legend {
  position: static; inset: auto; z-index: auto;
  max-width: none; width: auto; margin: 0;
  flex: 0 0 auto;
  flex-direction: row; flex-wrap: wrap; align-items: center;
  gap: 4px 14px;
  max-height: 86px; overflow-y: auto; overflow-x: hidden;
  border: 0; border-top: 1px solid var(--n-150); border-radius: 0;
  padding: 10px 14px 12px;
  box-shadow: none; background: var(--n-100);
}
/* Explore insets its canvas 20px from the rail. The band still runs edge to
   edge, but its first swatch lines up with the map above it rather than with
   the band's own corner — the alignment is the thing you actually read. */
.map-col > .theme-legend { padding-left: 20px; }
/* Every hairline INSIDE the strip was drawn in --n-100 against white. --n-100
   is now the band itself, so each one steps down to --n-200 or it disappears,
   and the chips take back a white surface — a control that dissolves into its
   own background stops reading as a control. */
:is(.map-col, .dash-mapcard) > .theme-legend .tl-title {
  margin: 0; flex: 0 0 auto; align-self: center;
  border-right: 1px solid var(--n-200); padding-right: 12px;
}
:is(.map-col, .dash-mapcard) > .theme-legend .tl-chips { margin-top: 0; flex: 0 0 auto;
  border-top-color: var(--n-200); }
:is(.map-col, .dash-mapcard) > .theme-legend .tl-src { margin-top: 0; flex: 1 1 100%;
  border-top-color: var(--n-200); }
:is(.map-col, .dash-mapcard) > .theme-legend .tl-chip { background: var(--n-000); }
/* The groups take the width the strip has; the title stays a fixed left rail. */
:is(.map-col, .dash-mapcard) > .theme-legend .tl-groups { flex: 1 1 auto; min-width: 0; }
/* 150 → 164 and 132 → 144: the old caps plus the ~9% the tl- type grew (see the
   type block up by .tl-title). These are scroll/clip limits, and a cap left
   behind by its own content is a silent truncation — the failure the chip
   counts exist to prevent. */
:is(.map-col, .dash-mapcard) > .theme-legend { max-height: 164px; }
/* The ~70-district zoning legend already scrolls sideways two rows deep, which
   is exactly the shape a bottom strip wants. Give it the full width. */
:is(.map-col, .dash-mapcard) > .theme-legend.tl-zoning { max-width: none; max-height: 144px; overflow-y: hidden; }
:is(.map-col, .dash-mapcard) > .theme-legend.tl-zoning .tl-zscroll { flex: 1 1 100%; }

/* The panel-reopen chip, docked into the analytics strip's footer. */
.es-foot .cp-open { position: static; inset: auto; z-index: auto; }
.es-foot .cp-open[hidden] { display: none; }

/* The stacked S2 transport block (.ss-anim / .ss-seasons / .ss-speed-wrap and
   a full-bleed .ss-play) was deleted 2026-08-07 — 131px of the old column.
   Play is a 64px transport button on the bar; speed and record moved into the
   "⋯" popover; the December/June pair went entirely, being a second visual
   language for the Dec 21 / Jun 21 chips one row above it. All of it now lives
   in the .solar-slider block at the top of this file. */

/* ── P1 · MASSING & SHADOW rail block (S1, S4) ────────────────────────────── */
.rail-massing { display: flex; flex-direction: column; gap: 6px; padding: 0 2px 2px; }
.mv-toggle { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px;
  background: var(--n-050); border: 1px solid var(--n-150); border-radius: var(--r-control); padding: 3px; }
.mv-btn { font-family: 'Manrope', sans-serif; font-size: 0.66rem; font-weight: 700;
  border: none; background: none; color: var(--n-600); padding: 6px 4px; border-radius: var(--r-control);
  cursor: pointer; }
.mv-btn:hover { color: var(--n-900); }
/* Solid reads red because the state it turns on is red. */
.mv-btn.on { background: var(--c-chrome); color: var(--n-000); }
.mv-btn[data-massing="solid"].on { background: var(--c-risk); }
.mv-check { display: block; font-size: 0.68rem; color: var(--n-800); line-height: 1.3;
  cursor: pointer; padding: 2px 2px 0; }
.mv-check input { accent-color: var(--c-blue-deep); margin-right: 5px; }
.mv-check small { display: block; font-size: 0.58rem; color: var(--n-500); margin-left: 20px; }
.mv-goto { font-family: 'Manrope', sans-serif; font-size: 0.68rem; font-weight: 700;
  border: 1px solid var(--c-risk-line); background: var(--c-risk-wash); color: var(--c-risk-deep); padding: 6px 8px;
  border-radius: var(--r-control); cursor: pointer; text-align: left; }
.mv-goto:hover { background: var(--c-risk-wash); }

/* ── P4 · H3 · INCOME SPREAD popup (click a proportional circle) ──────────── */
/* Not a donut. Seven ordered brackets are a SEQUENCE — a donut throws the order
   away and asks the reader to compare arc lengths around a ring, which is the
   one thing a stacked bar does better. The grammar she asked for is "median AND
   the share who are poor", and that is what this prints. */
.isp { width: 300px; font-family: 'Manrope', sans-serif; }
.isp-h { font-weight: 800; font-size: 0.9rem; color: var(--n-900); }
.isp-med { font-size: 0.72rem; color: var(--n-600); margin: 2px 0 9px; }
.isp-med b { color: var(--n-900); font-size: 0.82rem; font-variant-numeric: tabular-nums; }
.isp-bar { display: flex; height: 15px; border-radius: var(--r-control); overflow: hidden;
  background: var(--n-100); margin-bottom: 8px; }
.isp-bar i { display: block; height: 100%; min-width: 1px; }
.isp-key { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 10px;
  font-size: 0.62rem; color: var(--n-600); }
.isp-key span { display: inline-flex; align-items: center; gap: 5px; }
.isp-key i { width: 9px; height: 9px; border-radius: var(--r-control); flex: 0 0 auto; }
.isp-key b { color: var(--n-900); font-variant-numeric: tabular-nums; }
.isp-note { margin-top: 9px; font-size: 0.64rem; line-height: 1.45; color: var(--n-800);
  background: var(--n-050); border-left: 3px solid var(--c-amber); padding: 6px 8px; border-radius: 0 var(--r-control) var(--r-control) 0; }
.isp-note b { color: var(--c-amber-deep); }
.isp-src { margin-top: 7px; font-size: 0.56rem; color: var(--n-400); font-style: italic; }
/* The seven published ACS brackets, under the four display classes. Same ramp,
   finer resolution — never a second palette (B5). */
.isp-sub { margin-top: 10px; font-size: 0.58rem; letter-spacing: 0.06em; font-weight: 800; color: var(--n-500);
  display: flex; align-items: center; gap: 6px; }
.isp-rows { margin-top: 5px; display: flex; flex-direction: column; gap: 2px; }
.isp-row { display: grid; grid-template-columns: 10px 1fr auto; gap: 6px;
  align-items: center; font-size: 0.63rem; color: var(--n-700); position: relative;
  padding-bottom: 3px; }
.isp-row i { width: 10px; height: 10px; border-radius: var(--r-control); }
.isp-row b { color: var(--n-900); font-variant-numeric: tabular-nums lining-nums; }
.isp-row u { position: absolute; left: 16px; bottom: 0; height: 2px;
  border-radius: var(--r-pill); opacity: 0.75; max-width: calc(100% - 16px); }

/* ── DONUT COMPOSITION MARKERS (U1) ────────────────────────────────────────
   DOM markers, not a Mapbox circle layer: a circle layer cannot draw arcs, and
   a marker gives the click target, the focus ring and the accessible name for
   free. NO text on the marker — the breakdown is one click away in the panel,
   which is the documented mitigation for pie-symbol comprehension. */
.hood-donut {
  display: grid; place-items: center; cursor: pointer;
  filter: drop-shadow(0 1px 2px rgba(var(--ink-rgb), 0.28));
  transition: transform 0.12s ease;
}
.hood-donut svg { display: block; }
.hood-donut:hover { transform: scale(1.06); }
.hood-donut:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 3px; border-radius: 50%; }
/* THE ONE PLACE CHROME AND INCOME SHARE SPACE, disclosed rather than hidden.
   These donuts are Mapbox markers drawn in the income macro colours, sitting
   on the income-fill layer, so a selection ring here is a saturated ring on a
   saturated multi-hue field. Measured against the income arcs:
     teal   #2b8577 — 1.89:1 on m3 #1c4f84, but 1.01:1 on m2 #477cb0 (gone)
     chrome #1c5c84 — 1.16:1 on m3 #1c4f84, 1.64:1 on m2 #477cb0
   Neither hue is reliable; chrome is the less bad of the two, and it keeps
   selection consistent with the rest of the shell. The ring is NOT the only
   selection cue (.sel also scales 1.08 and the panel opens), so this is a
   weak signal rather than a broken one.
   The real fix is a change of FORM, not hue — a white outer halo under the
   ring so it reads against any arc. Queued with the chart-series decision. */
.hood-donut.sel { transform: scale(1.08); }
.hood-donut.sel::after {
  content: ''; position: absolute; inset: -6px;
  border: 2px solid var(--c-chrome); border-radius: 50%;
}
.hood-donut { position: relative; }

/* ── THE ONE POPUP, IN TOOLTIP DRESS (research rule 6) ─────────────────────
   Two lines: name + one headline value. No close button, no pointer events —
   a tooltip you can click is a popup, and popups are what B1 was about. */
.mapboxgl-popup.pop-tip .mapboxgl-popup-content {
  padding: 7px 10px; border-radius: var(--r-control);
  box-shadow: var(--shadow-2); font-family: 'Manrope', sans-serif;
  background: var(--n-000); border: 1px solid var(--n-150);
}
.mapboxgl-popup.pop-tip .mapboxgl-popup-close-button { display: none; }
.mapboxgl-popup.pop-tip { pointer-events: none; max-width: 220px; }
.pop-tip .tip-n { display: block; font-size: 0.74rem; font-weight: 800; color: var(--n-900);
  line-height: 1.25; }
.pop-tip .tip-v { display: block; font-size: 0.68rem; color: var(--n-600);
  font-variant-numeric: tabular-nums lining-nums; line-height: 1.3; }
.pop-tip .tip-h { display: block; font-size: 0.56rem; color: var(--n-400);
  margin-top: 3px; font-style: italic; }

/* ── CONTROLS DRAWER (2026-08-03) ──────────────────────────────────────────
   Quan: "for frame 1 it should show parcel card on the right with layers
   drawer (open)?" — yes, and collapsing the drawer is what turns FRAME 2
   (rail + map + parcel card) into FRAME 1 (map + parcel card). One shell,
   two states, so there is no second layout to keep in sync.

   The rail is position:fixed and .main clears it with margin-left, so
   collapsing RECLAIMS the space for the map instead of sliding a panel over
   it. Rocker's "I want to see the map 100% unobscured" holds in both states,
   which an overlay drawer would not. */
.sidebar.rail { transition: width .18s ease; }
.main         { transition: margin-left .18s ease; }
body.rail-off .sidebar.rail { width: 0; border-right: none; overflow: hidden; }
body.rail-off .main         { margin-left: 0; }

.rail-toggle { display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto;
  margin-right: 12px; padding: 7px 11px; border: 1px solid var(--n-150); border-radius: var(--r-control);
  background: var(--n-000); color: var(--n-900); cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.74rem; font-weight: 700; }
.rail-toggle:hover  { background: var(--n-025); border-color: var(--n-200); }
/* Was var(--accent, var(--c-blue-deep)) — an alert token with a chrome
   fallback that could never fire, so this focus ring rendered AMBER while the
   other seventeen rendered blue. The dead fallback records what was meant. */
.rail-toggle:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 2px; }
.rail-toggle svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; }
.rt-count { font-variant-numeric: tabular-nums lining-nums; color: var(--n-500); font-weight: 600; }

/* ── THEME TOGGLE · sits immediately after Controls, borrows its chrome ──────
   Icon-only by default. There is room for it now only because the nav labels
   were shortened; the header measures 0px of overflow at 1280 both before and
   after this button, which is the number that decides whether it may exist.
   The text label is a >1500px luxury and nothing depends on it. */
.theme-toggle { padding: 7px 9px; margin-right: 8px; }
.theme-toggle svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* One <svg> per state, and CSS picks — no icon rebuilt in JS, so the button
   cannot end up showing a state the attribute disagrees with. */
.theme-toggle .tt-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .tt-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .tt-sun  { display: block; }
.tt-label { display: none; }
@media (min-width: 1501px) { .tt-label { display: inline; } .theme-toggle { padding: 7px 11px; } }
/* Neighborhood analytics is full-page: the drawer is not just closed, it is
   gone, because none of its map controls apply there (directive U3). */
body.view-full .rail-toggle { display: none; }

/* S1 — a snapshot past its refresh window says so rather than looking current. */
/* --c-amber-ink is not defined anywhere, so this was rendering its raw #a9781f
   fallback — a retired 3.60:1 literal hiding behind a dangling token, and the
   last raw amber hex below the token block. Pointed at the alert token it
   always meant. */
.is-stale { color: var(--c-amber); font-weight: 700; }

/* G1 — a Drive file that is not link-shared says so, rather than vanishing. */
.pj-nomedia { padding: var(--s-3, 12px); font-size: 0.76rem; line-height: 1.45;
  color: var(--c-slate-600, #5b6b78); background: var(--c-slate-050, #f4f6f8);
  border: 1px dashed var(--c-line, #dde3e8); border-radius: var(--r-control, 8px); }

/* Projects the tracker holds but we could not geocode — stated, never dropped. */
.cp-unloc { display: block; margin-top: 4px; color: var(--c-slate-600, #5b6b78); font-size: 0.72rem; }

/* Tabs that navigate to a real page rather than switching a view. They are
   links, not spans, so they open in a new tab, get focus rings and can be
   middle-clicked — a disabled <span> could do none of that. */
.t360-link { text-decoration: none; }
.t360-link:hover { color: var(--n-900); }

/* ── F1 · NEIGHBORHOOD IN FOCUS ─────────────────────────────────────────── */
.focus-card { border-left: 3px solid var(--c-chrome); }
.focus-name { font-family: 'Instrument Serif', Georgia, serif; font-size: 1.55rem;
  line-height: 1.15; margin: var(--s-2) 0 var(--s-1); }
.focus-line { font-size: 0.8rem; color: var(--n-800); }
.focus-tie { font-size: 0.7rem; color: var(--n-600); margin-top: 3px; font-style: italic; }
.focus-tbl { width: 100%; border-collapse: collapse; margin-top: var(--s-3); font-size: 0.72rem; }
.focus-tbl td { padding: 3px 0; border-bottom: 1px solid var(--n-100); color: var(--n-700); }
.focus-tbl td.n { text-align: right; font-variant-numeric: tabular-nums lining-nums; white-space: nowrap;
  padding-left: var(--s-3); }
.focus-tbl tr:last-child td { border-bottom: 0; }
/* The method note is the point of the card, not fine print — it explains why
   the cadence is not the weekly one that was asked for. */
.focus-why { margin-top: var(--s-3); padding: var(--s-3); border-radius: var(--r-control);
  background: var(--n-050); font-size: 0.7rem; line-height: 1.5; color: var(--n-700); }
.focus-voice { margin-top: var(--s-2); font-size: 0.68rem; color: var(--n-500); font-style: italic;
  border: 1px dashed var(--n-200); border-radius: var(--r-control); padding: var(--s-2) var(--s-3); }

/* ================================================================
   MAP UNDERLAY — one block, two columns
   ================================================================
   Rocker 2026-08-03, clarified 08-05: "nothing on the bottom" was
   never a request to DELETE the bottom strip — the 07-30 record
   ("yeah, both") stands. What she meant is that the two stacked
   blocks under the map eat too much height: "the income tab takes
   too much space… can we instead make into one table but in two
   columns."

   Stacked, the map key (max 164px) and the selected-parcel strip
   (max 208px) cost up to ~372px plus margins. Side by side they
   cost max(164, 208) = 208px — about 164px of map height returned,
   which is the same fight as the map-height clamp above.

   Done by gridding .map-col itself rather than wrapping the two in
   a container: nine selectors above depend on
   `:is(.map-col, .dash-mapcard) > .theme-legend` being a DIRECT
   child, and a wrapper would silently break every one of them.

   2026-08-13 — THE RIGHT CELL IS NOW .map-footer, AND IT CARRIES THE
   SHADOW TOOL. Rocker: "what is currently floating on the map, we put
   down there. So their shadow tool is always with us… And then the
   other one on the left side is the legend map." So the row still has
   exactly two cells — LEGEND left, SHADOW right — and the selection
   strip rides inside the right one under the bar rather than claiming
   a third cell, because a third cell is a third band row and the
   standing note is that the map is too small.

   .theme-legend stays a DIRECT child for the reason above. Only
   .explore-strip moved, into .map-footer; its six `> .explore-strip`
   selectors moved with it and are the `.map-footer > .explore-strip`
   rules below.
   ---------------------------------------------------------------- */
.content-row .map-col {
  display: grid;
  /* NOT A RATIO — A WIDTH THE SHADOW BAR ACTUALLY NEEDS, AND THE REST TO THE
     LEGEND. The right cell used to be 1.15fr because it held five analytic
     cards; it now holds the transport bar, whose height is a step function of
     its width. Measured 2026-08-17 by driving .map-footer's width directly:

        cell width   bar rows / height   footer (bar + 8 + strip)
        >= 420px         2 / 108px             153px   ← fits the band
        380-400          3 / 160px             219px
        <= 360           4 / 212px             271px

     420 is not arbitrary: the bar's first row is dates 222 + all-hours 74 +
     play 64 + two 10px gaps = 380, and 380 + 12+12 padding + 2 border = 406.
     420 is that with headroom. Below it the bar takes a third row, the footer
     overtakes the LEGEND as the band's tallest cell, and the band grows — i.e.
     the map pays for the bar's wrapping, which is the whole thing this move was
     supposed to avoid.

     So: the right track is `minmax(0, 420px)` — it takes 420 when there is 420
     to take and shrinks rather than overflowing when there is not — and the
     legend takes everything left over, with a 200px floor so it never
     collapses. One rule for every topic; the columns no longer jump width when
     you change topic.

     The floor is 200 and not 260 for a measured reason: at 1280 with the left
     rail OPEN the band has 624px to divide, and a 260px floor leaves the footer
     404px — fourteen pixels under the cliff, which costs the map 43px of height
     to buy the legend 56px of width. The legend is height-capped and scrolls;
     the map is not. Between roughly 1100 and 1230 with the rail open there is
     not enough width for both and the band does grow by ~43px; below 1100 the
     pair stacks (see the breakpoint at the end of this block). */
  grid-template-columns: minmax(200px, 1fr) minmax(0, 420px);
  grid-template-rows: minmax(0, 1fr) auto;
  align-content: stretch;
  column-gap: 14px;
}
.content-row .map-col > .map-canvas { grid-column: 1 / -1; grid-row: 1; }

/* Two cells, each its own hairline card. The single-divider "one block"
   treatment retired with the move: it was two halves of one strip, and the
   right half is now a stack of two controls, which cannot butt a shared
   border without drawing that border twice. */
.content-row .map-col > .theme-legend {
  grid-column: 1; grid-row: 2;
  margin: 0 0 16px 20px;
  border: 1px solid var(--n-150);
  border-radius: var(--r-panel);
  background: var(--n-050);
  max-height: 208px; overflow-y: auto;
  padding: 10px 14px;
}

/* ── .map-footer — the band's right cell ────────────────────────────────────
   A bare flex column: no border, no padding, no background of its own. Its
   two children are already cards, and a box around two boxes is a third box.
   That also makes it FREE when the solar bar is display:none — an empty flex
   column with no padding is 0px tall, so on every non-shadow topic this cell
   is exactly the selection strip and nothing more, which is what it was
   before the move.

   Measured at 1440 (2026-08-17): legend 176px vs footer 100px (56 bar + 8 gap
   + 36 strip) on the Environment topic, so the ROW is 176px — set by the
   legend, unchanged from before the move, and the canvas keeps its 587px. */
.content-row .map-col > .map-footer {
  grid-column: 2; grid-row: 2;
  margin: 0 14px 16px 0;
  display: flex; flex-direction: column; gap: 8px;
  min-width: 0; align-self: start;
}
.content-row .map-col > .map-footer > .explore-strip {
  margin: 0;
  border-radius: var(--r-panel);
  max-height: 208px;
}
/* The map key carries the quieter surface, so its chips need to step back
   up to the card colour to still read as controls against it. */
.content-row .map-col > .theme-legend .tl-chip { background: var(--n-000); }

/* The shadow topic used to get its OWN column split (1.25fr/0.75fr) to buy the
   legend width for the era toggle. That override is gone: the track sizing
   above is already "the bar's 420px, and the rest to the legend", which gives
   the legend MORE than the old override did at every width it was measured at
   (616px at 1440 against 649px before, with the band 29px shorter).

   max-height still gets headroom on the shadow topic: it only ever clips, so it
   costs nothing while the content fits, and it stops a silent truncation if it
   stops fitting. A cap left behind by its own content is the failure the
   "+N more" chips exist to prevent. */
.content-row .map-col:has(> .theme-legend .sera-cell) > .theme-legend,
.content-row .map-col:has(> .theme-legend .sera-cell) > .map-footer > .explore-strip {
  max-height: 256px;
}

/* With no subject layer the legend is [hidden]; the footer then takes the full
   width rather than leaving a dead column. */
.content-row .map-col:has(> .theme-legend[hidden]) > .map-footer {
  grid-column: 1 / -1; margin-left: 20px;
}

/* Below the two-column threshold the pair STACKS — legend over shadow. Two
   ~400px columns would put the legend's swatch groups and the transport bar's
   scrubber under the same squeeze at the same time, and the bar's scrubber has
   a 300px flex basis it cannot go under without wrapping to a third row. One
   full-width legend over one full-width footer is the honest shape. */
@media (max-width: 1100px) {
  .content-row .map-col { grid-template-columns: minmax(0, 1fr); }
  .content-row .map-col > .theme-legend {
    grid-column: 1; grid-row: 2; margin: 0 14px 8px 20px;
  }
  .content-row .map-col > .map-footer {
    grid-column: 1; grid-row: 3; margin: 0 14px 16px 20px;
  }
  /* No extra legend cap is needed here, and one was measured and removed rather
     than left in place doing nothing: at 1000 the stacked band is legend 176 +
     8 + footer 153 = 337, against 352 for the two stacked strips it replaces,
     so the map is 15px TALLER stacked than it was before the move. The bar gets
     the full column width and still renders two rows (its first row needs 380px
     and its scrubber another 347). */
}

/* ================================================================
   SOLAR TIME — no longer anchored to, or over, the MAP
   ================================================================
   Quan 2026-08-05: "solar time should be in the map bottom." It got there in
   two steps: out of the rail and onto the canvas's bottom edge (2026-08-07),
   then off the canvas entirely and into the bottom band (2026-08-13, Rocker:
   "what is currently floating on the map, we put down there").

   THREE RULES DIED WITH THAT SECOND MOVE, and none of them is coming back:
     · `.solar-slider { position:absolute; left/right/bottom:12px }` — the bar
       is a flex child of .map-footer now, see the top of this file;
     · `.map-canvas.has-solarbar .mapboxgl-ctrl-bottom-* { bottom: calc(16px +
       var(--solarbar-h)) }` — the Mapbox scale and attribution had to step up
       over the bar while it covered the canvas. Nothing covers the canvas, so
       they sit where Mapbox puts them;
     · `--solarbar-h`, published by a ResizeObserver in cambridge.js, which had
       exactly one reader — the rule above. Both are deleted.
   The canvas now carries the north arrow and the scale bar and NOTHING else,
   which is the standing rule ("I don't want anything in the map").
   ---------------------------------------------------------------- */

/* ── WHOSE SHADOW — moved out of the solar bar and into the LEGEND BAND
   2026-08-07. It is a "what am I looking at" control, not a "when" control:
   same class as the swatches it now sits beside, it costs the canvas zero
   pixels, and the band already carries the provenance line the two envelopes
   need. It stays a first-class control — it is the newest and most
   argumentative thing on this topic — and the label is always the full
   "Ord 2025-1", never "New": that string IS the finding.
   Each era is paired with the colour it draws on the map, so the toggle and
   the legend read as one object. ── */
/* `Shadow [Old rule|Both|Ord 2025-1]` on one line, the sun caption beneath,
   as the FIRST cell of the band's group flow. It takes a full row of that flow
   — measured: at the band's ~440px there is nothing it can share a line with —
   which is why the band's cap is raised alongside it. Two other shapes were
   built and measured first: a stacked .tl-group (71px, worse) and a narrow
   vertical stack beside .tl-title (that one stole 136px of width from the
   groups and reflowed them 40px TALLER — the band's binding constraint is
   width, not height). */
.sera-cell { flex: 1 1 100%; flex-direction: row; flex-wrap: wrap;
  align-items: center; gap: 3px 8px; }
.sera-cell .tl-gname { flex: 0 0 auto; }
.sera-cell .sera-toggle { display: inline-flex; border: 1px solid var(--n-200);
  border-radius: var(--r-control); overflow: hidden; background: var(--n-000); }
.sera-cell .tl-gsub { flex: 1 1 100%; margin: 0; }
.sera-btn { display: inline-flex; align-items: center; gap: 5px;
  border: 0; background: transparent; cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.7rem; font-weight: 700; color: var(--n-700);
  padding: 5px 8px; white-space: nowrap; }
.sera-btn + .sera-btn { border-left: 1px solid var(--n-200); }
.sera-btn:hover { background: var(--n-050); color: var(--n-900); }
.sera-btn[aria-pressed="true"] { background: var(--c-chrome); color: var(--n-000); }
.sera-btn:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: -2px; }
/* The swatches ARE the map's own casts: the pre-2025 envelope is drawn as a
   dashed outline at low fill, Ord 2025-1's as a solid 150/255 cast. */
.sera-sw { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
.sera-sw-old  { background: rgba(var(--ink-rgb),0.16); border: 1px dashed rgba(var(--ink-rgb),0.75); }
.sera-sw-new  { background: rgba(var(--ink-rgb),0.59); border: 1px solid rgba(var(--ink-rgb),0.47); }
.sera-sw-both { background: linear-gradient(135deg,
                  rgba(var(--ink-rgb),0.16) 0 50%, rgba(var(--ink-rgb),0.59) 50% 100%);
                border: 1px solid rgba(var(--ink-rgb),0.47); }
.sera-btn[aria-pressed="true"] .sera-sw { border-color: rgba(var(--white-rgb),0.85); }
/* 11px / --n-600, not the band's shared 0.6rem-on---n-400: this caption is
   load-bearing (it is the sun altitude the shadow is cast from) and the shared
   style is under both DESIGN.md's 11px caption floor and 4.5:1. */
.sera-cell .tl-gsub { font-variant-numeric: tabular-nums; font-size: 11px;
  color: var(--n-600, #586b7d); max-width: none; }

/* ── LAYER ON/OFF CHIPS, in the same cell ───────────────────────────────────
   Rocker 2026-08-13: "we can keep on the bottom the navigation between legend
   on the left side and on the right side. For example, we can show tree canopy
   on, off, or we can show some shade on and off. You know. Flooding condition
   on and off." And earlier, on the shadow: "I wonder whether we could make that
   more direct in the legend … shadow on off kind of option."

   They render in the shadow cell because that is where "what am I looking at"
   already lives, and they reuse .sera-btn's geometry outright — one control
   language for the whole cell, and no second set of sizes to keep in step.
   The only difference is the swatch: a filled dot in the LAYER'S OWN colour
   when on, a hollow ring when off, so the chip says which layer as well as
   whether. Colours come from the registry row, not from this file.

   EVERY CHIP IS A REMOTE CONTROL, not a state store: it clicks the dock's own
   checkbox and then re-reads it. renderLayersPanel stays the single source of
   layer state and the two can never drift. See sysChipHTML() in cambridge.js. */
.sera-cell .sera-sys { display: inline-flex; flex-wrap: wrap; gap: 4px;
  align-items: center; }
.sys-chip { display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--n-200); border-radius: var(--r-pill);
  background: var(--n-000); cursor: pointer;
  font-family: 'Manrope', sans-serif; font-size: 0.7rem; font-weight: 700;
  color: var(--n-700); padding: 4px 9px; white-space: nowrap; }
.sys-chip:hover { background: var(--n-050); color: var(--n-900); }
.sys-chip[aria-pressed="true"] { background: var(--c-chrome); border-color: var(--c-chrome);
  color: var(--n-000); }
.sys-chip:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 2px; }
/* The dot carries the layer's colour via an inline custom property, so no
   layer palette is duplicated here. Off = the ring only, which reads as
   "this is the thing, and it is not on". */
.sys-sw { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  border: 1.5px solid var(--sys-c, currentColor); background: transparent; }
.sys-chip[aria-pressed="true"] .sys-sw { background: var(--sys-c, currentColor);
  border-color: rgba(var(--white-rgb),0.85); }

/* ── COMPACT INCOME STRIP ───────────────────────────────────────────────────
   The seven-bracket table and its method note used to run ~300px inside a
   strip already competing with the map, against a standing note that the map
   is too small. They now fold into a disclosure. Nothing is deleted: the
   detail is one click away and the provenance chip rides on the summary, so
   the source is visible whether the block is open or shut. */
.isp-lead {
  font-size: 0.72rem; line-height: 1.35; color: var(--n-700, #3f4b52);
  margin: 7px 0 2px;
}
.isp-lead b { color: var(--n-900, #23282c); font-variant-numeric: tabular-nums; }

.isp-more { margin-top: 6px; }
.isp-more > summary {
  cursor: pointer; list-style: none;
  font-size: 0.6rem; letter-spacing: .04em; text-transform: uppercase;
  font-weight: 700; color: var(--n-600, #5b7282);
  padding: 4px 0; display: flex; align-items: center; gap: 6px;
}
.isp-more > summary::-webkit-details-marker { display: none; }
/* the caret is drawn rather than inherited, so the row height is predictable */
.isp-more > summary::before {
  content: ''; width: 0; height: 0; flex: 0 0 auto;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
  transition: transform .12s ease;
}
.isp-more[open] > summary::before { transform: rotate(90deg); }
/* Was var(--c-teal-deep, #1c5c84): a teal token carrying a Spinellblau
   fallback that could never fire. The file had been drifting to the house
   value on its own; this just makes it honest. */
.isp-more > summary:hover { color: var(--c-chrome); }
.isp-more > summary:focus-visible {
  outline: 2px solid var(--c-chrome); outline-offset: 2px; border-radius: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .isp-more > summary::before { transition: none; }
}

/* ── BOTTOM BAND COMPACTION (V3) ────────────────────────────────────────────
   Measured 2026-08-08: the band's height was never set by its content. It was
   two literals — max-height 208px, and 256px on shadow topics — and the strip's
   content was 456-471px, so ~260px sat behind a scrollbar. At 1440 the band was
   37.5% of the map and 50.5% on the climate topic, against a standing client
   note that the map is too small.
   Nothing here deletes data. The analytic cards move to the Neighborhood
   analytics tab (#es-more now opens it), the seven brackets stay in the
   disclosure, and every provenance string is relocated rather than dropped. */
/* `> .explore-strip` became `> .map-footer > .explore-strip` on 2026-08-13 —
   the strip moved one level down, into the band's right cell, under the shadow
   bar. A cap that no longer selects anything is worse than no cap: it reads as
   protection that is not there. */
.content-row .map-col > .theme-legend,
.content-row .map-col > .map-footer > .explore-strip { max-height: 112px; }
.content-row .map-col:has(> .theme-legend .sera-cell) > .theme-legend,
.content-row .map-col:has(> .theme-legend .sera-cell) > .map-footer > .explore-strip { max-height: 176px; }

/* STACKED, the legend gives some of that cap back. Below 1100px the legend and
   the footer are two full-width rows, so the band is their SUM rather than the
   larger of the two, and the shadow topic's 176px legend would put the band at
   176 + 8 + 153 = 337. 132 puts it at 293. The legend is the cell that scrolls
   and it has the full column width here, so 132px shows roughly the same number
   of swatch rows a 176px cap showed at 470px wide.
   (Placed here, after the caps above, because it has to out-order them: same
   specificity, and the 176 rule would otherwise win on source order.) */
@media (max-width: 1100px) {
  .content-row .map-col:has(> .theme-legend .sera-cell) > .theme-legend { max-height: 132px; }
}

/* Header line: selection, dimension control, actions. Then the lead beneath.
   The old 160px | 1fr | auto grid squeezed the charts to 300px, which stacked
   five cards vertically with 88px of gap. */
.es-main {
  grid-template-columns: minmax(0, 1fr) auto auto;
  grid-template-areas: "sel dims acts" "lead lead lead";
  align-items: center; gap: 5px 12px; padding: 6px 14px 7px;
}
.es-sel { grid-area: sel; flex-direction: row; align-items: baseline; gap: 7px;
  min-width: 0; overflow: hidden; }
.es-sel b { font-size: 0.78rem; }
.es-dims { grid-area: dims; display: inline-flex; align-items: center; gap: 6px; }
.es-acts { grid-area: acts; display: inline-flex; align-items: center; gap: 10px; }
.es-lead { grid-area: lead; }
/* The cards live in the analytics tab now; the node stays so the renderer and
   any future inline view keep working. */
.es-main > .es-charts { display: none; }

.es-dimlab { font-size: 0.69rem; color: var(--n-600, #586b7d); }
.es-dimsel {
  font: 500 0.72rem/1.2 'Manrope', sans-serif; color: var(--n-800, #2f3b45);
  background: var(--n-000, #fff); border: 1px solid var(--n-200, #d7dee3);
  border-radius: var(--r-control, 4px); padding: 3px 6px;
}
.es-dimsel:focus-visible { outline: 2px solid var(--c-chrome); outline-offset: 2px; }

/* One line of figures. Each item carries its own label and city comparison, so
   the row stays readable when it wraps. */
.es-lead { display: flex; flex-wrap: wrap; gap: 4px 18px; align-items: baseline; }
.esl { display: inline-flex; align-items: baseline; gap: 5px; font-size: 0.72rem; }
.esl i { font-style: normal; color: var(--n-600, #586b7d); }
.esl b { font-size: 0.84rem; color: var(--n-900, #1f272d); font-variant-numeric: tabular-nums; }
.esl u { text-decoration: none; font-size: 0.69rem; color: var(--n-600, #586b7d); }

/* Legend: the +N chip rides the group-name line instead of taking its own.
   :not(.sera-cell) matters — this block is appended after css:2408, where the
   shadow cell sets its own flex layout but inherits `display` from .tl-group.
   Without the exclusion, `display:grid` here silently rebuilds that cell and
   the era toggle plus the sun-altitude readout lose their row. */
.tl-group:not(.sera-cell) {
  display: grid; grid-template-columns: auto auto;
  grid-template-areas: "name chip" "rows rows";
  justify-content: start; align-items: center; gap: 1px 8px;
}
.tl-group:not(.sera-cell) > .tl-gname { grid-area: name; }
.tl-group:not(.sera-cell) > .tl-chip { grid-area: chip; margin-top: 0; padding: 1px 7px; }
.tl-group:not(.sera-cell) > .tl-rows { grid-area: rows; grid-template-rows: repeat(2, auto); gap: 1px 14px; }
.tl-groups { gap: 4px 14px; }

/* The relocated provenance, now above the 11px floor and at 4.89:1 rather
   than the 2.26:1 it had per-group. */
.tl-prov {
  flex: 1 1 100%; margin: 3px 0 0;
  font-size: 0.69rem; line-height: 1.35; font-style: italic;
  color: var(--n-600, #586b7d);
}
.tl-prov b { font-style: normal; font-weight: 700; color: var(--n-700, #46596b); }

/* Type floors. These were already failing both the 11px caption floor and the
   4.5:1 contrast floor at 9.28-9.92px on --n-400 (2.54:1). Fixed, not traded. */
.es-c h5, .isp-src, .es-rank small, .es-key {
  font-size: 0.69rem; color: var(--n-600, #586b7d);
}

/* cp-open was neutralised by `.es-foot .cp-open` (css:2107). That row no longer
   exists, so the same reset rides its new home — without this the chip returns
   to floating over the canvas, which is what docking it fixed. */
.es-acts .cp-open { position: static; inset: auto; z-index: auto; }
.es-acts .cp-open[hidden] { display: none; }
.es-acts .es-more, .es-acts .cp-open { white-space: nowrap; }

/* ── NEIGHBORHOOD TAB (#cp-neighborhood) ────────────────────────────────────
   Rocker 2026-08-13: "what about if we could click parcel information and next
   to is another tab and we would do neighborhood information", with the
   constraint stated in the same breath — "the map stays exactly how it is. You
   toggle on or off. It's like an Excel spreadsheet." The panel is a fixed
   336px, so a tab costs the map nothing; this block only re-flows the strip's
   own cards into one column, it authors no new type, colour or radius.

   The cards' markup is unchanged — .es-c, .es-vs, .es-stack, .es-key,
   .es-donut, .isp-* — because it is literally esCharts() and
   incomeDetailHTML() output. Same four income display classes, same seven ACS
   brackets, same VARIABLE_COLORS.income the choropleth reads. */
.nb-sel { display: flex; flex-direction: column; gap: 1px; margin-bottom: 8px; }
.nb-sel b { font-size: 0.86rem; color: var(--n-900); }
.nb-dims { margin-bottom: 10px; }
.nb-lead:not(:empty) { margin-bottom: 10px; }
/* One column at 336px: the strip's row of five cards would otherwise stack as
   five 300px-wide items with 22px gaps and read as a gap-first list. */
.nb-charts { flex-direction: column; gap: 14px; }
.nb-charts .es-c { min-width: 0; width: 100%; }
.nb-charts .es-c-wide { flex: 1 1 auto; min-width: 0; max-width: none; }
/* The seven brackets go two-up in the wide strip; a 336px panel is a one-up
   column, so the disclosure stops being a squeeze of two clipped labels. */
.nb-charts .es-c-wide .isp-rows { grid-template-columns: 1fr; }
.nb-charts .es-key { max-width: none; }
/* The line clamp existed to stop a 3-line method note owning the bottom band.
   There is no band pressure here — this pane scrolls — so the note runs full. */
.nb-charts .es-c-wide .isp-note { -webkit-line-clamp: none; overflow: visible; }

/* ============================================================================
   CANVAS CONTROLS — north arrow + graphic scale
   ============================================================================
   The only two things permitted on the map (Rocker's standing exception:
   "a classic map has a title, and has a scale and where N is"). Everything
   is drawn from existing tokens, so both invert correctly under
   :root[data-theme="dark"] without a single dark-mode override below.
   ========================================================================== */

/* NO HARD PLATE. A solid white disc on a light basemap reads as a chip
   someone dropped on the map; the reference sketches are line drawings sitting
   directly on the sheet. So the ground is a translucent wash of the surface
   token — enough to hold the glyph over dark parcels, water or a satellite
   tile, not enough to look like a control. color-mix keeps it token-derived,
   so it inverts with the theme and adds no raw hex. */
.cam-north {
  background: color-mix(in srgb, var(--n-000) 72%, transparent);
  backdrop-filter: blur(2px);
  border-radius: 999px;
  box-shadow: none;
  padding: 0;
  width: 44px;
  height: 44px;
}
.cam-north-svg { width: 44px; height: 44px; display: block; }
/* Rotation happens about the RING's centre, not the SVG box — the N sits
   above the circle, so a box-centre origin would swing the letter in an arc. */
.cam-north-rot { transform-origin: 22px 24px; }
.cam-north-ring   { fill: none; stroke: var(--n-700); stroke-width: 1.1; }
.cam-north-needle   { fill: var(--n-900); }   /* solid half = north */
.cam-north-needle-s { fill: var(--n-900); }
.cam-north-tail     { fill: none; stroke: var(--n-700); stroke-width: 1; }
.cam-north-n {
  fill: var(--n-900);
  font: 700 8.5px/1 'Manrope', sans-serif;
  letter-spacing: 0.06em;
}

.cam-scale {
  background: color-mix(in srgb, var(--n-000) 72%, transparent);
  backdrop-filter: blur(2px);
  border-radius: var(--r-control, 6px);
  box-shadow: none;
  padding: 4px 8px 3px;
  margin-bottom: 6px;
}
.cam-scale-svg { display: block; overflow: visible; }
/* The checkerboard. `is-fill` alternates; the rest are hollow with the same
   stroke, which is what makes the segments countable rather than a gradient. */
.cam-scale-seg {
  fill: var(--n-000);
  stroke: var(--n-900);
  stroke-width: 0.9;
}
.cam-scale-seg.is-fill { fill: var(--n-900); }
.cam-scale-lab {
  fill: var(--n-900);
  font: 600 8.5px/1 'Manrope', sans-serif;
}
.cam-scale-unit {
  fill: var(--n-700);
  font: 600 8.5px/1 'Manrope', sans-serif;
}

/* On a translucent ground the glyph carries its own legibility. A paint-order
   halo is what a cartographer would use — it survives a dark parcel, water and
   a satellite tile without a box, and costs nothing when the ground is pale. */
.cam-scale-lab,
.cam-scale-unit,
.cam-north-n {
  paint-order: stroke fill;
  stroke: var(--n-000);
  stroke-width: 2.2px;
  stroke-linejoin: round;
}
