/* ============================================================
   MAP UI LAYOUT PRINCIPLES — foundational rules
   ============================================================

   The map has two classes of floating UI; they coexist by
   following a few fixed rules. If you're adding a new panel,
   button, or viewer that floats over the map, pick the class
   it belongs to and follow the rules for that class.

   1. CONTEXT surfaces — persistent, represent user state.
          * #details-sidebar (selection panel, right edge)
          * #mapillary-container.floating-viewer (Street View)
      These own their space. Users open them deliberately and
      expect them to stay put. They do NOT shift or collapse
      when transient UI appears.

   2. CONTROL surfaces — transient, user opens → picks → closes.
          * #filter-panel
          * #layers-panel
          * #boundaries-popover
      These anchor to the top-right. They:
        (a) auto-close each other (one at a time), via the
            'floating-panel-open' CustomEvent.
        (b) dismiss on outside click and Escape.
        (c) never occlude a context surface — they shift left
            by --sidebar-width when body.sidebar-visible, so
            the selection panel is always visible beneath them.

   3. CONTEXT surfaces yield to CONTROL surfaces on the same
      edge. The Street View PiP sits bottom-right; if a control
      panel opens, SV shifts further left by --control-panel-
      offset so the user's current pick remains visible. When
      the control panel closes, SV slides back.

   The CSS variables that implement these rules:
        --sidebar-width          width of the selection sidebar
        --control-panel-offset   width + gap of the currently-
                                 open control panel (0px when
                                 no panel is open)

   Transitions on 'right' are deliberate: a sudden snap looks
   like a bug, a smooth slide reads as intentional reflow.
   ============================================================ */

/* ============================================================
   DESIGN TOKENS — foundational primitives
   ============================================================
   Rules:
   * Components reference tokens, not raw values. If you're
     about to type '4px' or '#fff' in a panel style, look for
     a token first. Adding a new one is cheap; drifting is
     expensive.
   * Tokens are grouped by axis: spacing, radius, shadow,
     motion, z-index, color. Adding a new axis = new group.
   * Dark-mode tokens override semantic colors under
     body.theme-dark. Never theme individual components
     directly — always theme via token.
   * Layout primitives (--sidebar-width, --control-panel-
     offset) are set dynamically by JS; they live here for
     documentation even though their values change at runtime.
   ============================================================ */
body {
    /* Layout primitives — set/overridden at runtime by JS.
       Keep in sync with the literal 380px in index.html
       #details-sidebar { width } — the variable here drives
       every offset that anchors against the sidebar's left
       edge (icon rail, top-right popovers, floating header). */
    --sidebar-width: 380px;
    --control-panel-offset: 0px;
    /* Horizontal space the right-edge icon rail occupies,
       including the gap between it and anything to its left.
       Panels anchored top-right use this so they don't cover
       the rail. Rail itself is 36px + 4px padding on each
       side = 44px, plus a 20px gap = 64px. */
    --rail-gutter: 64px;

    /* Spacing scale (4px base grid) */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-8: 32px;
    --space-10: 48px;

    /* ── Measure (the readable line) ─────────────────────────────
       A MEASURE IS COUNTED IN CHARACTERS, AND `ch` IS NOT ONE.
       `1ch` is the advance of "0", which in this stack's UI sans
       measures 0.606em; the average character in the site's own
       running prose measures 0.477em (sampled over every caption,
       footnote and deck on /sales and /foreclosure). So
       `max-width: 72ch` never set a 72-character line — it set a
       ~92-character one, and 109 on the longest lines. That was
       the almanac pages' cap, and it was simultaneously too long
       to read comfortably and too short to close a 1106px figure
       card, which is what made a full-width figure look unfinished.

       Both tokens below are stated in `em` at ~0.48em per
       character, so the count in the comment is the count on the
       screen. They are different KINDS of value on purpose:

       --measure-deck  a MAX-WIDTH, for a block that must never
                       fragment — one sentence read once, under a
                       title. Splitting a sentence into columns
                       costs the reader an upward jump mid-clause.
       --measure-col   a COLUMN-WIDTH, i.e. a MINIMUM, for a text
                       BAND: the plural method text under a
                       full-width figure. The realized column
                       stretches to fill the card (70–75 characters
                       in the 1180px almanac, 67–74 on a tablet),
                       and the column count falls out of the
                       container rather than being written down —
                       a half-width panel and a phone get one
                       column, exactly as before.

       `--measure-deck` is 45em rather than a rounder number on
       purpose: that is 72ch to within 3px at every size the almanac
       sets, so the blocks that were already at 72ch do not move, and
       the only ones that change are the three that had drifted to
       78ch, 80ch and 90ch — the widest measures on either page, and
       the ones that were running at 115, 126 and 134 characters. */
    --measure-deck: 45em;   /* ≈ 94 characters */
    --measure-col: 28em;    /* ≈ 58 characters, floor */

    /* Radius */
    --radius-sm: 3px;
    --radius-md: 4px;
    --radius-lg: 8px;
    --radius-xl: 12px;
    --radius-pill: 999px;

    /* Shadow */
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15);
    --shadow-lg: 0 6px 18px rgba(0, 0, 0, 0.22);

    /* Motion */
    --motion-fast: 0.15s;
    --motion-base: 0.25s;

    /* ─── Z-index stack ─────────────────────────────────────────
       New floating UI should pick the *named layer* it belongs
       to (`var(--z-pip)`, `var(--z-rail)`, `var(--z-floating)`,
       `var(--z-panel)`, etc.) rather than reach for a raw number.
       The named middle tiers (map-control / pip / pip-chrome / rail
       / overlay) were added to cover the crowded 800–960 band; the
       raw numbers still in that band are the migration target — swap
       them for the named tier (order-preserving) as you touch each
       surface. The raw-number list below documents current usage.

         1            — map canvas baseline
         2, 5         — in-panel internal stacking (chart overlays,
                        viewport-locked badges); scoped to their
                        parent stacking context, harmless globally
         920          — viewing-mode chip (floats just above map)
         940–954      — controls / popovers / legend
                        (`--z-floating` is in this band)
         1000         — sidebars + floating viewer (`--z-panel`)
         1001         — floating header above sidebars (`--z-header`)
         1002         — overlay UI that sits above an open sidebar
                        (e.g. the parcel address-search expanded state)
         1100         — survey-app floating panels that must escape
                        #main-container's stacking-context trap (the
                        annotation panel, multi-year recap card, etc.).
                        Documented in
                        ~/.claude/projects/-Users-dexterslu-Projects-det-land/
                        memory/det-land-floating-overlay-positioning.md
         2000         — modals (`--z-modal`)
         11000        — survey-app full-viewport modals that need to sit
                        above EVERY other surface (annotation
                        preview-confirm modal). Use sparingly.
         9999, 10000  — tooltips + emergency-top elements; reserve
                        the 9000+ range for "must always be visible"
                        affordances like aria-tooltips
       ──────────────────────────────────────────────────────── */
    --z-map: 1;
    --z-map-control: 100;    /* on-map controls: legend, attribution, AERIAL, boundaries control */
    --z-pip: 800;            /* small floating map / mapillary PiP surface */
    --z-pip-chrome: 860;     /* swap / minimize buttons that sit on the PiP */
    --z-rail: 900;           /* icon-rail + mapillary toolbar */
    --z-floating: 950;
    --z-panel: 1000;
    --z-header: 1001;
    --z-overlay: 1500;       /* full-surface overlays (e.g. historic gallery) */
    --z-modal: 2000;
    /* Survey-app additions (multi-year + annotation work). */
    --z-survey-floating-overlay: 1100;
    --z-dropdown: 1200;      /* form autocomplete / select popovers */
    --z-offline-banner: 1600; /* connectivity strip — above panels/overlays, below modals */
    --z-survey-modal-fullscreen: 11000;

    /* In-panel stacking — scoped to a scrolling panel's own stacking
       context, NOT the global overlay ladder above. The sticky topic
       strip rides above the sticky module mini-headers, which ride
       above module content (parcel-surface redesign). Tokenized so
       panel chrome never reaches for a raw number. */
    --z-in-panel-head: 3;
    --z-in-panel-strip: 5;

    /* Semantic colors — light theme.
       Values chosen to match the most common hex already in
       use so the tokens pass is visually invisible. */
    --color-bg-elevated: #fff;
    --color-bg-muted: #f8f9fa;
    --color-bg-hover: #f5f5f5;
    --color-border: #eee;
    --color-border-strong: #dee2e6;
    --color-fg-primary: #333;
    --color-fg-secondary: #495057;
    /* THE QUIET END OF THE RAMP IS STILL TEXT — it has to be readable.
       Audited with axe over a rendered /sales: 59 `color-contrast` failures in
       light, and all but a handful were these two tokens on a white or #f8f9fa
       surface. `#6c757d` measured 4.41 on the muted surface (every "Figure N ·"
       eyebrow, every `.meta`, every chart footnote) and `#868e96` measured 3.32
       (the dot-plot's ENTITY NAMES, the ladder's descriptive copy, the map's
       freshness stamp, the §4.5 vintage tags). Both are below the 4.5:1 spec
       for normal-size text, so the page's explanatory layer — the layer that
       says what a number means — was its least legible.
       Darkened one notch each, keeping the three-step hierarchy visible
       (8.18 / 5.75 / 5.02 on white) and clearing 4.5 on white, #f8f9fa and
       #f7f8fa alike. Tokens, not components: every page inherits the fix. */
    --color-fg-muted: #5f676e;
    --color-accent: #0080ff;
    --color-accent-strong: #0066cc;
    /* The accent as TEXT on a light surface. The brand accent is a fill
       colour — #0080ff reads 3.79:1 on white, so a wordmark, an active
       scroll-spy pill or an inline link set in it fails at normal size. Fills
       and borders keep `--color-accent`; anything a reader has to READ takes
       this. In dark the accent is already a bright blue on a dark surface,
       so there the two coincide. */
    --color-accent-text: #0066cc;
    /* Aliases referenced (with hardcoded fallbacks) across the app — defining
       them here so they actually flip in dark mode instead of falling back. */
    --color-text: #333;
    --color-fg-tertiary: #697077;
    --color-bg-surface: #f7f8fa;
    --color-bg-subtle: #f6f7f9;
    --color-bg-elevated-2: #eef2f6;
    --color-border-muted: #f0f0f0;
    --color-link: #2563eb;
    --color-on-accent: #fff;

    /* Semantic status palette. Added so new components don't have to
     * hand-pick hex values + remember to add a dark-theme override
     * for each — they reference the token and inherit dark-mode for
     * free. Used by the multi-year recap (success-ish "no change"),
     * the no-imagery / image-quality / image-quality flag UI
     * (warning), and the destructive remove/cancel actions (danger).
     *
     * Hex values chosen to match the most common hardcoded colors
     * already in the codebase (Tailwind-ish palette: green-600,
     * amber-100/600, red-700) so the migration is visually
     * invisible. */
    --color-success-bg:     #ecfdf5;
    --color-success-border: #a7f3d0;
    --color-success-fg:     #065f46;
    --color-success-accent: #16a34a;

    --color-warning-bg:     #fef3c7;
    --color-warning-border: #fde68a;
    --color-warning-fg:     #78350f;
    --color-warning-accent: #d97706;

    --color-danger-bg:      #fef2f2;
    --color-danger-border:  #fecaca;
    --color-danger-fg:      #991b1b;
    --color-danger-accent:  #b91c1c;

    /* Accent palette — one shared vocabulary for color-coding both the
       boundary dashboards (per chapter) and the parcel panel (per topic).
       Color-named so any surface can pick an accent; semantic surfaces map
       their sections onto these (see .bp-chapter--* and the panel topic
       accents). Dark-theme variants are lightened for contrast below. */
    --accent-indigo: #5c6bc0;
    --accent-cyan:   #0891b2;
    --accent-green:  #16a34a;
    --accent-amber:  #d97706;
    --accent-violet: #7c3aed;

    /* Imagery scrim — chrome composed OVER photography (the street-view
       hero's bottom gradient + the in-frame date pill). A photo is not
       themed, so these are theme-INVARIANT by design: no dark overrides. */
    --scrim-strong: rgba(13, 15, 18, 0.78);
    --scrim-soft: rgba(13, 15, 18, 0.42);
    --color-on-scrim: rgba(255, 255, 255, 0.92);
    --color-on-scrim-bg: rgba(255, 255, 255, 0.16);
    --color-on-scrim-bg-hover: rgba(255, 255, 255, 0.26);
    --color-on-scrim-border: rgba(255, 255, 255, 0.35);
    /* Media frame — the surface a photo/pano loads INTO (street-view
       hero, dashboard imagery pane). Dark in both themes, like the
       scrims above: an empty photo frame must read as "photo loading",
       never as a white hole in the panel (the 2026-08-07 street-view
       smoothness invariant: from tap to settled pano, the hero never
       paints white or black). Slightly above pure black so a loading
       frame reads designed, not dead. */
    --color-media-frame: #14171b;
}

/* Dark-theme overrides. Only semantic colors change; spacing,
   radius, motion, and z-index are theme-agnostic. */
body.theme-dark {
    --color-bg-elevated: #1e1e1e;
    --color-bg-muted: #2a2a2a;
    --color-bg-hover: #2a2a2a;
    --color-border: #333;
    --color-border-strong: #444;
    --color-fg-primary: #e8eaed;
    --color-fg-secondary: #c9ccd1;
    --color-fg-muted: #9aa0a6;
    --color-accent: #4dabf7;
    --color-accent-strong: #339af0;
    /* On a dark surface the accent is already the high-contrast one (6.73:1
       on #1e1e1e), so accent-as-text needs no separate value here. */
    --color-accent-text: #4dabf7;
    /* Dark values for the alias tokens defined in :root. */
    --color-text: #e8eaed;
    /* 4.53 on #1e1e1e passed only on the darkest surface it is used on — the
       page footer's #232323 took the same token to 4.26 (axe caught it on
       /sales' disclaimer). One notch lighter clears 4.5 on every dark
       surface in the system. */
    --color-fg-tertiary: #8d9399;
    --color-bg-surface: #232323;
    --color-bg-subtle: #242424;
    --color-bg-elevated-2: #262626;
    --color-border-muted: #2c2c2c;
    --color-link: #8ab4f8;
    --color-on-accent: #fff;

    /* Dark variants of the semantic status palette. */
    --color-success-bg:     #14271f;
    --color-success-border: #2c4234;
    --color-success-fg:     #c8e6d3;
    --color-success-accent: #4ade80;

    --color-warning-bg:     #2d250e;
    --color-warning-border: #4a3a16;
    --color-warning-fg:     #fcd34d;
    --color-warning-accent: #fbbf24;

    --color-danger-bg:      #2a1414;
    --color-danger-border:  #4d2020;
    --color-danger-fg:      #fca5a5;
    --color-danger-accent:  #f87171;

    /* Lightened accents for contrast on dark surfaces. */
    --accent-indigo: #818cf8;
    --accent-cyan:   #22d3ee;
    --accent-green:  #4ade80;
    --accent-amber:  #fbbf24;
    --accent-violet: #a78bfa;
}

/* Base typography improvements */
body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    line-height: 1.5;
    color: #333;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
    margin-top: 0;
    margin-bottom: 0.5rem;
    font-weight: 600;
    line-height: 1.2;
}

/* Header improvements */
#floating-header {
    padding: 5px 10px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    display: flex;
    align-items: center;
    gap: 10px;
    border-radius: 4px;
    z-index: 1001;
}

#home-button {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.5px;
    color: #0080ff;
}

/* Search improvements */
#search-container {
    flex-grow: 1;
    max-width: 500px;
    margin: 0 20px;
}

/* Button improvements */
.header-button {
    padding: 8px 12px;
    border-radius: var(--radius-md);
    transition: all 0.2s;
    font-weight: 500;
}

.header-button:hover {
    background-color: rgba(0, 128, 255, 0.1);
}

.header-button.active {
    background-color: rgba(0, 128, 255, 0.15);
    color: var(--color-accent);
}

/* ---- Icon rail --------------------------------------------------
   The right-edge floating menu that hosts Filters / Layers /
   Boundaries / Settings. A vertical stack of icon-only buttons
   in a rounded card, same visual family as the panels it
   opens. See the layout-principles block at the top of this
   stylesheet — the rail is a CONTROL cluster, so it shifts
   left along with its panels when the selection sidebar is
   visible. */
#icon-rail {
    position: absolute;
    top: 10px;
    right: 10px;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-1);
    background: var(--color-bg-elevated);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    z-index: var(--z-panel);
    transition: right var(--motion-base) ease;
}
/* Shift the rail left when the selection sidebar is visible
   so it stays 10px off the sidebar's inner edge. Note this is
   sidebar + 10px, NOT sidebar + rail-gutter: the rail's whole
   job is to live INSIDE the rail-gutter area (64px wide).
   Panels anchor at sidebar + rail-gutter, so the rail needs to
   be at the smaller offset (sidebar + 10) to sit between the
   panel and the sidebar. If we used rail-gutter here, the rail
   would overlap the panel. */
body.sidebar-visible #icon-rail {
    right: calc(var(--sidebar-width, 300px) + 10px);
}
/* On mobile the sidebar slides up from the bottom, not in from
   the right, so the rail stays pinned to the right edge. */
@media (max-width: 768px), (max-height: 600px) {
    body.sidebar-visible #icon-rail {
        right: 10px;
    }
}
/* SHORT VIEWPORT: THE RAIL TURNS A HEIGHT PROBLEM INTO A WIDTH ONE.
   The rail is a content-sized column — eight 36px buttons plus gaps, ~316px
   tall, starting at y=54 with the account chip present. On a landscape phone
   the sheet takes the bottom 40%, so the map's visible band is 180px and the
   rail runs 136px past it. It cannot paint over the sheet either: the rail
   lives inside #main-container, which is `z-index: 100` and therefore a
   stacking context, while #details-sidebar is that container's SIBLING at
   900 — the rail's own z-index is scoped inside a layer that loses. Four of
   eight controls (boundaries, locate-me, settings, legend) simply did not
   exist if you turned the phone sideways.
   Same move the ownership map's control card already makes when its sheet
   opens, along the other axis: bounded by the height it actually has, the
   column wraps into as many columns as it needs. Nothing is hidden, nothing
   is capped, every button keeps its full 36px face. */
@media (max-height: 600px) {
    body.sidebar-visible #icon-rail {
        /* GRID, NOT `flex-flow: column wrap`. Both engines wrap the items,
           but WebKit — which is the phone this gets reported from — does not
           grow the CONTAINER to fit the extra column, so the second column
           renders outside the card with no background behind it. Grid sizes
           the box from its tracks in both engines.
           Four rows is the worst case that fits: the shortest phone-class
           viewport leaves the rail 168px (390 tall, minus the sheet's 40%,
           minus the 54px account-chip offset and a 12px margin) and four
           36px buttons plus gaps and padding come to 160. An added ninth
           button starts a third column rather than growing the height. */
        display: grid;
        grid-auto-flow: column;
        grid-template-rows: repeat(4, auto);
    }
}

/* Icon-only button used by the rail. 36x36 with centered icon;
   no text. Tooltip via the title attribute, accessible name
   via the aria-label attribute. */
.icon-rail-button {
    position: relative;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: var(--radius-md);
    color: var(--color-fg-secondary);
    cursor: pointer;
    padding: 0;
    transition: background-color var(--motion-fast), color var(--motion-fast);
}
.icon-rail-button:hover {
    background-color: var(--color-bg-hover);
    color: var(--color-fg-primary);
}
.icon-rail-button:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}
.icon-rail-button.active {
    background-color: rgba(0, 128, 255, 0.12);
    color: var(--color-accent);
}
/* Active-state indicator for boundaries (the only rail button
   with an external "is anything selected?" signal). A tiny dot
   in the top-right corner of the icon. */
.icon-rail-button.has-active::after {
    content: '';
    position: absolute;
    top: 6px;
    right: 6px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-accent);
    box-shadow: 0 0 0 2px var(--color-bg-elevated);
}
/* Filter badge sits inside the button. It used to float to the
   right of the "Filters" label; in icon-only mode it overlays
   the top-right of the funnel icon. */
#filter-button .filter-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 14px;
    height: 14px;
    min-width: 14px;
    padding: 0;
    font-size: 9px;
    line-height: 14px;
    margin: 0;
    border-radius: var(--radius-pill);
    box-shadow: 0 0 0 2px var(--color-bg-elevated);
}

/* ---- Settings popover ------------------------------------------
   Anchored top-right like the other rail popovers, offset so it
   doesn't cover the rail itself. Same visual language as
   #layers-panel / #boundaries-popover. */
#settings-popover {
    position: absolute;
    top: 70px;
    right: var(--rail-gutter);
    width: 280px;
    max-height: calc(100vh - 90px);
    background: var(--color-bg-elevated);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    z-index: var(--z-panel);
    display: none;
    overflow: hidden;
    flex-direction: column;
    transition: right var(--motion-base) ease;
}
#settings-popover[style*="display: block"] {
    display: flex !important;
}
body.sidebar-visible #settings-popover {
    right: calc(var(--sidebar-width, 300px) + var(--rail-gutter));
}
.settings-popover-header {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}
.settings-popover-header h3 {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
}
.settings-popover-content {
    padding: var(--space-2) 0;
    overflow-y: auto;
}
.settings-section {
    padding: var(--space-2) var(--space-4) var(--space-3);
}
.settings-section + .settings-section {
    border-top: 1px solid var(--color-border);
}
.settings-section-title {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted);
    margin: var(--space-1) 0 var(--space-2);
}
.settings-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px var(--space-1);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background-color var(--motion-fast);
}
.settings-row:hover {
    background-color: var(--color-bg-hover);
}
.settings-row-label {
    font-size: 13.5px;
    color: var(--color-fg-primary);
}
.settings-row input[type="checkbox"] {
    margin: 0;
    cursor: pointer;
}
.settings-action-btn {
    width: 100%;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-border-strong);
    background: var(--color-bg-muted);
    color: var(--color-fg-primary);
    font-size: 13px;
    font-family: inherit;
    cursor: pointer;
    transition: background-color var(--motion-fast);
}
.settings-action-btn:hover {
    background: var(--color-bg-hover);
}
.settings-copy-status {
    margin-top: 6px;
    font-size: 12px;
    color: var(--color-fg-muted);
    min-height: 16px;
}
.settings-copy-status--error {
    color: #b42318;
}
/* Dark-mode: rail + settings inherit from semantic tokens; only
   the shadow needs an opacity bump for visibility on dark bg. */
body.theme-dark #icon-rail,
body.theme-dark #settings-popover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
/* Mobile: settings panel stays right-anchored (same as desktop),
   just narrower so it fits the smaller screen. */
@media (max-width: 768px), (max-height: 600px) {
    #settings-popover {
        width: min(280px, calc(100vw - 74px)) !important;
    }
}

/* Panel improvements. (#details-sidebar's shadow + radius live on its base
   rule in survey.css — it owns its own layout, so it's not stacked in here
   anymore.) */
#filter-panel {
    box-shadow: var(--shadow-md);
    border-radius: var(--radius-md);
}

.filter-header {
    padding: 12px 16px;
    border-bottom: 1px solid #eee;
}

.filter-header h3 {
    font-size: 16px;
    font-weight: 600;
    margin: 0;
}

.filter-content {
    padding: 16px;
}

.filter-section-title {
    font-size: 15px;
    font-weight: 600;
    margin-bottom: 12px;
    color: #333;
}

.filter-option {
    margin-bottom: 12px;
}

.filter-button {
    padding: 8px 16px;
    border-radius: 4px;
    font-weight: 500;
    transition: all 0.2s;
}

.filter-apply {
    background-color: #0080ff;
    color: white;
}

.filter-apply:hover {
    background-color: #0066cc;
}

.filter-close, .filter-reset {
    background-color: #f5f5f5;
    color: #333;
}

.filter-close:hover, .filter-reset:hover {
    background-color: #e0e0e0;
}

/* ---- Layers panel (floating, anchored to the layers button) --- */
/* Shares the visual language of #filter-panel but is narrower —
   the panel is a compact legend + checkbox list, not a form, so
   it doesn't need the filter panel's width. Positioned against
   the map container so it follows the map when the sidebar
   opens and pushes the map edge inward. */
#layers-panel {
    position: absolute;
    top: 70px;
    right: var(--rail-gutter);
    width: 320px;
    max-height: calc(100vh - 90px);
    background: var(--color-bg-elevated);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    z-index: var(--z-panel);
    display: none;
    overflow: hidden;
    flex-direction: column;
    transition: right var(--motion-base) ease;
}
#layers-panel[style*="display: block"] {
    display: flex !important;
}
/* CONTROL surface — shift left when the selection sidebar is
   open so it never occludes the user's current selection.
   See the layout-principles block at the top of this file. */
body.sidebar-visible #layers-panel {
    right: calc(var(--sidebar-width, 300px) + var(--rail-gutter));
}

/* Small X close button used in Layers, Labels, and similar panel headers. */
.panel-close-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px;
    color: var(--color-fg-muted);
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.panel-close-btn:hover {
    background: var(--color-border);
    color: var(--color-text);
}
body.theme-dark .panel-close-btn:hover {
    background: rgba(255,255,255,0.1);
}

.layers-panel-header {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-shrink: 0;
}
.layers-panel-header h3 {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
}

.layers-panel-content {
    padding: var(--space-2) 0;
    overflow-y: auto;
    flex: 1 1 auto;
}

.layers-section {
    padding: var(--space-2) var(--space-4) var(--space-3);
}
.layers-section + .layers-section {
    border-top: 1px solid var(--color-border);
}
.layers-section-title {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted);
    margin: var(--space-1) 0 10px 0;
}

/* Row: [swatch | label-group | checkbox] (the main <label>) plus a
   small "Show in Legend" eye-toggle button on the right. The main
   target stays a <label for=…> so clicking anywhere in it flips the
   visibility checkbox; the legend-toggle is a separate <button> so
   its clicks don't also toggle visibility. */
.layer-row {
    display: flex;
    align-items: center;
    gap: 4px;
    border-radius: var(--radius-md);
}
.layer-row-main {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px var(--space-1);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background-color var(--motion-fast);
    min-width: 0;
}
.layer-row-main:hover {
    background-color: var(--color-bg-hover);
}
.layer-row-main input[type="checkbox"] {
    margin: 0;
    flex-shrink: 0;
    cursor: pointer;
}
.layer-legend-toggle {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-fg-muted);
    transition: background-color var(--motion-fast), color var(--motion-fast);
    padding: 0;
}
.layer-legend-toggle:hover {
    background-color: var(--color-bg-hover);
    color: var(--color-fg-primary);
}
/* When the layer is included in the Legend, the eye reads as
   "active" — slightly stronger color to distinguish from the
   slashed-eye (excluded) state. */
.layer-legend-toggle[aria-pressed="true"] {
    color: var(--color-fg-primary);
}
.layer-legend-toggle[aria-pressed="false"] {
    color: var(--color-fg-muted);
    opacity: 0.6;
}
.layer-label-group {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.layer-label {
    font-size: 13.5px;
    font-weight: 500;
    color: var(--color-fg-primary);
}
/* Lifetime (∞) badge — inline chip that signals "this layer
   ignores the master date-range picker." Appears next to the
   label in the Layers panel when datePeriod !== 'lifetime'. */
.layer-lifetime-badge {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--color-fg-muted);
    background: var(--color-bg-subtle, rgba(0,0,0,0.05));
    border: 1px solid var(--color-border);
    border-radius: 8px;
    cursor: help;
    vertical-align: middle;
}
body.theme-dark .layer-lifetime-badge {
    background: rgba(255,255,255,0.06);
    color: #999;
}
.layer-description {
    font-size: 11.5px;
    color: var(--color-fg-muted);
    line-height: 1.3;
}

/* Swatches come in three shapes matching legend.shape values. */
.layer-swatch {
    flex-shrink: 0;
    width: 14px;
    height: 14px;
    border: 1px solid rgba(0, 0, 0, 0.15);
    box-sizing: border-box;
}
.layer-swatch--fill {
    border-radius: var(--radius-sm);
}
.layer-swatch--line {
    height: 3px;
    border-radius: 2px;
    margin: 0 0 0 0;
    align-self: center;
}
.layer-swatch--dot {
    border-radius: 50%;
}

.layers-empty {
    padding: var(--space-5) var(--space-4);
    color: var(--color-fg-muted);
    font-size: 13px;
    text-align: center;
}

/* ---- Layers panel — accordion sub-groups ---------------------- */
/* Data Overlays uses collapsible sections so 39 layers aren't a
   flat wall of checkboxes. Each subgroup has a clickable header
   with a chevron and an active-count badge. */

/* Widen the panel from 320px to 360px for more label room. */
#layers-panel {
    width: 360px;
}

.subgroup-accordion {
    border-top: 1px solid var(--color-border);
    margin-top: 4px;
}
.subgroup-accordion:first-child {
    border-top: none;
    margin-top: 0;
}
.subgroup-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 7px var(--space-1);
    border: none;
    background: transparent;
    cursor: pointer;
    text-align: left;
    border-radius: var(--radius-sm);
    font-size: 13px;
    font-weight: 500;
    color: var(--color-fg-primary);
    transition: background-color var(--motion-fast);
    gap: 6px;
}
.subgroup-header:hover {
    background-color: var(--color-bg-hover);
}
.subgroup-header-label {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.subgroup-chevron {
    flex-shrink: 0;
    font-size: 10px;
    color: var(--color-fg-muted);
}
.subgroup-active-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1;
    border-radius: 9px;
    background-color: var(--color-accent, #0080ff);
    color: #fff;
}
.subgroup-body {
    padding-bottom: 4px;
}

/* ---- Labels panel -------------------------------------------- */
/* Dedicated panel for on-map parcel attribute labels (address,
   parcel ID, owner). Shares layout with #layers-panel. */

#labels-panel {
    position: absolute;
    top: 70px;
    right: var(--rail-gutter);
    width: 300px;
    max-height: calc(100vh - 90px);
    background: var(--color-bg-elevated);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    z-index: var(--z-panel);
    display: none;
    overflow: hidden;
    flex-direction: column;
    transition: right var(--motion-base) ease;
}
#labels-panel[style*="display: block"] {
    display: flex !important;
}
body.sidebar-visible #labels-panel {
    right: calc(var(--sidebar-width, 300px) + var(--rail-gutter));
}

.labels-panel-hint {
    margin: 0;
    padding: var(--space-2) var(--space-4) 0;
    font-size: 12px;
    color: var(--color-fg-muted);
}

.label-layer-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px var(--space-4);
    border-radius: var(--radius-md);
    transition: background-color var(--motion-fast);
}
.label-layer-row:hover {
    background-color: var(--color-bg-hover);
}
.label-layer-info {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.label-layer-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-fg-primary);
}
.label-layer-desc {
    font-size: 11px;
    color: var(--color-fg-muted);
    line-height: 1.3;
}
.label-eye-toggle {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border);
    background: transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-fg-muted);
    transition: background-color var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);
    padding: 0;
}
.label-eye-toggle:hover {
    background-color: var(--color-bg-hover);
    color: var(--color-fg-primary);
}
.label-eye-toggle--on {
    background-color: var(--color-accent, #0080ff);
    border-color: var(--color-accent, #0080ff);
    color: #fff;
}
.label-eye-toggle--on:hover {
    background-color: var(--color-accent, #0080ff);
    color: #fff;
    filter: brightness(1.1);
}

/* Dark mode overrides for new components */
body.theme-dark #labels-panel {
    background: var(--color-bg-elevated);
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
}
body.theme-dark .label-eye-toggle {
    border-color: var(--color-border);
}
body.theme-dark .subgroup-active-badge {
    background-color: var(--color-accent, #3399ff);
}

/* ---- MapLibre attribution / logo -------------------------------- */
/* Force the attribution into the compact "i" affordance regardless
 * of viewport width. mapConfig sets compact:true at the JS level
 * but MapLibre re-evaluates on resize; on a wide canvas it can
 * forget the option and expand the inner text. Hiding the inner
 * by default + revealing it only when the user taps the i
 * (.maplibregl-compact-show class) makes the behavior viewport-
 * independent. The `i` button itself stays tiny and subtle.
 */
.maplibregl-ctrl-attrib {
    font-size: 9px !important;
    background: rgba(255, 255, 255, 0.6) !important;
    padding: 1px 4px !important;
}
.maplibregl-ctrl-attrib a {
    font-size: 9px !important;
}
/* The attribution is a <details> element with a <summary> (the
 * "i" button) and the inner credits div. Hide every child except
 * the summary — defeats both MapLibre's own `.maplibregl-ctrl-
 * attrib-inner { display: block }` rule AND any case where the
 * <details> opens with `open=true` (which would otherwise reveal
 * children regardless of CSS). When the user taps the i and
 * MapLibre adds .maplibregl-compact-show (and/or sets open=true),
 * reveal the inner. */
.maplibregl-ctrl-attrib > *:not(.maplibregl-ctrl-attrib-button):not(summary) {
    display: none !important;
}
.maplibregl-ctrl-attrib .maplibregl-ctrl-attrib-inner {
    display: none !important;
}
.maplibregl-ctrl-attrib.maplibregl-compact-show .maplibregl-ctrl-attrib-inner,
.maplibregl-ctrl-attrib[open] .maplibregl-ctrl-attrib-inner {
    display: block !important;
}
@media (max-width: 768px), (max-height: 600px) {
    .maplibregl-ctrl-attrib {
        font-size: 8px !important;
    }
}

/* ---- Legend (read-only summary) ------------------------------- */
/* Desktop: floats bottom-left of the map as a card. Starts
   expanded; collapse shrinks it to an icon-only puck in the
   same corner. The collapsed button and the expanded body
   share the same #legend element — we toggle visibility of the
   two via .legend--expanded / .legend--collapsed classes.
   Mobile: the card is CSS-hidden and a rail button (created in
   js/ui/legend.js) opens the same DOM as a full-width sheet. */
.legend {
    position: absolute;
    left: 20px;
    bottom: 20px;
    z-index: var(--z-control);
    font-family: inherit;
    color: var(--color-fg-primary);
}
.legend .legend-expanded-body {
    display: none;
    width: 280px;
    max-height: calc(50vh);
    background: var(--color-bg-elevated);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    overflow: hidden;
    flex-direction: column;
}
.legend .legend-collapsed-button {
    display: none;
    width: 40px;
    height: 40px;
    align-items: center;
    justify-content: center;
    background: var(--color-bg-elevated);
    color: var(--color-fg-primary);
    border: none;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    padding: 0;
    transition: background-color var(--motion-fast);
}
.legend .legend-collapsed-button:hover {
    background-color: var(--color-bg-hover);
}
.legend.legend--expanded .legend-expanded-body {
    display: flex;
}
.legend.legend--collapsed .legend-collapsed-button {
    display: inline-flex;
}

.legend-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 10px 5px;
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}
.legend-title {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted);
}
.legend-collapse-button {
    width: 20px;
    height: 20px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-fg-muted);
    font-size: 15px;
    line-height: 1;
    padding: 0;
    transition: background-color var(--motion-fast);
}
.legend-collapse-button:hover {
    background-color: var(--color-bg-hover);
    color: var(--color-fg-primary);
}
.legend-collapse-button:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 1px;
}

.legend-content {
    overflow-y: auto;
    padding: 2px 0 4px;
    flex: 1 1 auto;
}
.legend-section {
    padding: 5px 10px 3px;
}
.legend-section + .legend-section {
    border-top: 1px solid var(--color-border);
}
.legend-section-title {
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted);
    margin: 0 0 3px 0;
}

.legend-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 2px 0;
}
.legend-swatch {
    flex-shrink: 0;
    width: 12px;
    height: 12px;
    border-radius: var(--radius-sm);
    border: 1px solid rgba(0, 0, 0, 0.15);
    box-sizing: border-box;
}
.legend-row-label {
    font-size: 13px;
    color: var(--color-fg-primary);
    line-height: 1.3;
}

/* Multi-swatch legend entry (e.g. foreclosure status colors): a layer
   label heading with the per-status swatch rows indented beneath it. */
.legend-multi {
    padding: 2px 0;
}
.legend-multi-label {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-fg-primary);
    line-height: 1.3;
    margin-bottom: 2px;
}
.legend-multi-rows {
    padding-left: 10px;
}
.legend-multi-rows .legend-row-label {
    font-size: 12px;
    color: var(--color-fg-muted);
}

.legend-filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.legend-filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--color-bg-hover);
    font-size: 11.5px;
    line-height: 1.4;
}
.legend-filter-chip-category {
    font-weight: 600;
    color: var(--color-fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 10px;
}
.legend-filter-chip-label {
    color: var(--color-fg-primary);
}

.legend-empty {
    padding: var(--space-4) var(--space-3);
    font-size: 12px;
    color: var(--color-fg-muted);
    text-align: center;
    line-height: 1.4;
}

/* Rail button is only an entry point on mobile. Desktop hides
   it because the Legend is already visible bottom-left. */
.legend-rail-button { display: inline-flex; }
@media (min-width: 769px) and (min-height: 601px) {
    .legend-rail-button { display: none; }
}

/* Mobile layout: legend anchored to the bottom-left corner,
   same as desktop but flush with the edges. */
@media (max-width: 768px), (max-height: 600px) {
    .legend {
        position: fixed;
        left: 0;
        bottom: 0;
        z-index: var(--z-panel);
    }
    .legend .legend-collapsed-button {
        display: none !important;
    }
    .legend .legend-expanded-body {
        display: none !important;
        width: min(260px, 85vw);
        max-height: 50vh;
        border-radius: 0 var(--radius-md) 0 0;
    }
    .legend.legend--mobile-open .legend-expanded-body {
        display: flex !important;
    }
}

/* ---- Boundaries popover (radio-style lens picker) ------------- */
/* Smaller than the layers panel — a boundaries popover is a
   short list of radio rows, not a groupable legend. Anchored
   against the map container same as the layers panel so it
   tracks the visible map edge when the sidebar opens. */

/* Compact dropdown anchored near the Boundaries button.
   `top` is set dynamically by JS. `right` tracks the rail edge. */
#boundaries-popover {
    position: absolute;
    top: 70px;
    right: var(--rail-gutter);
    width: 260px;
    max-height: calc(100vh - 90px);
    background: var(--color-bg-elevated);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    z-index: var(--z-panel);
    display: none;
    flex-direction: column;
    overflow: hidden;
    transition: right var(--motion-base) ease;
}
#boundaries-popover[style*="display: flex"] {
    display: flex !important;
}
/* Shifts left when the selection sidebar is open. */
body.sidebar-visible #boundaries-popover {
    right: calc(var(--sidebar-width, 300px) + var(--rail-gutter));
}

.boundaries-popover-content {
    padding: 4px 0 6px;
    overflow-y: auto;
    flex: 1 1 auto;
}

/* Dropdown-style boundary options — full-width buttons, no radio. */
.boundary-option {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 8px 12px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 13px;
    font-weight: 400;
    color: var(--color-fg-primary);
    text-align: left;
    transition: background-color var(--motion-fast);
}
.boundary-option:hover {
    background-color: var(--color-bg-hover);
}
.boundary-option--active {
    background-color: #0080ff;
    color: #fff;
    font-weight: 500;
}
.boundary-option--active:hover {
    background-color: #006be6;
}
.boundary-option--active .boundary-option-check {
    color: #fff;
}
.boundary-option--active .boundary-option-swatch {
    border-color: rgba(255, 255, 255, 0.4);
}
.boundary-option--disabled {
    cursor: not-allowed;
    opacity: 0.45;
}
.boundary-option--disabled:hover {
    background-color: transparent;
}
.boundary-option-label {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 6px;
}
.boundary-option-check {
    flex-shrink: 0;
    color: #0080ff;
}
.boundary-coming-soon {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #868e96;
    background: #f1f3f5;
    padding: 2px 6px;
    border-radius: 10px;
}

/* Boundary swatches are always fill-style squares — boundaries
   are polygon regions, no need for line/dot variants. */
.boundary-option-swatch {
    flex-shrink: 0;
    width: 14px;
    height: 14px;
    border-radius: var(--radius-sm);
    border: 1px solid rgba(0, 0, 0, 0.15);
    box-sizing: border-box;
}

.boundaries-empty {
    padding: var(--space-5) var(--space-4);
    color: var(--color-fg-muted);
    font-size: 13px;
    text-align: center;
}

/* ---- New selection panel (chip row + body + close) ------------ */
/* --sidebar-width lives in the tokens block at the top of this
   stylesheet. */

#panel-close {
    background: #e9ecef;
    color: #495057;
}
#panel-close:hover { background: #dee2e6; color: #212529; }

#panel-chip-row:empty { display: none; }

/* Chip row */
.chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    height: 26px;
    border-radius: 13px;
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1;
    border: 1px solid transparent;
    background: #f1f3f5;
    color: #495057;
    cursor: default;
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.chip--current {
    background: #0080ff;
    color: #fff;
    border-color: #0080ff;
    cursor: default;
    font-weight: 600;
}
.chip--current[disabled] { opacity: 1; }
.chip--neighborhood { background: #e7f5ff; color: #1971c2; border-color: #c5e0fa; }
.chip--ward         { background: #fff4e6; color: #d9480f; border-color: #ffd8a8; }
.chip--council      { background: #f3f0ff; color: #5f3dc4; border-color: #d0bfff; }
/* Census tract — palette matches the lens fill in census-boundaries.js (#1c7ed6) */
.chip--tract        { background: #e7f5ff; color: #1c7ed6; border-color: #a5d8ff; }
.chip--block        { background: #f4fce3; color: #5c940d; border-color: #d8f5a2; }
/* Phase-5b breadcrumb chips */
.chip--block_group  { background: #f4fce3; color: #5c940d; border-color: #d8f5a2; }
/* DFD battalion (red) and CVI area (green) — match the lens fills
   in administrative-boundaries.js so chip color = polygon color. */
.chip--dfd_battalion { background: #ffe3e3; color: #c92a2a; border-color: #ffc9c9; }
.chip--cvi_area      { background: #ebfbee; color: #2f9e44; border-color: #b2f2bb; }
.chip--nez_district  { background: #fff4e6; color: #e67700; border-color: #ffd8a8; }
/* Zoning chip — neutral tan to match the styled-fill layer's
   default residential-yellow palette. */
.chip--zoning       { background: #f8f9fa; color: #5c4a30; border-color: #ced4da; }
.chip--clickable { cursor: pointer; }
.chip--clickable:hover {
    background: #0080ff;
    color: #fff;
    border-color: #0080ff;
}
.chip--info { cursor: default; opacity: 0.95; }

/* Selection heading + sections in panel body */
.selection-heading {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-bottom: 14px;
}
.selection-heading-text { flex: 1; min-width: 0; }
.selection-heading-text h3 {
    margin: 0 0 4px 0;
    font-size: 17px;
    font-weight: 600;
}
.parcel-address {
    font-size: 13px;
    color: #555;
    line-height: 1.35;
}
/* Caption row under the H1 in selection headings — holds the
   parcel id (when the address is the title) and the zoning tag
   inline. Light, low-contrast typography so it reads as
   secondary metadata rather than competing with the title. */
.parcel-caption {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 2px;
    font-size: 12px;
    color: #6c757d;
    line-height: 1.4;
}
.parcel-caption-sep {
    color: #adb5bd;
}
/* Zoning tag — inline in the caption row, next to the parcel
   id. Margin-left removed since the caption row uses gap. */
.parcel-zoning-tag {
    display: inline-block;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 600;
    color: #3e2723;
    background: #efebe9;
    border: 1px solid #d7ccc8;
    border-radius: 10px;
    vertical-align: middle;
    white-space: nowrap;
}
.parcel-nez-tag {
    display: inline-block;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 600;
    color: #1b5e20;
    background: #e8f5e9;
    border: 1px solid #a5d6a7;
    border-radius: 10px;
    vertical-align: middle;
    white-space: nowrap;
}
/* Confidence row — Occupancy + Ownership tiers from parcel_confidence,
   rendered at the top of the Ownership group. Two chips side-by-side
   with a small ⓘ help-link to /methodology#confidence. */
.confidence-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px 0;
    flex-wrap: wrap;
}
.confidence-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    font-size: 12px;
    line-height: 1.2;
    border-radius: 12px;
    border: 1px solid transparent;
    white-space: nowrap;
}
.confidence-chip__label { color: #495057; font-weight: 500; }
.confidence-chip__tier { font-weight: 700; }
.confidence-chip__score {
    font-variant-numeric: tabular-nums;
    opacity: 0.7;
    font-weight: 500;
}
.confidence-chip--high   { background: #e8f5e9; border-color: #a5d6a7; color: #1b5e20; }
.confidence-chip--medium { background: #fff8e1; border-color: #ffe082; color: #8d6e00; }
.confidence-chip--low    { background: #ffebee; border-color: #ef9a9a; color: #b71c1c; }
.confidence-chip--public { background: #e3f2fd; border-color: #90caf9; color: #0d47a1; }
.confidence-chip--na     { background: #f5f5f5; border-color: #e0e0e0; color: #6c757d; }
.confidence-row__methodology {
    display: inline-flex;
    align-items: center;
    color: #9aa0a6;
    text-decoration: none;
    padding: 2px;
    margin-left: auto;
}
.confidence-row__methodology:hover { color: #5c6bc0; }

/* Per-parcel "Nearest services" card — accessibility vector from
   det.pipelines parcel_accessibility table. Renders inside its own
   collapsible renderGroup section in the parcel panel + expanded dashboard. */
.accessibility-card {
    display: flex;
    flex-direction: column;
    gap: 16px;
}
.accessibility-group {
    display: flex;
    flex-direction: column;
}
.accessibility-group__title {
    margin: 0 0 6px 0;
    padding-bottom: 6px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6c757d;
    border-bottom: 1px solid rgba(0,0,0,0.06);
}
.accessibility-row {
    display: grid;
    grid-template-columns: minmax(96px, 0.7fr) minmax(0, 1.6fr) auto;
    align-items: baseline;
    gap: 12px;
    padding: 7px 4px;
    font-size: 13px;
}
.accessibility-row + .accessibility-row {
    border-top: 1px solid rgba(0,0,0,0.04);
}
.accessibility-row--clickable {
    cursor: pointer;
    border-radius: 4px;
    margin: 0 -6px;
    padding-left: 6px;
    padding-right: 6px;
    transition: background-color 80ms ease;
}
.accessibility-row--clickable:hover { background-color: rgba(92,107,192,0.08); }
.accessibility-row--clickable:focus-visible {
    outline: 2px solid #5c6bc0;
    outline-offset: -2px;
    background-color: rgba(92,107,192,0.08);
}
.accessibility-row__cat {
    color: #6c757d;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.3;
}
.accessibility-row__poi {
    color: #212529;
    font-weight: 500;
    line-height: 1.3;
    overflow-wrap: anywhere;
}
.accessibility-row__time {
    color: #495057;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    font-weight: 600;
}
.accessibility-footnote {
    margin-top: 4px;
    font-size: 11px;
    color: #9aa0a6;
    font-style: italic;
}
body.theme-dark .accessibility-group__title { color: #9aa0a6; }
body.theme-dark .accessibility-row { border-bottom-color: rgba(255,255,255,0.06); }
body.theme-dark .accessibility-row__cat { color: #adb5bd; }
body.theme-dark .accessibility-row__poi { color: #e9ecef; }
body.theme-dark .accessibility-row__time { color: #adb5bd; }
body.theme-dark .accessibility-footnote { color: #6c757d; }
body.theme-dark .accessibility-row--clickable:hover { background-color: rgba(130,177,255,0.10); }
body.theme-dark .accessibility-row--clickable:focus-visible {
    outline-color: #82b1ff;
    background-color: rgba(130,177,255,0.10);
}

/* DOM-based marker for the focused POI on the map. Uses maplibregl.Marker
   rather than a circle layer because property-filter circle layers were
   silently failing to render on our MapLibre version. The dot anchors at
   the POI lat/lng; the label floats above it. */
.accessibility-poi-marker {
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: none;
    transform: translateY(4px);
}
.accessibility-poi-marker__label {
    margin-top: 4px;
    padding: 3px 8px;
    font-size: 12px;
    font-weight: 600;
    color: #ad1457;
    background: rgba(255,255,255,0.92);
    border: 1px solid #e91e63;
    border-radius: 12px;
    white-space: nowrap;
    box-shadow: 0 2px 4px rgba(0,0,0,0.15);
}
.accessibility-poi-marker__dot {
    width: 18px;
    height: 18px;
    background: #e91e63;
    border: 3px solid #ffffff;
    border-radius: 50%;
    box-shadow: 0 2px 6px rgba(0,0,0,0.35);
}
body.theme-dark .accessibility-poi-marker__label {
    color: #f8bbd0;
    background: rgba(33,33,33,0.92);
    border-color: #f06292;
}

/* Boundary accessibility card — top-5 POIs per category, ranked by both
   popularity (parcels served) and proximity (avg drive minutes). Rows
   are clickable: dispatches det:boundary-poi-focus → marker + faded
   line bundle + parcel highlight on the map. */
.boundary-accessibility-card {
    display: flex;
    flex-direction: column;
    gap: 14px;
}
.boundary-accessibility-card__intro {
    font-size: 12px;
    color: #6c757d;
    line-height: 1.5;
}
/* Boundary dashboard CTA — the prominent "open the full report page for this
   boundary" link pinned at the top of the boundary panel body, above the
   stats. Kept high so it's visible in the mobile sheet peek without
   expanding; icon + title + one-line contents preview, full-width with a
   comfortable tap target on phones. */
.boundary-dashboard-cta {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: 1px solid rgba(92, 107, 192, 0.35);
    border-radius: var(--radius-lg);
    background: rgba(92, 107, 192, 0.08);
    text-decoration: none;
    transition: background 120ms ease, border-color 120ms ease;
}
.boundary-dashboard-cta:hover {
    background: rgba(92, 107, 192, 0.14);
    border-color: rgba(92, 107, 192, 0.55);
}
.boundary-dashboard-cta__icon {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    color: #5c6bc0;
}
.boundary-dashboard-cta__icon svg { display: block; width: 100%; height: 100%; }
.boundary-dashboard-cta__text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.boundary-dashboard-cta__title {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.25;
    color: #4453b8;
}
.boundary-dashboard-cta__desc {
    font-size: 12px;
    line-height: 1.35;
    color: #6a7180;
}
.boundary-dashboard-cta__arrow {
    flex: 0 0 auto;
    align-self: center;
    font-size: 15px;
    line-height: 1;
    color: #5c6bc0;
}
body.theme-dark .boundary-dashboard-cta {
    border-color: rgba(140, 158, 255, 0.30);
    background: rgba(140, 158, 255, 0.10);
}
body.theme-dark .boundary-dashboard-cta:hover {
    background: rgba(140, 158, 255, 0.16);
    border-color: rgba(140, 158, 255, 0.50);
}
body.theme-dark .boundary-dashboard-cta__icon,
body.theme-dark .boundary-dashboard-cta__arrow,
body.theme-dark .boundary-dashboard-cta__title { color: #aeb8ff; }
body.theme-dark .boundary-dashboard-cta__desc { color: #9aa0b5; }
.boundary-poi-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.boundary-poi-group__title {
    margin: 0;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6c757d;
}
.boundary-poi-card {
    border: 1px solid rgba(0,0,0,0.05);
    border-radius: 8px;
    padding: 10px 12px;
    background: rgba(0,0,0,0.015);
    background: rgba(0,0,0,0.015);
}
.boundary-poi-card__title {
    font-size: 12px;
    font-weight: 600;
    color: #495057;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-bottom: 6px;
    border-bottom: 1px solid rgba(0,0,0,0.06);
    margin-bottom: 4px;
}
.boundary-poi-row {
    display: grid;
    grid-template-columns: 22px minmax(0,1fr) auto;
    align-items: center;
    gap: 10px;
    padding: 8px 4px;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 80ms ease;
}
.boundary-poi-row + .boundary-poi-row {
    border-top: 1px solid rgba(0,0,0,0.04);
}
.boundary-poi-row:hover { background: rgba(92,107,192,0.07); }
.boundary-poi-row:focus-visible {
    outline: 2px solid #5c6bc0;
    outline-offset: -2px;
    background: rgba(92,107,192,0.07);
}
.boundary-poi-row__rank {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(92,107,192,0.12);
    color: #5c6bc0;
    font-weight: 700;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.boundary-poi-row__main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.boundary-poi-row__name {
    color: #212529;
    font-weight: 500;
    font-size: 13px;
    line-height: 1.3;
    overflow-wrap: anywhere;
}
.boundary-poi-row__served {
    color: #868e96;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.boundary-poi-row__prox-note {
    color: #00897b;
    font-weight: 600;
    margin-left: 6px;
}
.boundary-poi-row__time {
    color: #495057;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    white-space: nowrap;
}
body.theme-dark .boundary-accessibility-card__intro,
body.theme-dark .boundary-poi-group__title,
body.theme-dark .boundary-poi-card__title { color: #9aa0a6; }
body.theme-dark .boundary-poi-card {
    border-color: rgba(255,255,255,0.06);
    background: rgba(255,255,255,0.02);
}
body.theme-dark .boundary-poi-row__name { color: #e9ecef; }
body.theme-dark .boundary-poi-row__served { color: #868e96; }
body.theme-dark .boundary-poi-row__time { color: #c9d1d9; }
body.theme-dark .boundary-poi-row__rank {
    background: rgba(130,177,255,0.16);
    color: #82b1ff;
}
body.theme-dark .boundary-poi-row + .boundary-poi-row {
    border-top-color: rgba(255,255,255,0.05);
}
body.theme-dark .boundary-poi-card__title {
    border-bottom-color: rgba(255,255,255,0.05);
}
body.theme-dark .boundary-poi-row:hover { background: rgba(130,177,255,0.10); }

/* ── Boundary profile page (/neighborhood, /council, /ward, /nez, /ecf) ─── */
/* New "bp-" design system: chapters + sticky TOC + skeleton loaders.
   Old .boundary-profile-* / .boundary-hero-grid / .boundary-tile-*
   classes still drive the inside of each section's renderer — left
   intact. */
.boundary-profile-page {
    max-width: 1100px;
    margin: 0 auto;
    padding: 32px 24px 72px;
    color: #212529;
}
/* Rail layout widens the page so the content column stays roomy beside the
   190px sticky TOC rail. */
.boundary-profile-page--rail { max-width: 1300px; }
@media (max-width: 768px), (max-height: 600px) {
    .boundary-profile-page { padding: 20px 16px 56px; }
}

/* Header */
.bp-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 380px);
    gap: 28px;
    align-items: end;
    margin-bottom: 24px;
    padding-bottom: 24px;
    border-bottom: 1px solid rgba(0,0,0,0.08);
}
.bp-header__text { min-width: 0; }
.bp-eyebrow {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #6c757d;
    margin-bottom: 6px;
}
.bp-title {
    margin: 0 0 10px 0;
    font-size: 36px;
    font-weight: 700;
    line-height: 1.1;
    color: #1a1d20;
    letter-spacing: -0.015em;
}
.bp-crumbs a {
    color: #5c6bc0;
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
}
.bp-crumbs a:hover { text-decoration: underline; }
/* Ownership-profile additions to the bp header: a one-line subtitle + a badge
   row (public / dissolved / nonprofit), reusing the existing .ownership-badge. */
.bp-subtitle {
    margin: 0 0 10px 0;
    font-size: 15px;
    color: var(--color-fg-secondary, #5c6670);
    line-height: 1.5;
    max-width: 60ch;
}
.bp-badges {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 10px;
}
.bp-badges .ownership-badge { margin-top: 0; }

/* Cohort chips below the title — which other boundaries this one
   sits inside. Click → that boundary's dashboard. */
.bp-cohort {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 12px 0 14px;
}
.bp-cohort__chip {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 999px;
    background: rgba(92,107,192,0.08);
    color: #5c6bc0;
    text-decoration: none;
    font-size: 12px;
    transition: background-color 80ms ease;
}
.bp-cohort__chip:hover { background: rgba(92,107,192,0.18); }
.bp-cohort__chip-type {
    color: #6c757d;
    font-weight: 500;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.bp-cohort__chip-id {
    color: #495057;
    font-weight: 600;
}
body.theme-dark .bp-cohort__chip {
    background: rgba(130,177,255,0.10);
    color: #82b1ff;
}
body.theme-dark .bp-cohort__chip:hover { background: rgba(130,177,255,0.20); }
body.theme-dark .bp-cohort__chip-type { color: #9aa0a6; }
body.theme-dark .bp-cohort__chip-id { color: #e9ecef; }
.bp-inset {
    width: 100%;
    height: 200px;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid rgba(0,0,0,0.08);
    background: #eef0f3;
}
@media (max-width: 768px), (max-height: 600px) {
    .bp-header {
        grid-template-columns: minmax(0, 1fr);
        gap: 16px;
    }
    .bp-title { font-size: 28px; }
    .bp-inset { height: 160px; }
}

/* Sticky in-page TOC */
.bp-toc {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    gap: 4px;
    padding: 10px 0;
    margin-bottom: 8px;
    background: rgba(255,255,255,0.94);
    backdrop-filter: saturate(180%) blur(8px);
    -webkit-backdrop-filter: saturate(180%) blur(8px);
    border-bottom: 1px solid rgba(0,0,0,0.06);
    overflow-x: auto;
    scrollbar-width: thin;
}
.bp-toc__link {
    flex: 0 0 auto;
    padding: 6px 12px;
    font-size: 13px;
    font-weight: 500;
    color: #495057;
    text-decoration: none;
    border-radius: 999px;
    transition: background-color 80ms ease, color 80ms ease;
    white-space: nowrap;
}
.bp-toc__link:hover { background: rgba(92,107,192,0.08); color: #5c6bc0; }
.bp-toc__link--active {
    background: #5c6bc0;
    color: #fff;
}

/* ── Neighborhood comparison (compare mode) ────────────────────────────── */
/* The bar under the header: primary chip + colored removable member chips +
   an "Add neighborhood" picker, and a one-line expectation-setting hint. */
.bp-compare-bar {
    margin: 4px 0 16px;
    padding: 12px 14px;
    background: rgba(92,107,192,0.05);
    border: 1px solid rgba(92,107,192,0.16);
    border-radius: 10px;
}
.bp-compare-bar[hidden] { display: none; }
.cmp-bar__inner {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 10px;
}
.cmp-bar__eyebrow {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #6c757d;
}
.cmp-bar__chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    flex: 1 1 auto;
}
.cmp-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    font-size: 12px;
    font-weight: 600;
    color: #343a40;
    background: #fff;
    border: 1px solid rgba(0,0,0,0.10);
    border-radius: 999px;
    white-space: nowrap;
}
.cmp-chip.is-primary { border-color: rgba(92,107,192,0.4); }
.cmp-chip.is-loading { opacity: 0.6; }
.cmp-chip__dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.cmp-chip__x {
    display: inline-flex; align-items: center; justify-content: center;
    width: 16px; height: 16px; padding: 0;
    font-size: 14px; line-height: 1;
    color: #868e96; background: none; border: none; border-radius: 50%;
    cursor: pointer;
}
.cmp-chip__x:hover { color: #e03131; background: rgba(224,49,49,0.10); }
.cmp-add { display: inline-flex; }
.cmp-add__select {
    padding: 4px 10px;
    font-size: 12px;
    font-weight: 600;
    color: #5c6bc0;
    background: #fff;
    border: 1px dashed rgba(92,107,192,0.5);
    border-radius: 999px;
    cursor: pointer;
}
.cmp-add--full { font-size: 12px; color: #868e96; align-self: center; }
/* Compare | Combine segmented toggle. */
.cmp-mode {
    display: inline-flex; flex: 0 0 auto; margin-left: auto;
    border: 1px solid rgba(92,107,192,0.35); border-radius: 999px;
    overflow: hidden;
}
.cmp-mode__btn {
    padding: 4px 12px; font-size: 12px; font-weight: 600;
    color: #5c6bc0; background: #fff; border: none; cursor: pointer;
}
.cmp-mode__btn.is-active { background: #5c6bc0; color: #fff; }
body.theme-dark .cmp-mode { border-color: rgba(130,140,248,0.4); }
body.theme-dark .cmp-mode__btn { background: #1c1f24; color: #aeb8ff; }
body.theme-dark .cmp-mode__btn.is-active { background: #818cf8; color: #0a0d10; }
.bp-surroundings__map {
    height: 300px; margin-bottom: 14px;
    border: 1px solid var(--color-border); border-radius: 10px; overflow: hidden;
}
@media (max-width: 768px), (max-height: 600px) { .bp-surroundings__map { height: 230px; } }
.bp-surroundings__ctl { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.cmp-bar__hint {
    margin-top: 8px;
    font-size: 12px;
    color: #6c757d;
    line-height: 1.4;
}

/* One metric block = a label (+ citywide value) and one bar per neighborhood.
   Bars are zero-based and share a scale within the metric, so bar length reads
   as magnitude; the dashed tick marks Detroit citywide on rate metrics. */
/* Tufte pass: metrics are borderless small multiples separated by whitespace +
   a single ledger hairline (no card boxes), each a Cleveland dot plot — one dot
   per neighborhood on a thin shared baseline instead of a filled bar. Maximises
   the data-ink ratio; color is reserved for the data dots, text stays in ink. */
.cmp-bars {
    --cmp-surface: #f4f5f7; /* page surface, for the dot's separating ring */
    display: grid;
    /* min(360px, 100%), not a bare 360px: a bare track minimum cannot shrink
       below itself, so in any container narrower than 360px the track stayed
       360 wide and pushed the whole PAGE into horizontal scroll on a phone.
       min() keeps the two-up layout identical at >=360px and collapses to a
       single fitting column below it. */
    grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
    gap: 6px 40px;
}
.cmp-metric {
    padding-top: 11px;
    border-top: 1px solid rgba(0,0,0,0.09);
}
.cmp-metric__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 7px;
}
.cmp-metric__label { font-size: 12.5px; font-weight: 600; color: #343a40; }
/* THE REFERENCE ANCHOR IS THE POINT OF THIS COMPONENT (§4.1), and it was the
   least legible text on the page: #adb5bd on white measures 2.07:1. That is
   "Citywide $109,000" on Figure 5 and "United States −1.9%" on Figure 8 — the
   literal "compared to what?" the whole number-layering standard is built on,
   set fainter than any other label in the figure. It takes the shared muted
   token now, so it can never fall behind the tokens again. */
.cmp-metric__city { font-size: 11px; color: var(--color-fg-muted); white-space: nowrap; }
.cmp-metric__bars { display: flex; flex-direction: column; gap: 2px; }
.cmp-bar {
    display: grid;
    grid-template-columns: minmax(84px, 128px) 1fr minmax(50px, auto);
    align-items: center;
    gap: 12px;
    font-size: 12px;
}
.cmp-bar__name {
    min-width: 0;
    /* the entity's own name, not decoration — on the shared token so it
       tracks the ramp (was a hardcoded #868e96 at 3.32:1) */
    color: var(--color-fg-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cmp-bar.is-primary .cmp-bar__name { font-weight: 600; color: #212529; }
/* Plot band = one hairline baseline (the shared scale), no filled container. */
.cmp-bar__track {
    position: relative;
    height: 16px;
    background: linear-gradient(to bottom,
        transparent calc(50% - 0.5px), rgba(0,0,0,0.11) calc(50% - 0.5px),
        rgba(0,0,0,0.11) calc(50% + 0.5px), transparent calc(50% + 0.5px));
}
/* The value dot, positioned along the baseline. A 2px surface ring lifts it off
   the baseline (and off the citywide tick / a neighbouring dot). */
.cmp-bar__mark {
    position: absolute;
    top: 50%;
    left: 0;
    width: 9px; height: 9px;
    margin-left: -4.5px;
    border-radius: 50%;
    transform: translateY(-50%);
    box-shadow: 0 0 0 2px var(--cmp-surface);
}
.cmp-bar.is-primary .cmp-bar__mark {
    width: 12px; height: 12px;
    margin-left: -6px;
    box-shadow: 0 0 0 2px var(--cmp-surface), 0 0 0 3.5px rgba(92,107,192,0.35);
}
/* Detroit citywide reference — a quiet vertical tick on the shared scale. */
.cmp-bar__city {
    position: absolute;
    top: 2px; bottom: 2px; left: 0;
    width: 0;
    border-left: 1px solid rgba(0,0,0,0.26);
    pointer-events: none;
}
.cmp-bar__val {
    font-variant-numeric: tabular-nums;
    text-align: right;
    color: #495057;
    white-space: nowrap;
}
.cmp-bar.is-primary .cmp-bar__val { font-weight: 700; color: #212529; }
/* Race composition — one stacked part-to-whole bar per neighborhood.
   2px surface gaps between segments (dataviz spacer rule); segments ≥8%
   carry an inline % label; identity always in the legend. */
/* Each composition graphic is a titled figure — ledger hairline + heading
   (mirroring .cmp-metric) with generous vertical rhythm, so consecutive
   bars read as separate figures instead of one cramped stream. */
.cmp-comp {
    margin-bottom: 30px;
    padding-top: 12px;
    border-top: 1px solid rgba(0,0,0,0.09);
}
.cmp-comp__head {
    font-size: 12.5px; font-weight: 600; color: #343a40;
    margin-bottom: 9px;
}
.cmp-comp__legend {
    display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 12px;
    font-size: 12px; color: #495057;
}
.cmp-comp__key { display: inline-flex; align-items: center; gap: 5px; }
.cmp-comp__dot { width: 9px; height: 9px; border-radius: 3px; }
.cmp-comp__row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.cmp-comp__name {
    flex: 0 0 auto; width: 128px; min-width: 0;
    font-size: 12px; color: #868e96;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cmp-comp__row.is-primary .cmp-comp__name { font-weight: 600; color: #212529; }
.cmp-comp__track {
    flex: 1 1 auto; display: flex; gap: 2px;
    height: 22px; border-radius: 5px; overflow: hidden;
}
.cmp-comp--named .cmp-comp__track { height: 18px; }
.cmp-comp__seg {
    display: flex; align-items: center; justify-content: center;
    min-width: 0; border-radius: 2px;
}
.cmp-comp__pct {
    font-size: 11px; font-weight: 600; color: #fff; line-height: 1;
    text-shadow: 0 1px 1px rgba(0,0,0,0.25);
}
.cmp-comp__pct--ink { color: #212529; text-shadow: none; }
/* Detroit reference row — muted so it reads as backdrop, not a peer. */
.cmp-comp__row.is-city .cmp-comp__name { font-style: italic; color: #adb5bd; }
.cmp-comp__row.is-city .cmp-comp__track { opacity: 0.62; }
body.theme-dark .cmp-comp__row.is-city .cmp-comp__name { color: #6c757d; }
.cmp-comp__note { margin: 10px 0 0; font-size: 11.5px; color: #868e96; line-height: 1.45; }
/* Mobile: narrower name column, tighter legend, slightly taller bars for
   touch legibility, extra block separation. */
@media (max-width: 768px), (max-height: 600px) {
    .cmp-comp { margin-bottom: 34px; padding-top: 14px; }
    .cmp-comp__name { width: 92px; font-size: 11.5px; }
    .cmp-comp__legend { gap: 5px 12px; font-size: 11.5px; }
    .cmp-comp__row { gap: 8px; margin-bottom: 9px; }
    .cmp-comp__track { height: 24px; }
    .cmp-comp--named .cmp-comp__track { height: 20px; }
}
body.theme-dark .cmp-comp { border-top-color: rgba(255,255,255,0.10); }
body.theme-dark .cmp-comp__head { color: #f1f3f5; }
body.theme-dark .cmp-comp__legend { color: #c9d1d9; }
body.theme-dark .cmp-comp__name { color: #9aa0a6; }
body.theme-dark .cmp-comp__row.is-primary .cmp-comp__name { color: #f1f3f5; }
body.theme-dark .cmp-comp__note { color: #9aa0a6; }

/* Age pyramid — paired horizontal bars (male left, female right) per 10-yr
   bin, 80+ on top; thin ticks mark Detroit citywide. Small multiples share
   one scale in compare mode. */
.pyr-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 24px 36px;
    margin: 2px 0 6px;
}
.pyr__title { font-size: 12.5px; font-weight: 600; color: #212529; margin-bottom: 6px; }
.pyr__head {
    display: grid; grid-template-columns: 1fr 44px 1fr;
    font-size: 10.5px; letter-spacing: 0.04em; text-transform: uppercase; color: #adb5bd;
    margin-bottom: 3px;
}
.pyr__head span:first-child { text-align: right; }
.pyr__head span:last-child { text-align: left; }
.pyr__row { display: grid; grid-template-columns: 1fr 44px 1fr; align-items: center; height: 17px; }
.pyr__age {
    text-align: center; font-size: 10.5px; color: #868e96;
    font-variant-numeric: tabular-nums;
}
.pyr__side { position: relative; height: 11px; }
.pyr__side--m { display: flex; justify-content: flex-end; }
.pyr__bar { display: inline-block; height: 100%; }
.pyr__bar--m { background: #0891b2; border-radius: 3px 0 0 3px; }
.pyr__bar--f { background: #e11d48; border-radius: 0 3px 3px 0; }
.pyr__val {
    position: absolute; top: 50%; transform: translateY(-50%);
    font-size: 9.5px; color: #adb5bd; font-variant-numeric: tabular-nums;
    pointer-events: none;
}
.pyr__side--m .pyr__val { right: calc(100% + 0px); padding-right: 3px; }
.pyr__side--f .pyr__val { left: calc(100% + 0px); padding-left: 3px; }
.pyr__side--m { padding-left: 30px; }
.pyr__side--f { padding-right: 30px; }
.pyr__side--m .pyr__val { right: auto; left: 0; }
.pyr__side--f .pyr__val { left: auto; right: 0; }
.pyr__tick {
    position: absolute; top: -2px; bottom: -2px; width: 0;
    border-left: 1px solid rgba(0,0,0,0.35);
}
body.theme-dark .pyr__title { color: #f1f3f5; }
body.theme-dark .pyr__age, body.theme-dark .pyr__val { color: #868e96; }
body.theme-dark .pyr__tick { border-left-color: rgba(255,255,255,0.4); }

/* Drive-time comparison — grouped dot plots (Health & safety, Food, …). */
.cmp-access-hint { margin: 0 0 16px; font-size: 12px; color: #6c757d; }
.cmp-access-group { margin-bottom: 20px; }
.cmp-access-group__title {
    margin: 0 0 9px;
    font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: #868e96;
}

@media (max-width: 768px), (max-height: 600px) {
    .cmp-bar { grid-template-columns: minmax(72px, 104px) 1fr minmax(48px, auto); gap: 8px; }
}

body.theme-dark .bp-compare-bar {
    background: rgba(130,140,248,0.08);
    border-color: rgba(130,140,248,0.22);
}
body.theme-dark .cmp-chip {
    color: #e8eaed;
    background: #1c1f24;
    border-color: rgba(255,255,255,0.12);
}
body.theme-dark .cmp-chip.is-primary { border-color: rgba(130,140,248,0.5); }
body.theme-dark .cmp-add__select {
    color: #aeb8ff;
    background: #1c1f24;
    border-color: rgba(130,140,248,0.5);
}
body.theme-dark .cmp-chip__x:hover { color: #ff8787; background: rgba(255,135,135,0.12); }
body.theme-dark .cmp-bar__eyebrow,
body.theme-dark .cmp-bar__hint,
body.theme-dark .cmp-metric__city { color: #9aa0a6; }
body.theme-dark .cmp-metric__label { color: #e8eaed; }
body.theme-dark .cmp-bar__name,
body.theme-dark .cmp-bar__val { color: #c9d1d9; }
body.theme-dark .cmp-bar.is-primary .cmp-bar__name,
body.theme-dark .cmp-bar.is-primary .cmp-bar__val { color: #f1f3f5; }
body.theme-dark .cmp-bars { --cmp-surface: #131619; }
body.theme-dark .cmp-bar__track {
    background: linear-gradient(to bottom,
        transparent calc(50% - 0.5px), rgba(255,255,255,0.14) calc(50% - 0.5px),
        rgba(255,255,255,0.14) calc(50% + 0.5px), transparent calc(50% + 0.5px));
}
body.theme-dark .cmp-bar__city { border-left-color: rgba(255,255,255,0.32); }
body.theme-dark .cmp-metric { border-top-color: rgba(255,255,255,0.10); }
body.theme-dark .cmp-access-hint,
body.theme-dark .cmp-access-group__title { color: #9aa0a6; }
body.theme-dark .cmp-metric__label { color: #f1f3f5; }
body.theme-dark .cmp-bar.is-primary .cmp-bar__mark {
    box-shadow: 0 0 0 2px var(--cmp-surface), 0 0 0 3.5px rgba(130,140,248,0.4);
}

/* Chapter + section structure */
.bp-body {
    display: flex;
    flex-direction: column;
    gap: 40px;
}
.bp-chapter {
    display: flex;
    flex-direction: column;
    gap: 28px;
    scroll-margin-top: 80px;  /* clears sticky TOC on jump-scroll */
}
/* Per-chapter accent. One color per chapter cascades into every tile +
   heading below via --bp-accent, so the dashboard reads as five grouped
   sections instead of one flat wall of gray cards. Colors come from the
   shared --accent-* palette (:root), which carries its own dark-theme
   variants — no per-chapter dark override needed. Access tiles keep their
   own ramp color (--tile-color), so they're untouched. */
.bp-chapter--overview { --bp-accent: var(--accent-indigo); }
.bp-chapter--people   { --bp-accent: var(--accent-cyan); }
.bp-chapter--health   { --bp-accent: var(--accent-green); }
.bp-chapter--housing  { --bp-accent: var(--accent-amber); }
.bp-chapter--activity { --bp-accent: var(--accent-violet); }
/* Profile (owner/operator) chapters — each gets a distinct accent so its
   sub-chapter cards share one color rail that ties back to the chapter title. */
.bp-chapter--portfolio  { --bp-accent: var(--accent-indigo); }
.bp-chapter--history    { --bp-accent: var(--accent-violet); }
.bp-chapter--associates { --bp-accent: var(--accent-cyan); }
.bp-chapter--properties { --bp-accent: var(--accent-amber); }
.bp-chapter--makeup     { --bp-accent: var(--accent-green); }
/* /ownership overview chapters */
.bp-chapter--landscape  { --bp-accent: var(--accent-indigo); }
.bp-chapter--owners     { --bp-accent: var(--accent-cyan); }
.bp-chapter--financing  { --bp-accent: var(--accent-green); }
.bp-chapter--networks   { --bp-accent: var(--accent-violet); }
.bp-chapter--public     { --bp-accent: var(--accent-amber); }
.bp-chapter--distress   { --bp-accent: var(--accent-amber); }
.bp-chapter--speculation { --bp-accent: var(--accent-violet); }
.bp-chapter__title {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
    color: #1a1d20;
    letter-spacing: -0.01em;
    padding-bottom: 10px;
    border-bottom: 2px solid var(--bp-accent, #5c6bc0);
}
.bp-section { scroll-margin-top: 80px; }
.bp-section__title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    margin: 0 0 4px 0;
    padding: 4px 0;
    font-size: 17px;
    font-weight: 600;
    color: #212529;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
}
.bp-section__chevron {
    flex: 0 0 auto;
    width: 18px; height: 18px;
    color: #adb5bd;
    transition: transform 160ms ease;
}
.bp-section.is-collapsed .bp-section__chevron { transform: rotate(-90deg); }
.bp-section.is-collapsed .bp-section__content { display: none; }
/* Progressive disclosure: the summary lead shows only while collapsed; the
   full breakdown shows only when expanded. */
.bp-section__summary { display: none; }
.bp-section.is-collapsed .bp-section__summary { display: block; }
body.theme-dark .bp-section__title { color: #f1f3f5; }

/* Top-destinations collapsed lead: #1 place per category + drive time. */
.bp-dest-summary {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 8px;
}
.bp-dest-summary__cell {
    padding: 8px 11px;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    background: var(--color-bg-elevated);
}
.bp-dest-summary__cat {
    font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: #868e96;
}
.bp-dest-summary__name {
    font-size: 13px; font-weight: 600; color: #212529;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bp-dest-summary__time { font-size: 12px; font-weight: 600; color: #5c6bc0; }
body.theme-dark .bp-dest-summary__cat { color: #9aa0a6; }
body.theme-dark .bp-dest-summary__name { color: #f1f3f5; }
body.theme-dark .bp-dest-summary__time { color: #aeb8ff; }

/* Top-destinations map — topic strip + boundary + numbered proximity pins. */
.bp-destmap { display: flex; flex-direction: column; gap: 10px; }
.bp-destmap__strip {
    display: flex; gap: 6px; overflow-x: auto; padding-bottom: 2px;
    scrollbar-width: thin;
}
.bp-destmap__chip {
    flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 11px; font-size: 12.5px; font-weight: 500;
    color: #495057; background: var(--color-bg-elevated);
    border: 1px solid var(--color-border); border-radius: 999px;
    white-space: nowrap; cursor: pointer;
    transition: background 80ms ease, border-color 80ms ease, color 80ms ease;
}
.bp-destmap__chip:hover { border-color: rgba(92,107,192,0.45); }
.bp-destmap__chip.is-active { background: #5c6bc0; color: #fff; border-color: #5c6bc0; }
.bp-destmap__chip.is-active .bp-destmap__dot { box-shadow: 0 0 0 2px rgba(255,255,255,0.65); }
.bp-destmap__dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.bp-destmap__canvas {
    height: 440px; border-radius: 10px; overflow: hidden;
    border: 1px solid var(--color-border);
}
.bp-destmap__hint { margin: 0; font-size: 12px; color: #6c757d; }
.bp-destmap__pin {
    width: 24px; height: 24px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-size: 12px; font-weight: 700; line-height: 1;
    border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.35);
    cursor: pointer;
}
.bp-destmap__pop { display: flex; flex-direction: column; gap: 2px; font-size: 12px; }
.bp-destmap__pop strong { font-size: 12.5px; color: #212529; }
.bp-destmap__pop span { color: #6c757d; }
@media (max-width: 768px), (max-height: 600px) {
    .bp-destmap__canvas { height: 340px; }
}
body.theme-dark .bp-destmap__hint { color: #9aa0a6; }
body.theme-dark .bp-destmap__pop strong { color: #f1f3f5; }
.bp-section__intro {
    margin: 0 0 16px 0;
    font-size: 13px;
    color: #6c757d;
    line-height: 1.5;
    max-width: 720px;
}

/* Subhead row (used inside the Property sales section for the chart + window pickers) */
.bp-subhead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin: 20px 0 12px;
    flex-wrap: wrap;
}
.bp-subhead:first-child { margin-top: 0; }
.bp-subhead__title {
    margin: 0;
    font-size: 12px;
    font-weight: 600;
    color: #6c757d;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.bp-picker {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: #6c757d;
}
.bp-picker select {
    font: inherit;
    padding: 4px 8px;
    border-radius: 6px;
    border: 1px solid rgba(0,0,0,0.12);
    background: #fff;
    color: #212529;
    cursor: pointer;
}

/* Side-by-side pair grid (demos + foreclosures, etc.) */
.bp-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 12px;
}

/* Source & method <details> — small, muted, sits below each section's
   data. Click to expand; reader gets full source list + how-we-calc text. */
.bp-method {
    margin-top: 14px;
    padding: 8px 12px;
    border-radius: 8px;
    background: rgba(0,0,0,0.025);
    border: 1px solid rgba(0,0,0,0.05);
    font-size: 12px;
    color: #6c757d;
}
.bp-method__summary {
    cursor: pointer;
    color: #495057;
    font-weight: 500;
    user-select: none;
    list-style: none;          /* drop default disclosure triangle */
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.bp-method__summary::-webkit-details-marker { display: none; }
.bp-method__summary::before {
    content: '▸';
    font-size: 9px;
    transition: transform 120ms ease;
    color: #adb5bd;
}
.bp-method[open] .bp-method__summary::before { transform: rotate(90deg); }
.bp-method__body {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid rgba(0,0,0,0.06);
}
.bp-method__text {
    margin: 0 0 8px 0;
    line-height: 1.55;
    color: #495057;
}
.bp-method__sources {
    margin: 0;
    font-size: 11px;
    color: #868e96;
}
.bp-method__sources a {
    color: #5c6bc0;
    text-decoration: none;
}
.bp-method__sources a:hover { text-decoration: underline; }
.bp-method__more {
    margin: 8px 0 0 0;
    font-size: 11px;
}
.bp-method__more a {
    color: #5c6bc0;
    text-decoration: none;
    font-weight: 600;
}
.bp-method__more a:hover { text-decoration: underline; }
body.theme-dark .bp-method__more a { color: #82b1ff; }

/* ── /sources page ─────────────────────────────────────────────────────── */
.sources-page {
    max-width: 820px;
    margin: 0 auto;
    padding: 40px 24px 80px;
    color: #212529;
}
.sources-header { margin-bottom: 36px; }
.sources-eyebrow {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #6c757d;
    margin-bottom: 6px;
}
.sources-title {
    margin: 0 0 14px 0;
    font-size: 34px;
    font-weight: 700;
    letter-spacing: -0.015em;
    color: #1a1d20;
}
.sources-lede {
    margin: 0 0 14px 0;
    font-size: 15px;
    line-height: 1.6;
    color: #495057;
}
.sources-crumbs a {
    color: #5c6bc0;
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
}
.sources-crumbs a:hover { text-decoration: underline; }
.sources-chapter { margin-top: 36px; }
.sources-chapter__title {
    margin: 0 0 16px 0;
    font-size: 20px;
    font-weight: 700;
    color: #1a1d20;
    padding-bottom: 8px;
    border-bottom: 2px solid #5c6bc0;
}
.sources-entry {
    padding: 16px 0;
    border-bottom: 1px solid rgba(0,0,0,0.06);
    scroll-margin-top: 24px;
}
.sources-entry:last-child { border-bottom: none; }
.sources-entry--flash {
    animation: sources-flash 1.6s ease-out;
}
@keyframes sources-flash {
    0%   { background: rgba(92,107,192,0.14); }
    100% { background: transparent; }
}
.sources-entry__title {
    margin: 0 0 8px 0;
    font-size: 16px;
    font-weight: 600;
    color: #212529;
    display: flex;
    align-items: baseline;
    gap: 8px;
}
.sources-entry__anchor {
    color: #adb5bd;
    text-decoration: none;
    font-weight: 400;
    opacity: 0;
    transition: opacity 80ms ease;
}
.sources-entry:hover .sources-entry__anchor { opacity: 1; }
.sources-entry__anchor:hover { color: #5c6bc0; }
.sources-entry__text {
    margin: 0 0 12px 0;
    font-size: 14px;
    line-height: 1.6;
    color: #495057;
}
.sources-entry__sources {
    font-size: 12px;
}
.sources-entry__sources-label {
    display: block;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #868e96;
    font-size: 11px;
    margin-bottom: 4px;
}
.sources-entry__sources ul {
    margin: 0;
    padding-left: 18px;
    color: #6c757d;
}
.sources-entry__sources li { margin: 2px 0; }
.sources-entry__sources a {
    color: #5c6bc0;
    text-decoration: none;
}
.sources-entry__sources a:hover { text-decoration: underline; }
.sources-footer {
    margin-top: 40px;
    padding-top: 20px;
    border-top: 1px solid rgba(0,0,0,0.08);
    font-size: 13px;
    color: #6c757d;
    line-height: 1.6;
}
.sources-footer a { color: #5c6bc0; text-decoration: none; }
.sources-footer a:hover { text-decoration: underline; }

body.theme-dark .sources-page { color: #e9ecef; }
body.theme-dark .sources-title,
body.theme-dark .sources-chapter__title { color: #f1f3f5; }
body.theme-dark .sources-eyebrow,
body.theme-dark .sources-entry__sources-label { color: #9aa0a6; }
body.theme-dark .sources-lede,
body.theme-dark .sources-entry__text { color: #adb5bd; }
body.theme-dark .sources-entry { border-bottom-color: rgba(255,255,255,0.07); }
body.theme-dark .sources-entry__title { color: #e9ecef; }
body.theme-dark .sources-footer { border-top-color: rgba(255,255,255,0.08); color: #9aa0a6; }
body.theme-dark .sources-entry--flash {
    animation: sources-flash-dark 1.6s ease-out;
}
@keyframes sources-flash-dark {
    0%   { background: rgba(130,177,255,0.16); }
    100% { background: transparent; }
}
body.theme-dark .bp-method {
    background: rgba(255,255,255,0.03);
    border-color: rgba(255,255,255,0.06);
    color: #9aa0a6;
}
body.theme-dark .bp-method__summary { color: #c9d1d9; }
body.theme-dark .bp-method__body { border-top-color: rgba(255,255,255,0.06); }
body.theme-dark .bp-method__text { color: #adb5bd; }
body.theme-dark .bp-method__sources { color: #868e96; }

/* Skeleton loaders — pulse a gradient placeholder until the section's
   data resolves. Tile + block variants. */
@keyframes bp-skel-shimmer {
    0%   { background-position: -200px 0; }
    100% { background-position: calc(200px + 100%) 0; }
}
.bp-skel {
    border-radius: 8px;
    background: linear-gradient(90deg, rgba(0,0,0,0.04) 0%, rgba(0,0,0,0.08) 50%, rgba(0,0,0,0.04) 100%);
    background-size: 200px 100%;
    background-repeat: no-repeat;
    animation: bp-skel-shimmer 1.4s ease-in-out infinite;
}
.bp-skel--tile { height: 96px; }
.bp-skel--block { height: 180px; }
.bp-skel-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 10px;
}

/* Dark theme overrides */
body.theme-dark .bp-header { border-bottom-color: rgba(255,255,255,0.08); }
body.theme-dark .bp-title { color: #f1f3f5; }
body.theme-dark .bp-eyebrow,
body.theme-dark .bp-section__intro,
body.theme-dark .bp-subhead__title,
body.theme-dark .bp-picker { color: #9aa0a6; }
body.theme-dark .bp-chapter__title { color: #f1f3f5; }
body.theme-dark .bp-section__title { color: #e9ecef; }
body.theme-dark .bp-toc {
    background: rgba(20,22,25,0.94);
    border-bottom-color: rgba(255,255,255,0.06);
}
body.theme-dark .bp-toc__link { color: #c9d1d9; }
body.theme-dark .bp-toc__link:hover { background: rgba(130,177,255,0.10); color: #82b1ff; }
body.theme-dark .bp-toc__link--active { background: #82b1ff; color: #0a0d10; }
body.theme-dark .bp-inset {
    background: rgba(255,255,255,0.04);
    border-color: rgba(255,255,255,0.08);
}
body.theme-dark .bp-picker select {
    background: rgba(255,255,255,0.04);
    border-color: rgba(255,255,255,0.12);
    color: #f1f3f5;
}
body.theme-dark .bp-skel {
    background: linear-gradient(90deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.04) 100%);
}

/* ── OLD .boundary-profile-* styles below are kept for the inner-section
   renderers (boundary-hero-grid, boundary-tile, boundary-poi-row, etc.).
   The old page/header/section selectors are dead but harmless — left in
   place to keep the diff focused on the new bp-* shell. */
.boundary-profile-header__deprecated {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
    gap: 24px;
    align-items: end;
    margin-bottom: 32px;
    padding-bottom: 20px;
    border-bottom: 1px solid rgba(0,0,0,0.08);
}
.boundary-profile-header__text { min-width: 0; }
.boundary-profile-inset {
    width: 100%;
    height: 180px;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid rgba(0,0,0,0.08);
    background: #eef0f3;
}
@media (max-width: 768px), (max-height: 600px) {
    .boundary-profile-header {
        grid-template-columns: minmax(0, 1fr);
    }
    .boundary-profile-inset { height: 160px; }
}
body.theme-dark .boundary-profile-inset {
    background: rgba(255,255,255,0.04);
    border-color: rgba(255,255,255,0.08);
}
.boundary-profile-eyebrow {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #6c757d;
    margin-bottom: 4px;
}
.boundary-profile-title {
    margin: 0 0 8px 0;
    font-size: 32px;
    font-weight: 700;
    line-height: 1.15;
    color: #212529;
}
.boundary-profile-crumbs a {
    color: #5c6bc0;
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
}
.boundary-profile-crumbs a:hover { text-decoration: underline; }
.boundary-profile-section { margin-bottom: 36px; }
.boundary-profile-section-title {
    margin: 0 0 6px 0;
    font-size: 18px;
    font-weight: 600;
    color: #212529;
}
.boundary-profile-section-intro {
    margin: 0 0 16px 0;
    font-size: 13px;
    color: #6c757d;
    line-height: 1.5;
}
.boundary-profile-section-head,
.boundary-profile-subsection-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}
.boundary-profile-subsection-head {
    margin-top: 24px;
}
.boundary-profile-subsection-title {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: #495057;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
body.theme-dark .boundary-profile-subsection-title { color: #c9d1d9; }
.boundary-profile-section-head .boundary-profile-section-title {
    margin: 0;
}
.boundary-profile-window-picker {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: #6c757d;
}
.boundary-profile-window-picker select {
    font: inherit;
    padding: 4px 8px;
    border-radius: 6px;
    border: 1px solid rgba(0,0,0,0.12);
    background: #fff;
    color: #212529;
    cursor: pointer;
}
body.theme-dark .boundary-profile-window-picker { color: #9aa0a6; }
body.theme-dark .boundary-profile-window-picker select {
    background: rgba(255,255,255,0.04);
    border-color: rgba(255,255,255,0.12);
    color: #f1f3f5;
}
.boundary-profile-loading {
    padding: 24px;
    text-align: center;
    color: #868e96;
    font-size: 13px;
    font-style: italic;
}
.boundary-profile-error {
    padding: 16px;
    border-radius: 8px;
    background: rgba(220,53,69,0.08);
    color: #c92a2a;
    font-size: 13px;
}
/* Hero stats row — top of every boundary profile page. 5 tiles
   (population / parcels / owner-occupancy / no-structure / median value).
   Wraps to 2 columns on phone-class widths. */
.boundary-hero-grid {
    display: grid;
    /* auto-fill (not auto-fit) so a sparse row keeps tiles at their natural
       width instead of stretching two tiles to fill the whole row. */
    grid-template-columns: repeat(auto-fill, minmax(185px, 1fr));
    gap: 14px;
}
.boundary-hero-stat {
    padding: 16px 14px 16px 16px;
    background: rgba(0,0,0,0.02);
    border: 1px solid rgba(0,0,0,0.06);
    border-left: 3px solid var(--bp-accent, #adb5bd);
    border-radius: 8px;
}
.boundary-hero-stat__value {
    font-size: 26px;
    font-weight: 700;
    color: #212529;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}
.boundary-hero-stat__label {
    margin-top: 2px;
    font-size: 12px;
    font-weight: 600;
    color: #495057;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.boundary-hero-stat__sub {
    margin-top: 4px;
    font-size: 11px;
    color: #868e96;
    line-height: 1.3;
}
body.theme-dark .boundary-hero-stat {
    background: rgba(255,255,255,0.03);
    border-color: rgba(255,255,255,0.08);
}
body.theme-dark .boundary-hero-stat__value { color: #f1f3f5; }
body.theme-dark .boundary-hero-stat__label { color: #c9d1d9; }
body.theme-dark .boundary-hero-stat__sub { color: #9aa0a6; }

/* Sales-trend figure container — inline SVG from js/shared/parcel-viz.js
   (renderMultiYearLines); the viewBox sizes itself, no JS height. */
.boundary-profile-chart {
    width: 100%;
    background: rgba(0,0,0,0.015);
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 8px;
    padding: 12px;
    box-sizing: border-box;
}
body.theme-dark .boundary-profile-chart {
    background: rgba(255,255,255,0.03);
    border-color: rgba(255,255,255,0.08);
}

/* Recent residential sales list. Each row links to /parcel/<id>. */
.boundary-sales-recent {
    margin-top: 16px;
}
.boundary-sales-recent__title {
    margin: 0 0 8px 0;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6c757d;
}
.boundary-sales-recent__list {
    display: flex;
    flex-direction: column;
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 8px;
    overflow: hidden;
}
.boundary-sale-row {
    /* permit/rental rows have an extra type column; auto-fits both shapes */
    display: grid;
    grid-template-columns: 110px minmax(0,1.5fr) auto auto;
    gap: 12px;
    align-items: baseline;
    padding: 10px 14px;
    text-decoration: none;
    color: inherit;
    transition: background-color 80ms ease;
}
.boundary-sale-row__type {
    color: #6c757d;
    font-size: 12px;
    text-align: right;
    white-space: nowrap;
}
body.theme-dark .boundary-sale-row__type { color: #9aa0a6; }

/* Top owners — leaderboard list with rank pill, name + sub, count. */
.boundary-owners-list {
    display: flex;
    flex-direction: column;
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 8px;
    overflow: hidden;
}
.boundary-owner-row {
    display: grid;
    grid-template-columns: 28px minmax(0,1fr) auto;
    gap: 12px;
    align-items: center;
    padding: 12px 14px;
    text-decoration: none;
    color: inherit;
    transition: background-color 80ms ease;
}
.boundary-owner-row + .boundary-owner-row { border-top: 1px solid rgba(0,0,0,0.05); }
.boundary-owner-row:hover { background: rgba(92,107,192,0.07); }
.boundary-owner-row__rank {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px; height: 24px;
    border-radius: 50%;
    background: rgba(92,107,192,0.12);
    color: #5c6bc0;
    font-weight: 700;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.boundary-owner-row__main { display: flex; flex-direction: column; min-width: 0; }
.boundary-owner-row__name {
    color: #212529;
    font-weight: 600;
    font-size: 14px;
    overflow-wrap: anywhere;
}
.boundary-owner-row__sub {
    color: #868e96;
    font-size: 11px;
    margin-top: 2px;
}
.boundary-owner-row__count {
    color: #212529;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Side-by-side demolition + foreclosure cards. Wraps to single column
   on phone-class widths. */
.boundary-profile-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 12px;
}
.boundary-profile-event-card {
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 8px;
    background: rgba(0,0,0,0.015);
    overflow: hidden;
}
.boundary-profile-event-card__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 12px 14px;
    border-bottom: 1px solid rgba(0,0,0,0.05);
}
.boundary-profile-event-card__title {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #495057;
}
.boundary-profile-event-card__count {
    font-size: 18px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: #212529;
}
.boundary-profile-event-card .boundary-sales-recent__list { border: none; border-radius: 0; }

body.theme-dark .boundary-owners-list,
body.theme-dark .boundary-profile-event-card { border-color: rgba(255,255,255,0.08); }
body.theme-dark .boundary-profile-event-card { background: rgba(255,255,255,0.03); }
body.theme-dark .boundary-profile-event-card__head { border-bottom-color: rgba(255,255,255,0.06); }
body.theme-dark .boundary-owner-row + .boundary-owner-row { border-top-color: rgba(255,255,255,0.06); }
body.theme-dark .boundary-owner-row__name { color: #f1f3f5; }
body.theme-dark .boundary-owner-row__sub { color: #9aa0a6; }
body.theme-dark .boundary-owner-row__count { color: #f1f3f5; }
body.theme-dark .boundary-owner-row__rank {
    background: rgba(130,177,255,0.16);
    color: #82b1ff;
}
body.theme-dark .boundary-owner-row:hover { background: rgba(130,177,255,0.10); }
body.theme-dark .boundary-profile-event-card__title { color: #c9d1d9; }
body.theme-dark .boundary-profile-event-card__count { color: #f1f3f5; }
.boundary-sale-row + .boundary-sale-row {
    border-top: 1px solid rgba(0,0,0,0.05);
}
.boundary-sale-row:hover {
    background: rgba(92,107,192,0.07);
}
.boundary-sale-row__date {
    color: #6c757d;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}
.boundary-sale-row__parcel {
    color: #5c6bc0;
    font-weight: 500;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
}
.boundary-sale-row__price {
    color: #212529;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
body.theme-dark .boundary-sales-recent__title { color: #9aa0a6; }
body.theme-dark .boundary-sales-recent__list { border-color: rgba(255,255,255,0.08); }
body.theme-dark .boundary-sale-row + .boundary-sale-row { border-top-color: rgba(255,255,255,0.06); }
body.theme-dark .boundary-sale-row__date { color: #9aa0a6; }
body.theme-dark .boundary-sale-row__price { color: #f1f3f5; }
body.theme-dark .boundary-sale-row:hover { background: rgba(130,177,255,0.10); }

/* Access tiles — one per (group, cat). Color swatch on top-right hints
   at the citywide choropleth ramp for the same category. */
.boundary-profile-tiles {
    display: flex;
    flex-direction: column;
    gap: 20px;
}
/* Vertical breathing room between measure groups (health single-view stacks
   these directly). Groups inside .boundary-profile-tiles use its flex gap. */
.boundary-tile-group { margin-bottom: 22px; }
.boundary-tile-group:last-child { margin-bottom: 0; }
.boundary-profile-tiles .boundary-tile-group { margin-bottom: 0; }
.boundary-tile-group__title {
    margin: 0 0 10px 0;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6c757d;
}
.boundary-tile-group__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 10px;
}
.boundary-tile {
    position: relative;
    padding: 14px 14px 14px 16px;
    border: 1px solid rgba(0,0,0,0.06);
    border-left: 4px solid var(--tile-color, #adb5bd);
    border-radius: 8px;
    background: rgba(0,0,0,0.015);
}
.boundary-tile__cat {
    font-size: 12px;
    font-weight: 500;
    color: #6c757d;
    margin-bottom: 4px;
}
.boundary-tile__value {
    font-size: 22px;
    font-weight: 700;
    color: #212529;
    line-height: 1.1;
}
.boundary-tile__unit {
    font-size: 13px;
    font-weight: 500;
    color: #6c757d;
    margin-left: 2px;
}
.boundary-tile__swatch {
    position: absolute;
    top: 12px; right: 12px;
    width: 12px; height: 12px;
    border-radius: 50%;
    background: var(--tile-color, transparent);
}
/* Destinations: reuse boundary-poi-card/row from the in-map panel so the
   visual language matches across the panel and the page. */
.boundary-profile-destinations {
    display: flex;
    flex-direction: column;
    gap: 20px;
}
.boundary-profile-destinations .boundary-poi-group__title {
    margin: 0 0 8px 0;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6c757d;
}
.boundary-profile-destinations .boundary-poi-row {
    /* in-page rows aren't clickable yet (no map context); drop the
       hover/cursor affordance until Phase 2 hooks them up. */
    cursor: default;
}
.boundary-profile-destinations .boundary-poi-row:hover {
    background: transparent;
}

body.theme-dark .boundary-profile-page { color: #e9ecef; }
body.theme-dark .boundary-profile-header { border-bottom-color: rgba(255,255,255,0.08); }
body.theme-dark .boundary-profile-title { color: #f1f3f5; }
body.theme-dark .boundary-profile-eyebrow,
body.theme-dark .boundary-profile-section-intro,
body.theme-dark .boundary-tile-group__title,
body.theme-dark .boundary-profile-destinations .boundary-poi-group__title { color: #9aa0a6; }
body.theme-dark .boundary-tile {
    background: rgba(255,255,255,0.03);
    border-color: rgba(255,255,255,0.08);
}
body.theme-dark .boundary-tile__cat,
body.theme-dark .boundary-tile__unit { color: #9aa0a6; }
body.theme-dark .boundary-tile__value { color: #f1f3f5; }

.parcel-pre-tag {
    display: inline-block;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 600;
    color: #0d47a1;
    background: #e3f2fd;
    border: 1px solid #90caf9;
    border-radius: 10px;
    vertical-align: middle;
    white-space: nowrap;
}
.selection-section {
    margin-top: 16px;
}
/* Neighborhood panel — active for-sale listings */
.neighborhood-sales-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.neighborhood-sale-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "addr price" "meta meta";
    gap: 0 var(--space-3);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
}
.neighborhood-sale-addr {
    grid-area: addr;
    font-size: 13px;
    color: var(--color-fg-primary);
}
.neighborhood-sale-price {
    grid-area: price;
    font-size: 14px;
    font-weight: 600;
    color: var(--accent-green);
    white-space: nowrap;
}
.neighborhood-sale-meta {
    grid-area: meta;
    font-size: 11px;
    color: var(--color-fg-muted, #6c757d);
}
.selection-section h4 {
    margin: 0 0 var(--space-2) 0;
    font-size: 15px;
    font-weight: 600;
    color: var(--color-fg-primary);
}

/* Enrichment-block header — emitted by buildSectionHeader(). Speaks the
   boundary-dashboard section-title language: sentence-case 17px primary
   text with the per-topic color carried as an accent underline + icon
   tint (via the inline --section-accent custom property), instead of the
   old fully-colored 16px title. Falls back to a neutral border when no
   topic color is passed. */
.panel-section-title {
    margin: 0 0 var(--space-3);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 17px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--color-fg-primary);
    padding-bottom: var(--space-2);
    border-bottom: 2px solid var(--section-accent, var(--color-border));
}
.panel-section-title__icon {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    color: var(--section-accent, var(--color-fg-muted));
}
.panel-section-title__icon svg {
    width: 1.05em;
    height: 1.05em;
}

/* ---- Panel group headers ---------------------------------------- */
/* Groups wrap related enrichment sections (Affordable Housing, Sales
   History, Compliance & Enforcement, Fire & Safety) under a slim
   label that reads as a structural divider, not a navigation element.
   The label sits above the first section's own h4, so both levels
   of hierarchy are visible simultaneously when collapsed. */
/* ---- Topic strip — the density navigator -------------------------- */
/* Sticky single-row strip under the masthead. One chip per record FEED
   present on the parcel ("Blight 30 · Jun 2025"): label + count +
   latest date, click = smooth-scroll to that feed's section in the
   Activity module. Quiet parcels collapse to their 2–3 populated
   topics; the strip hides entirely at ≤1 (renderer's decision).
   Sticks to the top of the scrolling panel so it's reachable from any
   depth — the busy parcel's table of contents. */
.topic-strip {
    position: sticky;
    top: 0;
    z-index: var(--z-in-panel-strip);
    display: flex;
    gap: var(--space-2);
    align-items: center;
    padding: var(--space-2) var(--space-4);
    background: var(--color-bg-elevated);
    border-bottom: 1px solid var(--color-border);
    overflow-x: auto;
    scrollbar-width: none;
}
.topic-strip::-webkit-scrollbar { display: none; }
.tchip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    font-size: 10.5px;
    color: var(--color-fg-secondary);
    font-family: inherit;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill);
    background: var(--color-bg-muted);
    padding: 1px 9px;
    cursor: pointer;
    white-space: nowrap;
}
.tchip:hover {
    border-color: var(--color-accent);
    color: var(--color-accent-text);
}
.tchip b { font-weight: 600; }
.tchip .n { font-variant-numeric: tabular-nums; }
.tchip .d { color: var(--color-fg-muted); }

/* ---- Occupants section ------------------------------------------- */
/* Compact list of businesses + food-service establishments tied to
   the parcel. Lives near the top of the profile because "who's at
   this address" is the first question users ask. */
.occupants-section {
    margin: 14px 0 18px;
}
.occupants-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted, #6c757d);
    margin: 0 0 8px;
    display: flex;
    align-items: center;
    gap: 8px;
}
.occupants-count {
    font-size: 10px;
    background: var(--color-bg-elevated-2, #eef2f6);
    color: var(--color-fg-muted, #6c757d);
    padding: 1px 6px;
    border-radius: 999px;
    letter-spacing: 0;
}
body.theme-dark .occupants-count {
    background: #2a2a2a;
    color: #aaa;
}
.occupants-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.occupant-row {
    display: flex;
    flex-direction: column;
    padding: 6px 0;
    border-bottom: 1px solid var(--color-border, #eee);
}
.occupant-row:last-child {
    border-bottom: none;
}
.occupant-name {
    font-size: 14px;
    font-weight: 500;
    color: var(--color-fg-primary);
}
.occupant-meta {
    font-size: 11px;
    color: var(--color-fg-muted, #6c757d);
    margin-top: 1px;
}
body.theme-dark .occupant-row {
    border-bottom-color: #2a2a2a;
}

.panel-group {
    margin-bottom: 24px;
}
.panel-group-header {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-fg-muted, #6c757d);
    padding-bottom: 6px;
    margin-bottom: 12px;
    border-bottom: 2px solid var(--color-border, #e0e0e0);
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    user-select: none;
}
.panel-group-header::after {
    content: '▲';
    font-size: 9px;
    flex-shrink: 0;
    color: var(--color-fg-muted, #6c757d);
    transition: transform 0.2s;
}
.panel-group--collapsed .panel-group-header::after {
    transform: rotate(180deg);
}
.panel-group-content { overflow: hidden; }
.panel-group--collapsed .panel-group-content { display: none; }
body.theme-dark .panel-group-header {
    border-bottom-color: #333;
    color: #9aa0a6;
}

/* ---- Scorecard row ---------------------------------------------- */
/* Headline KPI strip between the share button and Parcel Data — SEV,
   year built, open violations, HUD / FIE flags, tax status. Uses the
   boundary-dashboard KPI tile language (boundary-hero-stat): a bordered
   tile with a value over an uppercase label and a status-colored left
   accent bar, laid out in an auto-fit grid that stays compact in the
   narrow sidebar. Status meaning rides the accent color (--sc-accent)
   from the shared semantic + accent tokens, so dark mode comes for free
   via --color-bg-muted. */
.scorecard-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
    gap: var(--space-2);
    margin: var(--space-3) 0 var(--space-4);
}
.scorecard-chip {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 6px var(--space-2);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
    border-left: 3px solid var(--sc-accent, var(--color-border-strong));
    border-radius: var(--radius-md);
}
.scorecard-value {
    order: -1;
    font-size: 14px;
    font-weight: 700;
    line-height: 1.2;
    color: var(--color-fg-primary);
    font-variant-numeric: tabular-nums;
}
.scorecard-label {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-fg-muted);
}
/* In the roomy expanded dashboard, let the same tiles breathe a little. */
#expanded-dashboard .scorecard-value { font-size: 16px; }
#expanded-dashboard .scorecard-chip  { padding: var(--space-2) var(--space-3); }
.scorecard-chip--neutral { --sc-accent: var(--color-border-strong); }
.scorecard-chip--warning { --sc-accent: var(--color-warning-accent); }
.scorecard-chip--alert   { --sc-accent: var(--color-danger-accent); }
.scorecard-chip--info    { --sc-accent: var(--color-accent); }
.scorecard-chip--hud     { --sc-accent: var(--accent-violet); }
.scorecard-chip--fie     { --sc-accent: var(--accent-cyan); }

/* The `.debug-section` block styled the panel's "Developer: Raw tile
   properties" disclosure — a dashed box whose whole design brief was to
   say "not for regular users" on a public page. The disclosure is gone
   (2026-08-09) and so are its rules. */

/* ---- Source attribution footer ---------------------------------- */
.section-source {
    font-size: 11px;
    color: var(--color-fg-muted, #6c757d);
    margin-top: 8px;
    padding-top: 6px;
    border-top: 1px solid var(--color-border, #e0e0e0);
}
.section-source a {
    color: inherit;
    text-decoration: underline;
}
.section-source-updated {
    color: var(--color-fg-muted, #6c757d);
    margin-left: 2px;
}
body.theme-dark .section-source {
    border-top-color: #333;
}

/* ── Transaction timeline ──────────────────────────────────────────────
   One vertical chronology of property transactions. Each event shows the
   two source records (Register of Deeds + Assessor) that document it. Calm,
   token-driven styling — no hardcoded colors, dark-mode correct. */
.txn-timeline {
    position: relative;
    margin: var(--space-2) 0 var(--space-3);
}
.txn-event {
    position: relative;
    padding: 0 0 var(--space-4) var(--space-5);
    border-left: 2px solid var(--color-border-strong, #dee2e6);
}
.txn-event:last-child {
    border-left-color: transparent;
    padding-bottom: var(--space-1);
}
.txn-node {
    position: absolute;
    left: -6px;
    top: 3px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--color-fg-muted, #6c757d);
    box-shadow: 0 0 0 3px var(--color-bg-elevated, #fff);
}
.txn-event--sale .txn-node       { background: var(--color-success-accent, #16a34a); }
.txn-event--foreclosure .txn-node { background: var(--color-danger-accent, #b91c1c); }
.txn-event--forfeiture .txn-node  { background: var(--color-danger-accent, #b91c1c); }
.txn-event--judgment .txn-node    { background: var(--color-warning-accent, #d97706); }
.txn-event--redemption .txn-node  { background: var(--color-accent, #0080ff); }

.txn-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-2);
}
.txn-type {
    font-weight: 600;
    color: var(--color-fg-primary, #333);
    font-size: 14px;
}
.txn-meta {
    font-size: 12px;
    color: var(--color-fg-muted, #6c757d);
    white-space: nowrap;
}
.txn-parties {
    margin-top: 2px;
    font-size: 13px;
    color: var(--color-fg-secondary, #495057);
    line-height: 1.35;
}
.txn-parties .txn-arrow { color: var(--color-fg-muted, #6c757d); }

.txn-sources {
    margin-top: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.txn-source {
    display: grid;
    grid-template-columns: 116px 1fr;
    gap: var(--space-2);
    font-size: 12px;
    align-items: baseline;
}
.txn-source-label {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-fg-tertiary, #868e96);
    padding-top: 1px;
}
.txn-source-detail { color: var(--color-fg-secondary, #495057); }
.txn-source-detail a { color: var(--color-accent, #0080ff); text-decoration: none; }
.txn-source-detail a:hover { text-decoration: underline; }
.txn-source-missing {
    font-size: 12px;
    color: var(--color-fg-muted, #6c757d);
    font-style: italic;
}

/* ⓘ icon on parcel data section headers — links to pipelines glossary */
.section-info-link {
    margin-left: auto;
    display: flex;
    align-items: center;
    color: var(--color-fg-muted, #6c757d);
    text-decoration: none;
    opacity: 0.5;
    transition: opacity 0.15s;
    flex-shrink: 0;
}
.section-info-link:hover {
    opacity: 1;
}

.ambient-banner {
    font-size: 12px;
    margin-bottom: 12px;
}

/* ---- Floating Mapillary viewer (picture-in-picture) ----------- */
/* Pinned bottom-right of the map. No drag, no resize — the frame
   is entirely CSS-positioned so it can never drift out of sync
   with the map underneath (the map-PiP chrome had that problem
   when drag was allowed). "Minimize" closes the viewer; "Swap"
   flips primary vs. floating frames. */
#mapillary-container.floating-viewer {
    position: absolute;
    left: auto;
    /* SV is a CONTEXT surface — it yields to the selection
       sidebar (when visible) and to whatever CONTROL panel
       the user has open. See the layout-principles block at
       the top of this stylesheet. Both offsets are 0 when
       nothing is open, so the default collapses to 20px. */
    right: calc(var(--control-panel-offset, 0px) + 20px);
    bottom: 20px;
    top: auto;
    width: min(480px, 34vw);
    height: min(320px, 36vh);
    min-width: 280px;
    min-height: 180px;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border-strong);
    border-radius: 6px;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    z-index: var(--z-floating);
    display: none;
    transition: right var(--motion-base) ease;
}
/* When sidebar is visible, slide the viewer clear of its edge. */
body.sidebar-visible #mapillary-container.floating-viewer {
    right: calc(var(--sidebar-width, 300px) + var(--control-panel-offset, 0px) + 20px);
}

.floating-viewer-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 32px;
    background: var(--color-bg-muted);
    border-bottom: 1px solid var(--color-border-strong);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 var(--space-2) 0 var(--space-3);
    z-index: 5;
}
.floating-viewer-title-group {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    min-width: 0;
    overflow: hidden;
}
.floating-viewer-title {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--color-fg-secondary);
    letter-spacing: 0.02em;
    white-space: nowrap;
    /* Title shrinks/ellipsizes before the pill row does — the
       pills carry information, the title is branding. */
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex-shrink: 1;
}
/* Time pill — single button in the chrome header. Label shows
   the currently-viewed image's month+year; click opens the
   archive drill-down (.time-menu) below it. Separator "•"
   from the title rendered via ::before, matching the prior
   subtitle look so the chrome visually reads the same. */
.time-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    line-height: 1;
    padding: 3px 8px;
    border-radius: 10px;
    border: 1px solid #c5e0fa;
    background: #e7f5ff;
    color: #1971c2;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    flex-shrink: 0;
    transition: background-color 0.12s, color 0.12s, border-color 0.12s;
    font-family: inherit;
}
.time-pill::before {
    content: '•';
    margin-right: 4px;
    color: #ced4da;
    font-weight: 400;
}
.time-pill:hover:not(:disabled) {
    background: #d0ebff;
    border-color: #9ac8ec;
}
.time-pill--open,
.time-pill--open:hover:not(:disabled) {
    background: #1971c2;
    color: #fff;
    border-color: #1971c2;
}
.time-pill--open::before {
    color: rgba(255, 255, 255, 0.5);
}
.time-pill:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    background: #f1f3f5;
    color: #868e96;
    border-color: #dee2e6;
}
.time-pill-chevron {
    flex-shrink: 0;
    transition: transform 0.12s;
}
.time-pill--open .time-pill-chevron {
    transform: rotate(180deg);
}
/* Time menu — drill-down popover anchored beneath the pill. */
.time-menu {
    position: absolute;
    z-index: 1000;
    min-width: 180px;
    max-width: 260px;
    max-height: 320px;
    overflow-y: auto;
    background: #ffffff;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
    padding: 4px;
    font-size: 12px;
    color: #343a40;
}
.time-menu-year-wrap,
.time-menu-month-wrap {
    display: block;
}
.time-menu-row {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 6px 8px;
    background: transparent;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-family: inherit;
    font-size: 12px;
    color: inherit;
    text-align: left;
    line-height: 1.25;
}
.time-menu-row:hover {
    background: #f1f3f5;
}
.time-menu-row--active {
    background: #e7f5ff;
    color: #1971c2;
    font-weight: 600;
}
.time-menu-row--active:hover {
    background: #d0ebff;
}
.time-menu-row--year { font-weight: 600; }
.time-menu-row--month { padding-left: 20px; }
.time-menu-row--day { padding-left: 36px; color: #6c757d; }
.time-menu-row--day.time-menu-row--active { color: #1971c2; }
.time-menu-caret {
    flex-shrink: 0;
    transition: transform 0.12s;
    color: #868e96;
}
.time-menu-row--expanded .time-menu-caret {
    transform: rotate(90deg);
}
.time-menu-label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.time-menu-count {
    flex-shrink: 0;
    color: #868e96;
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    font-weight: 500;
}
.time-menu-row--active .time-menu-count {
    color: #1971c2;
}
.time-menu-children {
    display: block;
}
.time-menu-children--day {
    display: none;
}
.time-menu-month-wrap--expanded > .time-menu-children--day {
    display: block;
}
.floating-viewer-chrome-buttons {
    display: flex;
    gap: var(--space-1);
    flex-shrink: 0;
}
.floating-viewer-minimize-btn {
    background: transparent;
    border: none;
    cursor: pointer;
    width: 26px;
    height: 26px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-fg-secondary);
    transition: background-color var(--motion-fast);
    padding: 0;
}
.floating-viewer-minimize-btn:hover {
    background: var(--color-bg-hover);
}

/* Push the Mapillary canvas below the chrome header */
#mapillary-container.floating-viewer #mapillary-viewer {
    position: absolute;
    top: 32px;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: calc(100% - 32px);
}
#mapillary-container.floating-viewer #mapillary-spinner {
    top: calc(50% + 16px);
}

/* Detail row improvements */
.detail-row {
    padding: 8px 0;
    border-bottom: 1px solid #eee;
    display: flex;
    justify-content: space-between;
}

.detail-row strong {
    font-weight: 600;
}

/* ── Financing: one line per loan STILL IN FORCE, each naming its borrower ──
   A parcel can carry more than one open loan (06002546. and 01004105. both
   do), so the value side of the row is a stack rather than a single line. */
.detail-row--stack { align-items: flex-start; gap: var(--space-3); }
.fin-loans { display: flex; flex-direction: column; gap: var(--space-2); text-align: right; min-width: 0; }
.fin-loan { line-height: 1.35; }
.fin-loan__who { font-size: 11.5px; color: var(--color-fg-muted); margin-top: 1px; }

/* Share button (sidebar copy-link) */
.share-button {
    background-color: #0080ff;
    color: #fff;
    border: none;
    border-radius: 4px;
    padding: 8px 12px;
    margin-top: 8px;
    cursor: pointer;
    font-size: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.share-button:hover {
    background-color: #0066cc;
}
body.theme-dark .share-button {
    background-color: #1a6fd1;
}
body.theme-dark .share-button:hover {
    background-color: #2a80e0;
}

/* Detail-row status variants (light mode) */
.detail-row.detail-info {
    background: #e8f4ff;
    padding: 8px;
    margin-bottom: 16px;
}
.detail-row.detail-warning {
    background: #fff3e0;
    padding: 8px;
    margin-bottom: 16px;
}
.detail-row.detail-error {
    background: #ffebee;
    padding: 8px;
}

/* Tooltip styles */
.tooltip {
    position: relative;
    display: inline-block;
}

.tooltip .tooltip-text {
    visibility: hidden;
    width: 120px;
    background-color: rgba(0, 0, 0, 0.8);
    color: #fff;
    text-align: center;
    border-radius: 4px;
    padding: 5px;
    position: absolute;
    z-index: 1002;
    bottom: 125%;
    left: 50%;
    transform: translateX(-50%);
    opacity: 0;
    transition: opacity 0.2s;
    font-size: 12px;
    pointer-events: none;
}

.tooltip .tooltip-text::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 50%;
    margin-left: -5px;
    border-width: 5px;
    border-style: solid;
    border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
}

.tooltip:hover .tooltip-text {
    visibility: visible;
    opacity: 1;
}

/* Focus styles for accessibility */
button:focus, input:focus, select:focus, a:focus {
    outline: 2px solid #0080ff;
    outline-offset: 2px;
}

/* Loading indicator improvements */
#loading {
    background-color: rgba(255, 255, 255, 0.9);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    padding: 20px 30px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
}

#loading::before {
    content: '';
    width: 20px;
    height: 20px;
    margin-right: 10px;
    border: 2px solid #0080ff;
    border-top-color: transparent;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

/* The boot watchdog's failure state (see reportBootStall in main.js). The
   spinner has to go with it — a spinner beside "the map didn't load" says
   the opposite of the sentence next to it. */
#loading.is-stalled {
    gap: var(--space-3);
    font-weight: 500;
}

#loading.is-stalled::before {
    display: none;
}

.boot-retry {
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--color-accent-text);
    font: inherit;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--motion-fast);
}

.boot-retry:hover {
    background: var(--color-border-muted);
}

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

/* Hide debug panel toggle button and zoom level display by default */
#debug-toggle-btn, #zoom-level-display {
    display: none !important;
}

/* Responsive improvements */
@media (max-width: 768px), (max-height: 600px) {
    #floating-header {
        padding: 0 12px;
    }
    
    #search-container {
        max-width: 60%;
        margin: 0 10px;
    }
    
    .header-button span {
        display: none;
    }
    
    .header-button svg {
        margin-right: 0;
    }

    /* Toolbar panels: all right-anchored (same as desktop), just
       narrower to fit the smaller screen. No bottom-centering. */
    #layers-panel,
    #labels-panel,
    #boundaries-popover {
        width: min(280px, calc(100vw - 74px)) !important;
    }

    /* Filter panel overrides its own JS-injected width. */
    #filter-panel {
        width: min(300px, calc(100vw - 74px)) !important;
        max-width: none !important;
    }
}

/* Dark mode enhancements */
body.theme-dark {
    color: #eee;
    background-color: #121212;
}

body.theme-dark #floating-header {
    background-color: #1e1e1e;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

body.theme-dark #home-button {
    color: #0099ff;
}

/* The magnifying-glass summon button uses hardcoded light palette
   colors in index.html. In dark mode the near-white background collides
   with the dark header — override with dark-palette surfaces so it
   blends with the rest of the header chrome. */
body.theme-dark .search-icon-button {
    background-color: #333;
    border-color: #444;
    color: #bbb;
}
body.theme-dark .search-icon-button:hover {
    background-color: #404040;
    border-color: #0099ff;
    color: #0099ff;
}

body.theme-dark .header-button:hover {
    background-color: rgba(0, 153, 255, 0.2);
}

body.theme-dark .header-button.active {
    background-color: rgba(0, 153, 255, 0.25);
    color: #0099ff;
}

/* Force the dark background HERE, in the same stylesheet as the theme
   tokens — not only on the base rule in survey.css. The two files cache
   independently (max-age 300), so a stale survey.css (whose old literal was
   `background: white`) would otherwise leave the panel light in dark mode
   while the token-based child tiles correctly go dark. This higher-
   specificity rule wins regardless of survey.css's cached state.
   (Historical note: theme-enhancements.js used to carry a dead copy of
   this rule — removed with the rest of its never-injected CSS blob — so
   theme.css is the real source.) */
body.theme-dark #details-sidebar {
    background: var(--color-bg-elevated);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

body.theme-dark .filter-header {
    border-bottom-color: #333;
}

body.theme-dark .filter-section-title {
    color: #eee;
}

body.theme-dark .filter-close,
body.theme-dark .filter-reset {
    background-color: #333;
    color: #eee;
}

body.theme-dark .filter-close:hover,
body.theme-dark .filter-reset:hover {
    background-color: #444;
}

/* ---- Dark mode: layers panel ---------------------------------
   Most panel-chrome overrides are gone — the base rules use
   semantic tokens (--color-bg-elevated, --color-border,
   --color-fg-muted, --color-bg-hover) which flip automatically
   under body.theme-dark via the tokens block at the top.

   What stays: (a) shadow needs more opacity on dark bg to be
   visible; (b) swatch border inverts from black to white
   because a dark border is invisible against a dark card. */
body.theme-dark #layers-panel {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
body.theme-dark .layer-swatch {
    border-color: rgba(255, 255, 255, 0.18);
}

/* ---- Dark mode: Legend ---------------------------------------
   Semantic tokens carry most of the weight; only the swatch
   border + card shadow need explicit dark-mode values. */
body.theme-dark .legend .legend-expanded-body {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}
body.theme-dark .legend .legend-collapsed-button {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}
body.theme-dark .legend-swatch {
    border-color: rgba(255, 255, 255, 0.18);
}

/* ---- Dark mode: boundaries popover ---------------------------
   Same story as the layers panel — base uses semantic tokens
   that auto-flip. Only real overrides below are: (a) darker
   shadow; (b) white-tinted swatch border; (c) the blue
   selected-row tint (domain accent, not a semantic token);
   (d) the "coming soon" chip which has its own theme. */
body.theme-dark #boundaries-popover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
body.theme-dark .boundary-option--disabled:hover {
    background-color: transparent;
}
body.theme-dark .boundary-coming-soon {
    color: #9aa0a6;
    background: #2a2a2a;
}
body.theme-dark .boundary-option-swatch {
    border-color: rgba(255, 255, 255, 0.18);
}

/* ---- Dark mode: selection panel chips + thumb + floating viewer -- */
body.theme-dark #panel-chip-row { border-bottom-color: #333; }
body.theme-dark #panel-close {
    background: #333;
    color: #ddd;
}
body.theme-dark #panel-close:hover { background: #444; color: #fff; }

body.theme-dark .chip {
    background: #2a2a2a;
    color: #ccc;
    border-color: #333;
}
body.theme-dark .chip--current {
    background: #0099ff;
    color: #0a0a0a;
    border-color: #0099ff;
}
body.theme-dark .chip--neighborhood { background: #0d2d4a; color: #9ac8ec; border-color: #1a3e60; }
body.theme-dark .chip--ward         { background: #3a2410; color: #ffb380; border-color: #4a3220; }
body.theme-dark .chip--council      { background: #27204a; color: #c0adff; border-color: #3a305e; }
body.theme-dark .chip--tract        { background: #3a1428; color: #ffa8c2; border-color: #4a2036; }
body.theme-dark .chip--block        { background: #1f2d10; color: #c5e58f; border-color: #2e3d20; }
body.theme-dark .chip--clickable:hover {
    background: #0099ff;
    color: #0a0a0a;
    border-color: #0099ff;
}

body.theme-dark .parcel-address { color: #bbb; }
body.theme-dark .parcel-caption { color: #9aa0a6; }
body.theme-dark .parcel-caption-sep { color: #6c757d; }
body.theme-dark .parcel-zoning-tag {
    color: #d7ccc8;
    background: #3e2723;
    border-color: #5d4037;
}
body.theme-dark .parcel-nez-tag {
    color: #c8e6c9;
    background: #1b5e20;
    border-color: #2e7d32;
}
body.theme-dark .parcel-pre-tag {
    color: #bbdefb;
    background: #0d47a1;
    border-color: #1565c0;
}
body.theme-dark .confidence-chip__label { color: #9aa0a6; }
body.theme-dark .confidence-chip--high   { background: #1b5e20; border-color: #2e7d32; color: #c8e6c9; }
body.theme-dark .confidence-chip--medium { background: #5d4d00; border-color: #7e6a00; color: #fff3c4; }
body.theme-dark .confidence-chip--low    { background: #b71c1c; border-color: #c62828; color: #ffcdd2; }
body.theme-dark .confidence-chip--public { background: #0d47a1; border-color: #1565c0; color: #bbdefb; }
body.theme-dark .confidence-chip--na     { background: #2a2a2a; border-color: #3a3a3a; color: #9aa0a6; }
body.theme-dark .confidence-row__methodology { color: #6c757d; }
body.theme-dark .confidence-row__methodology:hover { color: #82b1ff; }

body.theme-dark #mapillary-container.floating-viewer {
    background: #1e1e1e;
    border-color: #333;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
}
body.theme-dark .floating-viewer-header {
    background: #252525;
    border-bottom-color: #333;
}
body.theme-dark .floating-viewer-title { color: #ddd; }
body.theme-dark .time-pill {
    background: #0d2d4a;
    color: #9ac8ec;
    border-color: #1a3e60;
}
body.theme-dark .time-pill::before { color: #4a4a4a; }
body.theme-dark .time-pill:hover:not(:disabled) {
    background: #14416b;
    border-color: #2a5682;
}
body.theme-dark .time-pill--open,
body.theme-dark .time-pill--open:hover:not(:disabled) {
    background: #9ac8ec;
    color: #0a0a0a;
    border-color: #9ac8ec;
}
body.theme-dark .time-pill:disabled {
    background: #252525;
    color: #5a5a5a;
    border-color: #333;
}
body.theme-dark .time-menu {
    background: #1e1e1e;
    border-color: #333;
    color: #ddd;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
}
body.theme-dark .time-menu-row:hover { background: #2a2a2a; }
body.theme-dark .time-menu-row--active {
    background: #0d2d4a;
    color: #9ac8ec;
}
body.theme-dark .time-menu-row--active:hover { background: #14416b; }
body.theme-dark .time-menu-row--day { color: #adb5bd; }
body.theme-dark .time-menu-row--day.time-menu-row--active { color: #9ac8ec; }
body.theme-dark .time-menu-count { color: #868e96; }
body.theme-dark .time-menu-row--active .time-menu-count { color: #9ac8ec; }
body.theme-dark .floating-viewer-minimize-btn { color: #ddd; }
body.theme-dark .floating-viewer-minimize-btn:hover { background: #333; }

/* .detail-row status variants keep their explicit dark backgrounds. (The
   base .detail-row border stays #eee in both themes — a dark flip existed
   only in the never-injected theme-enhancements.js copy, so the live app
   never had one.) */
body.theme-dark .detail-row.detail-info {
    background: #14304a;
    color: #cfe6ff;
}
body.theme-dark .detail-row.detail-warning {
    background: #3a2a10;
    color: #ffe0b2;
}
body.theme-dark .detail-row.detail-error {
    background: #3a1414;
    color: #ffcdd2;
}

body.theme-dark #loading {
    background-color: rgba(30, 30, 30, 0.9);
    color: #eee;
}

/* Sidebar layout + transitions (moved from sidebar.js) */
#details-sidebar {
    transition: width 0.3s, right 0.3s;
    overflow-y: auto;
}
@media (max-width: 768px), (max-height: 600px) {
    #details-sidebar {
        width: 100% !important;
        height: 40% !important;
        top: 60% !important;
        right: 0 !important;
        bottom: 0 !important;
        box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1) !important;
    }
    /* Floating viewer collapses on mobile (floating-viewer.js hides it anyway). */
    #mapillary-container.floating-viewer {
        display: none !important;
    }

    /* --- compact panel chrome on mobile --- */
    #panel-body         { padding: 8px 12px 12px; }
    #panel-chip-row     { padding: 6px 12px; }
    #panel-tab-strip    { padding: 4px 12px 2px; gap: 3px; }
    .topic-strip        { padding: 4px 0; margin: 4px 0 8px; gap: 4px; }
    .panel-group        { margin-bottom: 12px; }
    .panel-group-header { padding-bottom: 4px; margin-bottom: 6px; }
}
body.theme-dark .sidebar-toggle {
    background-color: #333;
    color: #eee;
}

/* Debug panel (debug-panel.js renders into #debug) */
#debug {
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.debug-title {
    margin-top: 0;
    margin-bottom: 10px;
    color: #333;
}
.debug-clear-button {
    position: absolute;
    top: 10px;
    right: 10px;
    padding: 3px 8px;
    font-size: 12px;
    background-color: #f5f5f5;
    border: 1px solid #ddd;
    border-radius: 3px;
    cursor: pointer;
    color: #333;
}
.debug-message {
    padding: 8px;
    margin: 8px 0;
}
.debug-message--success { background: #e8f5e9; }
.debug-message--warning { background: #fff9c4; }
.debug-message--error   { background: #ffebee; }
.debug-message--info    { background: #e3f2fd; }
.debug-timestamp {
    color: #666;
    font-size: 0.8em;
}

body.theme-dark #debug {
    background-color: #1e1e1e;
    color: #eee;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
body.theme-dark .debug-title { color: #eee; }
body.theme-dark .debug-clear-button {
    background-color: #333;
    border-color: #444;
    color: #eee;
}
body.theme-dark .debug-message--success { background: #14301a; color: #c8e6c9; }
body.theme-dark .debug-message--warning { background: #3a2e10; color: #fff9c4; }
body.theme-dark .debug-message--error   { background: #3a1414; color: #ffcdd2; }
body.theme-dark .debug-message--info    { background: #14304a; color: #cfe6ff; }
body.theme-dark .debug-timestamp { color: #9aa0a6; }

/* Stat cards (chart-utils.js) */
.stat-card {
    background-color: #f8f9fa;
    border-radius: 8px;
    padding: 16px;
    margin-bottom: 16px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    display: flex;
    align-items: center;
}
.stat-card-icon {
    margin-right: 16px;
    color: #0080ff;
    display: flex;
    align-items: center;
    justify-content: center;
}
.stat-card-content { flex: 1; }
.stat-card-title {
    font-size: 14px;
    color: #6c757d;
    margin-bottom: 4px;
}
.stat-card-value {
    font-size: 24px;
    font-weight: bold;
    color: #212529;
    margin-bottom: 4px;
}
.stat-card-value--no-subtitle { margin-bottom: 0; }
.stat-card-subtitle {
    font-size: 12px;
    color: #6c757d;
}
body.theme-dark .stat-card {
    background-color: #2a2a2a;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
body.theme-dark .stat-card-icon { color: #0099ff; }
body.theme-dark .stat-card-title,
body.theme-dark .stat-card-subtitle { color: #9aa0a6; }
body.theme-dark .stat-card-value { color: #eee; }

/* ── Boundary Stats Panel (.bstats-*) ──────────────────────────── */

.bstats-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 6px;
    margin-bottom: 12px;
}
.bstats-grid--2 {
    grid-template-columns: repeat(2, 1fr);
}
.bstats-grid--3 {
    grid-template-columns: repeat(3, 1fr);
}
/* Canonical compact KPI tile (shares the recipe with .scorecard-chip):
   bordered, value-over-uppercase-label, status-colored left accent bar. */
.bstats-tile {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 6px var(--space-2);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
    border-left: 3px solid var(--bstats-accent, var(--color-border-strong));
    border-radius: var(--radius-md);
    text-align: left;
}
button.bstats-tile--action {
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
button.bstats-tile--action:hover {
    background: var(--color-bg-hover);
}
button.bstats-tile--active {
    --bstats-accent: var(--color-accent);
    background: var(--color-bg-hover);
}
button.bstats-tile--active .bstats-tile-value {
    color: var(--color-accent);
}
.bstats-tile-value {
    font-size: 14px;
    font-weight: 700;
    color: var(--color-fg-primary);
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}
.bstats-tile-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    line-height: 1.3;
}
.bstats-heading {
    margin: 14px 0 6px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-secondary);
}
/* Period qualifier appended to time-aware headings
   ("Permits · last 12 months"). Rendered in a muted, looser
   weight so it reads as context, not a separate header. */
.bstats-period-label {
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    font-size: 10.5px;
    opacity: 0.85;
}

/* Date-range picker at the top of the boundary stats panel.
   Mirrors the master picker in the filter panel but compact so
   it doesn't dominate the layout. */
.bstats-period-picker {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px;
    padding: 8px 10px;
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
}
.bstats-period-picker-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-secondary);
    flex: 0 0 auto;
}
.bstats-period-select {
    flex: 1 1 auto;
    padding: 5px 8px;
    font-size: 12.5px;
    font-family: inherit;
    color: var(--color-fg-primary);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-md);
    cursor: pointer;
}
.bstats-period-select:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 2px var(--color-accent);
}

/* Segmented horizontal bar (PRE/homestead) */
.bstats-segbar {
    display: flex;
    height: 10px;
    border-radius: 5px;
    overflow: hidden;
    background: var(--color-border-strong);
    margin-bottom: 5px;
}
.bstats-segbar-fill { height: 100%; transition: width 0.3s; }
.bstats-segbar--pre100  { background: #2f9e44; }
.bstats-segbar--preparti { background: #f59f00; }
.bstats-segbar--pre0    { background: #e03131; }
/* Race & ethnicity segments (Census tract panel). Palette chosen for
   accessibility — distinct hues, similar luminance — and to align
   loosely with Detroit's largest race groups appearing leftmost. */
.bstats-segbar--race-black { background: #1864ab; }
.bstats-segbar--race-white { background: #f59f00; }
.bstats-segbar--race-asian { background: #2f9e44; }
.bstats-segbar--race-other { background: #868e96; }
/* Tract panel "source: …" footer — small, low-contrast, no border. */
.bstats-source-note {
    font-size: 10px;
    color: #868e96;
    margin: 4px 0 8px 0;
    font-style: italic;
}

/* HUD point click popup — used by hud-points-layer.js for both
   LIHTC + Public Housing. Tight card with a colored left border
   that matches the layer's dot color (passed in inline style). */
.hud-popup { font-size: 12px; min-width: 220px; }
.hud-popup-title {
    padding: 4px 8px;
    font-weight: 600;
    font-size: 12px;
    color: #495057;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 6px;
}
.hud-popup-body { display: grid; gap: 4px; padding: 0 4px 4px; }
.hud-popup-row {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
}
.hud-popup-label { color: #6c757d; }
.hud-popup-value { color: #212529; font-weight: 500; text-align: right; }

/* 5-column grid variant — used by Fair Market Rents (one tile
   per bedroom bucket: studio, 1BR, 2BR, 3BR, 4BR). */
.bstats-grid--5 {
    grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
    gap: 4px;
}

/* Tile range — small min-max line under the main value, used by
   the Phase-4d "FMR by ZIP" card to show intra-boundary variation
   below the parcel-weighted mean. */
.bstats-tile-range {
    font-size: 10px;
    color: #6c757d;
    margin-top: 2px;
    white-space: nowrap;
}

/* Zoning click popup (Phase 5a) — code + category description. */
.zoning-popup { padding: 4px 6px; min-width: 160px; }
.zoning-popup-code {
    font-size: 14px;
    font-weight: 600;
    color: #212529;
    letter-spacing: 0.04em;
}
.zoning-popup-desc {
    font-size: 12px;
    color: #495057;
    margin-top: 2px;
}

/* Income Limits comparison table — 3 tiers × N household sizes. */
.bstats-il-table-wrap { overflow-x: auto; }
.bstats-il-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
}
.bstats-il-table th,
.bstats-il-table td {
    padding: 4px 6px;
    text-align: right;
    border-bottom: 1px solid #f1f3f5;
}
.bstats-il-table thead th {
    font-weight: 600;
    color: #6c757d;
    border-bottom: 1px solid #dee2e6;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.bstats-il-table tbody th {
    text-align: left;
    font-weight: 500;
    color: #495057;
    white-space: nowrap;
}
.bstats-segbar-legend {
    font-size: 10px;
    color: #6c757d;
    display: flex;
    gap: 10px;
    margin-bottom: 10px;
    align-items: center;
}
.bstats-legend-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 3px;
}

/* §5 figure in a boundary stats card. The desktop rail renders the
   300-viewBox figure ~1:1; wider hosts (the phone-class sheet spans the
   viewport) would scale the in-SVG type up 2-4x, so cap the rendered
   width to keep the figure's type near its native size. */
.bstats-card .pv-fig svg { max-width: 420px; }

/* Ranked rows (owners, classes, tax) */
.bstats-rank-list {
    margin-bottom: 8px;
}
.bstats-rank-row {
    margin-bottom: 5px;
}
.bstats-rank-meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 6px;
    margin-bottom: 2px;
}
.bstats-rank-label {
    font-size: 11px;
    color: #343a40;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bstats-rank-count {
    font-size: 11px;
    font-weight: 600;
    color: #495057;
    white-space: nowrap;
}
.bstats-rank-pct {
    font-weight: 400;
    color: #868e96;
}
.bstats-rank-track {
    height: 4px;
    background: #e9ecef;
    border-radius: 2px;
    overflow: hidden;
}
.bstats-rank-bar {
    height: 100%;
    background: #339af0;
    border-radius: 2px;
    transition: width 0.3s;
}
.bstats-rank-bar--tax { background: #f76707; }

/* Dark-mode overrides — only for the rank rows, whose base colors aren't
   tokenized yet. Tiles, headings, segbar, and the period picker are now
   token-driven (--color-bg-muted / --color-fg-* / --color-border*), so they
   flip in dark automatically without hand-maintained overrides. */
body.theme-dark .bstats-rank-label { color: #ced4da; }
body.theme-dark .bstats-rank-count { color: #adb5bd; }
body.theme-dark .bstats-rank-track { background: #373d44; }

/* ── Grouped boundary stats: jump strip + collapsible topic groups ──
   The ~15 flat stat sections are clustered into topic groups (see
   GROUP_DEFS in boundary-stats-service.js). A sticky icon strip jumps
   between them; first group is open, the rest collapsed on first paint. */

/* Sticky topic jump-strip. Lives inside the #panel-body scroll
   container, so z-index:2 (documented in-panel internal stacking) is
   enough to float it over the scrolling cards below. */
.bstats-jump {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    gap: var(--space-1);
    overflow-x: auto;
    scrollbar-width: none;
    padding: var(--space-2) 0;
    margin-bottom: var(--space-2);
    background: var(--color-bg-elevated);
    border-bottom: 1px solid var(--color-border);
}
.bstats-jump::-webkit-scrollbar { display: none; }
.bstats-jump-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--color-fg-secondary);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--motion-fast), border-color var(--motion-fast), color var(--motion-fast);
}
.bstats-jump-chip .bstats-group-glyph { color: var(--color-accent); }
.bstats-jump-chip:hover {
    background: var(--color-bg-hover);
    border-color: var(--color-accent);
    color: var(--color-accent-strong);
}

/* Collapsible topic group */
.bstats-group {
    margin-bottom: var(--space-3);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-lg);
    overflow: hidden;
    /* Offset the sticky jump-strip when a chip scrolls a group to top. */
    scroll-margin-top: 52px;
}
.bstats-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: var(--space-3);
    font-family: inherit;
    text-align: left;
    background: var(--color-bg-muted);
    border: none;
    cursor: pointer;
    transition: background var(--motion-fast);
}
.bstats-group-header:hover { background: var(--color-bg-hover); }
.bstats-group-head-main {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}
.bstats-group-head-main .bstats-group-glyph { color: var(--color-accent); }
.bstats-group-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--color-fg-primary);
    letter-spacing: 0.01em;
}
.bstats-group-chevron {
    flex: 0 0 auto;
    color: var(--color-fg-muted);
    transition: transform var(--motion-fast);
}
.bstats-group--collapsed .bstats-group-chevron { transform: rotate(-90deg); }
.bstats-group--collapsed .bstats-group-body { display: none; }
.bstats-group-body {
    padding: var(--space-3) var(--space-3) var(--space-1);
}
.bstats-card { margin-bottom: var(--space-2); }
.bstats-card:last-child { margin-bottom: 0; }
.bstats-card-title {
    margin: 0 0 var(--space-2);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted);
}

/* Per-group "Sources" footer → deep-links to pipelines.det.land/#<slug> */
.bstats-group-sources {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-2);
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px dashed var(--color-border-strong);
}
.bstats-group-sources-label {
    font-size: 9.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-fg-muted);
}
.bstats-group-sources-links {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}
.bstats-src-link {
    font-size: 11px;
    font-weight: 500;
    color: var(--color-accent);
    text-decoration: none;
    padding: 1px 7px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill);
    transition: background var(--motion-fast), border-color var(--motion-fast);
}
.bstats-src-link:hover {
    background: #e8f0fe;
    border-color: var(--color-accent);
}

/* "Shows on map" pin on actionable tiles — drill-to-map affordance. */
.bstats-tile--action { position: relative; }
.bstats-tile-mapflag {
    position: absolute;
    top: 4px;
    right: 4px;
    color: var(--color-fg-muted);
    opacity: 0.45;
    transition: opacity var(--motion-fast), color var(--motion-fast);
}
button.bstats-tile--action:hover .bstats-tile-mapflag { opacity: 1; color: var(--color-accent); }
button.bstats-tile--active .bstats-tile-mapflag { opacity: 1; color: #0060cc; }

/* Dark-mode overrides for the grouped layout */
body.theme-dark .bstats-jump { background: var(--color-bg-elevated); border-bottom-color: var(--color-border); }
body.theme-dark .bstats-jump-chip { background: var(--color-bg-muted); border-color: var(--color-border-strong); color: var(--color-fg-secondary); }
body.theme-dark .bstats-jump-chip:hover,
body.theme-dark .bstats-src-link:hover { background: #1e3a5f; border-color: var(--color-accent); }
body.theme-dark .bstats-group { border-color: var(--color-border-strong); }
body.theme-dark .bstats-group-header { background: var(--color-bg-muted); }
body.theme-dark .bstats-group-header:hover { background: var(--color-bg-hover); }
body.theme-dark .bstats-group-title { color: var(--color-fg-primary); }
body.theme-dark button.bstats-tile--active .bstats-tile-mapflag { color: #4da3ff; }

/* ---- Swap mode: viewer becomes full-bleed, map becomes PiP ----- */
/* OWNERSHIP GATE: every body.sv-swapped rule in this block is scoped
   :not(.survey-active). During an active survey session the runtime
   owns the swap/PiP layout (css/survey-runtime.css "Phase Z" split +
   its mobile PiP blocks) — the runner keeps body.sv-swapped set, and
   without the gate this block's #mapillary-container inset/size rules
   (same specificity, same !important strength) would fight the
   runtime's and the winner would be decided purely by stylesheet
   injection order. One owner per surface: outside a survey these
   rules own swap mode; inside one they must not even compete. */
/* Map PiP chrome is a plain overlay pinned to the same
   bottom-right rect as the body.sv-swapped #map rule below.
   Because both rects are defined purely in CSS with the same
   values, they can never drift out of sync (the old drag-
   enabled version had this problem during move gestures).
   Only the top 32px "header" strip intercepts pointer events
   — the rest of the chrome passes through so the map canvas
   stays fully interactive. */
#map-pip-chrome {
    position: absolute;
    right: 20px;
    bottom: 20px;
    width: min(480px, 34vw);
    height: min(320px, 36vh);
    pointer-events: none;
    display: none;
    /* Must sit above the body.sv-swapped #map rule (z-index 952
       below), or the map canvas swallows clicks on the swap-back
       button. */
    z-index: 954;
}
#map-pip-chrome > * { pointer-events: auto; }
body.sv-swapped:not(.survey-active) #map-pip-chrome { display: block; }
body.sv-swapped.sidebar-visible:not(.survey-active) #map-pip-chrome {
    right: calc(var(--sidebar-width, 300px) + 20px);
}

.map-pip-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 32px;
    background: rgba(248, 249, 250, 0.96);
    border-bottom: 1px solid #dee2e6;
    border-top-left-radius: 6px;
    border-top-right-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 8px 0 12px;
}
.map-pip-title {
    font-size: 12.5px;
    font-weight: 600;
    color: #495057;
    letter-spacing: 0.02em;
}
.map-pip-swap-btn {
    background: transparent;
    border: none;
    height: 26px;
    padding: 0 10px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 500;
    color: #495057;
    cursor: pointer;
    transition: background-color 0.15s;
}
.map-pip-swap-btn:hover { background: #e9ecef; }

/* In swap mode: the Mapillary container fills the main container. */
body.sv-swapped:not(.survey-active) #mapillary-container.floating-viewer {
    left: 0 !important;
    top: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    width: 100% !important;
    height: 100% !important;
    border-radius: 0;
    border: none;
    box-shadow: none;
    z-index: 920;
}
/* In swap mode the streetview is full-bleed and its chrome
   header (32px bar with title + time pill + swap/minimize
   buttons) sits at the top edge — overlapping the search bar
   at top-left. Hide the whole header in swap mode: the map
   PiP at bottom-right has its own .map-pip-header with a
   "Swap" button so the user can still flip back. The gallery
   pill is irrelevant here too — swap mode only happens in the
   sidebar layout and the gallery is dashboard-only (CSS hides
   the pill in #floating-viewer-host context). Time-pill date
   label is a small loss but the streetview content is the
   focus in this mode. */
body.sv-swapped:not(.survey-active) .floating-viewer-header {
    display: none;
}
/* …and the map shrinks to a floating PiP in the lower-right.
   Matches the viewer's default PiP footprint (and the
   #map-pip-chrome rect above) so a swap doesn't change the
   frame size, only which content fills it. */
body.sv-swapped:not(.survey-active) #map {
    position: absolute !important;
    left: auto;
    top: auto;
    right: 20px;
    bottom: 20px;
    width: min(480px, 34vw);
    height: min(320px, 36vh);
    /* border-box so the 1px border is INSIDE the declared
       width/height. Without this, the map's outer rect ends
       up 2px wider + 2px taller than #map-pip-chrome (both
       share the same right/bottom anchor but chrome has no
       border), which produces a visible 1–2px sliver of the
       map's top-left corner peeking out past the chrome. */
    box-sizing: border-box;
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.22);
    border: 1px solid #dee2e6;
    z-index: 952;
    overflow: hidden;
}
body.sv-swapped.sidebar-visible:not(.survey-active) #map {
    right: calc(var(--sidebar-width, 300px) + 20px);
}

/* Swap button (in floating viewer chrome) */
.floating-viewer-swap-btn {
    background: transparent;
    border: none;
    height: 26px;
    padding: 0 10px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 500;
    color: #495057;
    cursor: pointer;
    transition: background-color 0.15s;
}
.floating-viewer-swap-btn:hover { background: #e9ecef; }
.floating-viewer-swap-btn.active {
    background: #d0ebff;
    color: #1971c2;
}
body.theme-dark .map-pip-header {
    background: rgba(37, 37, 37, 0.96);
    border-bottom-color: #333;
}
body.theme-dark .map-pip-title { color: #ddd; }
body.theme-dark .map-pip-swap-btn { color: #ddd; }
body.theme-dark .map-pip-swap-btn:hover { background: #333; }
body.theme-dark .floating-viewer-swap-btn { color: #ddd; }
body.theme-dark .floating-viewer-swap-btn:hover { background: #333; }
body.theme-dark .floating-viewer-swap-btn.active {
    background: #0d2d4a;
    color: #9ac8ec;
}
/* (Selector was `body.theme-dark body.sv-swapped #map` — body inside
   body never matches, so the dark PiP border never applied. Fixed to
   a compound selector, gated like the rest of the block.) */
body.theme-dark.sv-swapped:not(.survey-active) #map {
    border-color: #333;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
}
/* ============================================================
   HISTORIC GALLERY — "this property over time"
   ============================================================
   Triggered by the .gallery-pill button next to .time-pill in
   the floating viewer header. Only available in the expanded
   dashboard (large profile view) — the small floating PiP
   hides the button via the selector below. When open, an
   overlay covers the streetview pane with a scrollable column
   of large per-year vintage cards (goobingdetroit aesthetic). */

/* Pill button styling — mirrors .time-pill's chrome so they
   sit visually paired in the header. Icon-only (no label)
   to keep the chrome compact. */
.gallery-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 22px;
    padding: 0;
    border-radius: 10px;
    border: 1px solid #c5e0fa;
    background: #e7f5ff;
    color: #1971c2;
    cursor: pointer;
    flex-shrink: 0;
    transition: background-color 0.12s, color 0.12s, border-color 0.12s;
    font-family: inherit;
}
.gallery-pill:hover:not(:disabled) {
    background: #d0ebff;
    border-color: #9ac8ec;
}
.gallery-pill--open,
.gallery-pill--open:hover:not(:disabled) {
    background: #1971c2;
    color: #fff;
    border-color: #1971c2;
}
.gallery-pill:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
body.theme-dark .gallery-pill {
    background: rgba(25, 113, 194, 0.22);
    border-color: #2c4a66;
    color: #9ac8ec;
}
body.theme-dark .gallery-pill:hover:not(:disabled) {
    background: rgba(25, 113, 194, 0.32);
    border-color: #3d6890;
}
body.theme-dark .gallery-pill--open,
body.theme-dark .gallery-pill--open:hover:not(:disabled) {
    background: #1971c2;
    color: #fff;
    border-color: #1971c2;
}
/* Hide the gallery button when the viewer is in the floating
   PiP host — the gallery is dashboard-only. The viewer
   container (#mapillary-container) moves between hosts at
   runtime, so we key off ancestor classes. */
#floating-viewer-host .gallery-pill { display: none; }

/* Full-screen compare overlay — Mapillary-style side panel +
   draggable-divider stage. Mounted at <body> level above
   everything; the streetview viewer in the dashboard keeps
   running underneath but is fully obscured by the dark
   background while the gallery is up. */
.historic-gallery-overlay {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    background: #1c2330;
    color: #f3f4f6;
    font-family: inherit;
    animation: historic-gallery-fadein 140ms ease-out;
}
@keyframes historic-gallery-fadein {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* ── Side panel (left, ~320px) ── */
.hg-side-panel {
    width: 320px;
    flex-shrink: 0;
    background: #161c26;
    border-right: 1px solid #232b39;
    overflow-y: auto;
    padding: 22px 18px 22px 22px;
    display: flex;
    flex-direction: column;
    gap: 22px;
    scrollbar-width: thin;
    scrollbar-color: #3a4456 transparent;
}
.hg-side-panel-section {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.hg-side-panel-label {
    font-size: 13px;
    font-weight: 500;
    color: #cbd2dd;
    letter-spacing: 0.2px;
}
.hg-side-panel-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* ── Card (vintage thumbnail) ── */
.hg-card {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 0;
    border: 2px solid transparent;
    background: #0f141d;
    border-radius: 8px;
    overflow: hidden;
    cursor: pointer;
    transition: border-color 140ms ease, transform 140ms ease;
    text-align: left;
}
.hg-card:hover {
    transform: translateY(-1px);
    border-color: #3a4456;
}
.hg-card.is-selected {
    border-color: #f3f4f6;
}
.hg-card[data-role="original"] { cursor: default; }
.hg-card[data-role="original"]:hover { transform: none; }
.hg-card-image {
    width: 100%;
    aspect-ratio: 2 / 1;
    background: #0a0e15;
    background-repeat: repeat-x;
    background-position-y: center;
    background-size: cover;
}
.hg-card-meta {
    position: absolute;
    left: 10px;
    bottom: 8px;
    background: rgba(255, 255, 255, 0.92);
    color: #1f2937;
    padding: 3px 9px;
    border-radius: 5px;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.1px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}

/* ── Stage (the compare area on the right) ── */
.hg-stage-wrap {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: stretch;
}
.hg-exit-btn {
    position: absolute;
    top: 18px;
    right: 22px;
    z-index: 2;
    background: rgba(255, 255, 255, 0.92);
    color: #1f2937;
    border: none;
    border-radius: 999px;
    padding: 6px 18px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
    transition: background-color 0.12s;
    font-family: inherit;
}
.hg-exit-btn:hover { background: #fff; }
.hg-stage {
    position: relative;
    flex: 1;
    overflow: hidden;
    user-select: none;
    cursor: grab;
    touch-action: none;
}
.hg-stage-half {
    position: absolute;
    inset: 0;
    background-color: #0a0e15;
    background-repeat: repeat-x;
    background-position-y: center;
    background-size: 400% auto; /* default — overridden inline */
}
.hg-stage-half--right {
    /* Right is on top; clip-path inset from left via JS so it
       only reveals the portion to the right of the divider. */
    clip-path: inset(0 0 0 50%);
}
.hg-stage-divider {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    margin-left: -1px;
    background: #ffffff;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
    cursor: ew-resize;
    z-index: 1;
    touch-action: none;
}
.hg-stage-divider-handle {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: #ffffff;
    color: #1f2937;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
    pointer-events: none;
}
.hg-stage-controls {
    padding: 18px 24px 22px;
    display: flex;
    justify-content: center;
    background: #1c2330;
}
.hg-stage-slider {
    width: min(560px, 70%);
    -webkit-appearance: none;
    appearance: none;
    height: 6px;
    background: #3a4456;
    border-radius: 3px;
    outline: none;
}
.hg-stage-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #ffffff;
    border: 2px solid #1c2330;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.hg-stage-slider::-moz-range-thumb {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #ffffff;
    border: 2px solid #1c2330;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}

/* Light-mode override: the gallery's dark theme is intentional
   and stays the same in light mode (it's a dedicated UI moment,
   like a video lightbox). No theme variants needed. */

/* ============================================================
   BASEMAP TOGGLE — rail thumbnail button (order:5, top of rail)
   ============================================================
   Mounts in #icon-rail at order:5, before Filter (10).
   Shows a 36×36 thumbnail of the OTHER basemap so the button
   reads as both a control and a live view-mode indicator.
   No positioning rules needed — it's just a rail button. */

.basemap-rail-btn {
    position: relative;
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: var(--radius-md);
    border: 2px solid rgba(255, 255, 255, 0.75);
    overflow: hidden;
    cursor: pointer;
    background: #d8e4f0; /* placeholder while tile loads */
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
    transition: border-color var(--motion-fast),
                box-shadow var(--motion-fast);
    flex-shrink: 0;
}
.basemap-rail-btn:hover {
    border-color: rgba(255, 255, 255, 1);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.38);
}
.basemap-rail-btn:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
}

/* Thumbnail fills the button */
.basemap-rail-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Label strip over the bottom edge */
.basemap-rail-label {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    font-size: 7px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    text-align: center;
    padding: 2px 0 3px;
    line-height: 1;
}

/* Stronger scrim when aerial active — road-map thumb is lighter */
.basemap-rail-btn--aerial .basemap-rail-label {
    background: rgba(0, 0, 0, 0.62);
}

body.theme-dark .basemap-rail-btn {
    border-color: rgba(255, 255, 255, 0.5);
}

/* ── Icon rail: don't scrunch in swap mode ──────────────────
   When body.sv-swapped #map shrinks to a PiP, the rail used
   to live inside that tiny box. It's now in #main-container
   so this rule is just an extra safety guard: keep the rail
   at the viewport top-right even in swap mode.
   :not(.survey-active) — same ownership gate as the swap/PiP
   block above; survey-runtime.css positions the rail during a
   session. */
body.sv-swapped:not(.survey-active) #icon-rail {
    right: 10px;
}
body.sv-swapped.sidebar-visible:not(.survey-active) #icon-rail {
    right: calc(var(--sidebar-width, 300px) + 10px);
}
@media (max-width: 768px), (max-height: 600px) {
    body.sv-swapped.sidebar-visible:not(.survey-active) #icon-rail {
        right: 10px;
    }
}

/* ─────────────────────────────────────────────────────────────
 * Portfolio card — v1 "sneaky link" on the parcel panel surfacing
 * the controller_profile data (rest of this owner's holdings, top
 * neighborhoods, named people). Lives under the Ownership & Tax card.
 * Slug carries through to the "View full portfolio" link for the
 * routed page that lands in slice 3.
 * ───────────────────────────────────────────────────────────── */
.portfolio-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    font-size: 13px;
    color: var(--color-fg-primary);
}
/* Header row that leads INTO the portfolio card. Sits in the same
   detail-row rhythm as the assessor labels above ("Publicly Owned:",
   "Taxpayer:", …) so the eye reads it as the next field-row, with the
   ⓘ where the value would go and the card below as the block answer. */
.portfolio-section-header {
    /* detail-row already gives us flex + space-between + the 1px bottom
       border; we just neutralize that border so the card below visually
       attaches instead of being separated by a divider. */
    border-bottom: none;
    padding-bottom: var(--space-2);
}
.portfolio-section-info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--color-fg-muted);
    text-decoration: none;
    border-radius: var(--radius-pill);
    padding: 2px;
    transition: color 0.15s, background-color 0.15s;
}
.portfolio-section-info:hover {
    color: var(--color-accent);
    background: var(--color-bg-hover);
}
.portfolio-headline {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.portfolio-stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}
.portfolio-stat strong {
    color: var(--color-accent-strong);
    font-weight: 600;
    font-size: 15px;
    margin-right: 4px;
}
.portfolio-mailing {
    color: var(--color-fg-muted);
    font-size: 12px;
}
.portfolio-row {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.portfolio-row-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-fg-muted);
}
.portfolio-row-value {
    color: var(--color-fg-secondary);
    line-height: 1.45;
}
.portfolio-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
}
.portfolio-chip {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    padding: 3px var(--space-2);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    font-size: 12px;
    color: var(--color-fg-secondary);
    white-space: nowrap;
}
.portfolio-chip em {
    font-style: normal;
    font-weight: 600;
    color: var(--color-fg-primary);
}
.portfolio-class-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.portfolio-class-list li {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    color: var(--color-fg-secondary);
}
.portfolio-class-label {
    color: var(--color-fg-secondary);
}
.portfolio-class-count {
    color: var(--color-fg-primary);
    font-variant-numeric: tabular-nums;
    font-weight: 500;
}
.portfolio-unit-note {
    font-weight: 400;
    color: var(--color-fg-muted);
    font-size: 12px;
}
.portfolio-link {
    align-self: flex-start;
    margin-top: var(--space-1);
    padding: var(--space-1) var(--space-2);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--color-accent-strong);
    text-decoration: none;
    border-radius: var(--radius-md);
    transition: background-color 0.15s, color 0.15s;
}
.portfolio-link:hover {
    background: var(--color-bg-hover);
    color: var(--color-accent);
}

/* ─────────────────────────────────────────────────────────────
 * Portfolio-highlight button — sits inside the parcel panel's
 * Ownership group, lights up the controller's parcels on the main
 * map the user is already looking at. Click toggles via the
 * `det:portfolio-highlight` custom event (see selection-panel.js +
 * portfolio-highlight-layer.js).
 * ───────────────────────────────────────────────────────────── */
.portfolio-highlight-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding: var(--space-2) var(--space-3);
    font-size: 12.5px;
    font-weight: 600;
    color: var(--color-accent-strong);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-accent);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
}
.portfolio-highlight-btn:hover {
    background: var(--color-bg-hover);
}
.portfolio-highlight-btn--active {
    background: var(--color-accent);
    color: #fff;
    border-color: var(--color-accent-strong);
}
.portfolio-highlight-btn--active:hover {
    background: var(--color-accent-strong);
}
body.theme-dark .portfolio-highlight-btn {
    background: var(--color-bg-elevated);
    border-color: var(--color-accent);
}

/* ─────────────────────────────────────────────────────────────
 * Related-parcels banner — rendered above the Ownership group
 * when the parcel sits in a multi-parcel `related_parcel_id`
 * cluster (condo blocks, building complexes, platted subdivisions).
 * Each sibling row selects the matching parcel via setParcel.
 * ───────────────────────────────────────────────────────────── */
.related-parcels-banner {
    margin: var(--space-3) 0 var(--space-4);
    padding: var(--space-3) var(--space-3);
    background: var(--color-bg-muted);
    border-left: 3px solid var(--color-accent);
    border-radius: var(--radius-md);
}
.related-parcels-headline {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-fg-secondary);
    margin-bottom: var(--space-2);
}
.related-parcels-highlight-btn {
    margin-top: 0;
}
.related-parcels-caption {
    margin-top: var(--space-2);
    font-size: 11.5px;
    color: var(--color-fg-muted);
    font-style: italic;
}
body.theme-dark .related-parcels-banner {
    background: var(--color-bg-muted);
    border-left-color: var(--color-accent);
}

/* ─────────────────────────────────────────────────────────────
 * Portfolio card — slice 2 additions: person-link drill-down,
 * operator mode (with back link + controllers list), loading state.
 * Keeps the same visual language as the controller view so the swap
 * feels like a mode change, not a different surface.
 * ───────────────────────────────────────────────────────────── */
.portfolio-people {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: baseline;
}
.portfolio-person-link {
    color: var(--color-accent-strong);
    text-decoration: none;
    border-bottom: 1px dotted var(--color-border-strong);
    font-weight: 500;
    transition: color 0.15s, border-color 0.15s;
}
.portfolio-person-link:hover {
    color: var(--color-accent);
    border-bottom-color: var(--color-accent);
}
.portfolio-person-plain {
    color: var(--color-fg-secondary);
}
.portfolio-person-sep {
    color: var(--color-fg-muted);
    margin: 0 2px;
}

/* Operator-mode header — name above the stats row, back link sits
 * inline at the top of the card. */
.portfolio-card[data-portfolio-mode="operator"] .portfolio-headline {
    gap: var(--space-2);
}
.portfolio-operator-name {
    font-weight: 600;
    font-size: 14px;
    color: var(--color-fg-primary);
    letter-spacing: 0.01em;
}
.portfolio-back-link {
    align-self: flex-start;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-fg-muted);
    text-decoration: none;
    padding: 2px var(--space-2);
    border-radius: var(--radius-md);
    transition: background-color 0.15s, color 0.15s;
}
.portfolio-back-link:hover {
    background: var(--color-bg-hover);
    color: var(--color-fg-primary);
}

/* Controllers list (operator mode) */
.portfolio-ctrl-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.portfolio-ctrl-list li {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--color-border);
}
.portfolio-ctrl-list li:last-child { border-bottom: 0; }
.portfolio-ctrl-name {
    font-weight: 500;
    color: var(--color-fg-primary);
    font-size: 12.5px;
}
.portfolio-ctrl-meta {
    font-size: 11.5px;
    color: var(--color-fg-muted);
    font-variant-numeric: tabular-nums;
}
.portfolio-ctrl-more {
    font-size: 11.5px;
    color: var(--color-fg-muted);
    margin-top: 2px;
    font-style: italic;
}

/* In-flight drill-down — dim the card while fetchOperatorProfile resolves. */
.portfolio-card.portfolio-loading {
    opacity: 0.6;
    pointer-events: none;
    transition: opacity 0.15s;
}

/* ─────────────────────────────────────────────────────────────
 * Ownership profile routed page — /owner/:slug & /operator/:slug.
 * Full-page dashboard view (slice 3 of the ownership-profile UI).
 * Header, stats strip, named-people row, two-column hood/class
 * breakdown, full property list (with paginated "Show more").
 * Operator mode adds a controllers list section.
 * ───────────────────────────────────────────────────────────── */
.ownership-nav {
    position: fixed;
    top: var(--space-3);
    left: var(--space-3);
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    z-index: var(--z-overlay, 200);
}
.ownership-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-3);
    background: var(--color-bg-elevated);
    color: var(--color-fg-secondary);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    text-decoration: none;
    font-size: 12px;
    font-weight: 600;
    box-shadow: var(--shadow-sm);
    cursor: pointer;
    max-width: 60vw;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ownership-chip > span:not(.ownership-chip-arrow) {
    overflow: hidden;
    text-overflow: ellipsis;
}
.ownership-chip:hover {
    color: var(--color-fg-primary);
    background: var(--color-bg-hover);
}
.ownership-chip-arrow {
    color: var(--color-fg-muted);
    font-weight: 700;
}
.ownership-chip-home {
    color: var(--color-fg-muted);
}

/* ─────────────────────────────────────────────────────────────
 * Profile parcel panel — in-map docked parcel detail panel on
 * the ownership profile pages (/owner/:slug, /operator/:slug).
 * Slides in from the right edge of the embedded portfolio map
 * when a parcel is clicked (tile or map). On phone-class
 * viewports it becomes a bottom sheet stacked under the map.
 *
 * The mount (.ownership-map-mount) grows from its idle 360px to
 * a taller "panel open" height via the .has-docked-panel class
 * so the rich parcel record has room without crushing the map.
 * ───────────────────────────────────────────────────────────── */
.ownership-map-mount.has-docked-panel {
    height: 620px;
    transition: height var(--motion-md, 200ms) ease;
}

.profile-parcel-panel {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 360px;
    max-width: 92%;
    overflow-y: auto;
    background: var(--color-bg-elevated);
    border-left: 1px solid var(--color-border);
    box-shadow: -8px 0 24px rgba(15, 23, 42, 0.12);
    padding: var(--space-5) var(--space-4) var(--space-6);
    color: var(--color-fg-primary);
    z-index: 4;
    transform: translateX(100%);
    transition: transform var(--motion-md, 200ms) ease;
}
.profile-parcel-panel--open {
    transform: translateX(0);
}
.profile-parcel-panel[hidden] {
    display: none;
}
.profile-parcel-panel-close {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    background: var(--color-bg-muted);
    color: var(--color-fg-secondary);
    cursor: pointer;
    z-index: 1;
}
.profile-parcel-panel-close:hover {
    color: var(--color-fg-primary);
    background: var(--color-bg-hover);
}
.profile-parcel-panel-loading {
    padding: var(--space-8) 0;
    text-align: center;
    color: var(--color-fg-muted);
    font-size: 13px;
}
.profile-parcel-panel-header {
    margin: var(--space-1) var(--space-6) var(--space-4) 0;
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-border);
}
.profile-parcel-panel-title {
    margin: 0;
    font-size: 17px;
    font-weight: 700;
    line-height: 1.25;
}
.profile-parcel-panel-scorecard {
    margin-bottom: var(--space-4);
}
.profile-parcel-panel-cta {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    background: var(--color-accent);
    color: var(--color-on-accent, #fff);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
}
.profile-parcel-panel-cta:hover {
    filter: brightness(1.08);
}

/* Phone: panel becomes a bottom sheet inside the map. Mount grows
 * to fit both the map and the sheet stacked above each other. */
@media (max-width: 768px), (max-height: 600px) {
    .ownership-map-mount.has-docked-panel {
        height: 80vh;
    }
    .profile-parcel-panel {
        top: auto;
        left: 0;
        right: 0;
        height: 60%;
        width: 100%;
        max-width: 100%;
        border-left: 0;
        border-top: 1px solid var(--color-border);
        box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.12);
        border-radius: var(--radius-lg, 14px) var(--radius-lg, 14px) 0 0;
        transform: translateY(100%);
    }
    .profile-parcel-panel--open {
        transform: translateY(0);
    }
}

.ownership-page {
    max-width: 960px;
    margin: 0 auto;
    /* Extra top padding clears the position:fixed back-chip (~40px tall)
     * so it never overlaps the header on narrow viewports. */
    padding: var(--space-10) var(--space-4) var(--space-8);
    color: var(--color-fg-primary);
}
.ownership-loading,
.ownership-error {
    padding: var(--space-8) var(--space-4);
    text-align: center;
    color: var(--color-fg-muted);
}
.ownership-error {
    color: var(--color-fg-secondary);
}

/* ── Operator search (typeahead) ──────────────────────────────────────────
 * Matches the ownership-page card style:
 * `--color-bg-elevated` surface, `--color-border` hairline, `--radius-md`,
 * `--shadow-md` for the floating dropdown. The popover sits at
 * `--z-dropdown` (1200) — above .ownership-content cards (default flow)
 * but below modals.
 * ──────────────────────────────────────────────────────────────────────── */

.ownership-search-slot {
    margin-bottom: var(--space-5);
}
.op-search {
    position: relative;
}
.op-search-input-wrap {
    position: relative;
    display: flex;
    align-items: center;
}
.op-search-icon {
    position: absolute;
    left: var(--space-4);
    color: var(--color-fg-muted);
    pointer-events: none;
}
.op-search-input {
    width: 100%;
    box-sizing: border-box;
    /* Left padding clears the 16px icon + a token of breathing room. */
    padding: 14px var(--space-4) 14px calc(var(--space-4) + 16px + var(--space-3));
    font: inherit;
    font-size: 15px;
    color: var(--color-fg-primary);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    transition: border-color var(--motion-fast) ease, box-shadow var(--motion-fast) ease;
}
.op-search-input::placeholder {
    color: var(--color-fg-muted);
}
.op-search-input:hover {
    border-color: var(--color-border-strong);
}
.op-search-input:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent);
}
/* Strip the browser-default clear button on type=search — we handle Esc
 * to clear so the native UI control is redundant + visually noisy. */
.op-search-input::-webkit-search-cancel-button { display: none; }

.op-search-results {
    position: absolute;
    top: calc(100% + var(--space-1));
    left: 0;
    right: 0;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    max-height: 420px;
    overflow-y: auto;
    /* Above flow content (cards, twocol grid). Below modals/overlays. */
    z-index: var(--z-dropdown);
}
.op-search-empty {
    padding: var(--space-4);
    color: var(--color-fg-muted);
    font-size: 14px;
    text-align: center;
}
.op-search-result {
    display: block;
    width: 100%;
    text-align: left;
    padding: var(--space-3) var(--space-4);
    background: transparent;
    border: 0;
    border-top: 1px solid var(--color-border);
    color: var(--color-fg-primary);
    cursor: pointer;
    font: inherit;
    transition: background var(--motion-fast) ease;
}
.op-search-result:first-of-type { border-top: 0; }
.op-search-result:hover,
.op-search-result.is-active {
    background: color-mix(in srgb, var(--color-accent) 7%, transparent);
}
.op-search-result-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
}
.op-search-result-name {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--color-fg-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Kind chip: which section a result comes from (Operator / Owner / Lender),
   color-keyed to the same accents the landing-page sections use. */
.op-search-result-kind {
    flex: none;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 2px 6px;
    border-radius: var(--radius-sm, 4px);
    color: var(--color-fg-secondary);
    background: var(--color-bg-muted);
}
.op-search-result-kind--operator { color: var(--accent-cyan);   background: color-mix(in srgb, var(--accent-cyan) 14%, transparent); }
.op-search-result-kind--owner    { color: var(--accent-indigo); background: color-mix(in srgb, var(--accent-indigo) 14%, transparent); }
.op-search-result-kind--lender   { color: var(--accent-green);  background: color-mix(in srgb, var(--accent-green) 16%, transparent); }

/* ── Viz primitives (js/shared/viz.js) — token-themed, dark-mode safe ─────── */
.viz-bars { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 2px; }
.viz-bar-row {
    display: grid;
    grid-template-columns: minmax(80px, 34%) 1fr auto;
    align-items: center;
    gap: 10px;
    font-size: 13px;
}
.viz-bar-label {
    color: var(--color-fg-secondary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.viz-bar-track {
    background: var(--color-bg-muted);
    border-radius: var(--radius-sm, 4px);
    height: 10px; overflow: hidden;
}
.viz-bar-fill { display: block; height: 100%; border-radius: var(--radius-sm, 4px); opacity: 0.85; }
.viz-bar-val { color: var(--color-fg-primary); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.viz-bar-sub { color: var(--color-fg-muted); font-weight: 400; }

.viz-spark-wrap { margin: 6px 0 2px; }
.viz-spark { display: block; }
.viz-spark-area { opacity: 0.13; }
.viz-spark-line { fill: none; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; }
.viz-spark-labels {
    display: flex; justify-content: space-between;
    font-size: 11px; color: var(--color-fg-muted); margin-top: 2px;
}

.viz-share { margin: 6px 0 2px; }
.viz-share-bar {
    display: flex; height: 14px; width: 100%;
    border-radius: var(--radius-sm, 4px); overflow: hidden;
    background: var(--color-bg-muted);
}
.viz-share-seg { display: block; height: 100%; }
.viz-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size: 12px; color: var(--color-fg-secondary); }
.viz-legend-item { display: inline-flex; align-items: center; }
.viz-legend-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 5px; }
.viz-legend-val { color: var(--color-fg-muted); margin-left: 4px; }

.viz-yearcols { margin: 6px 0 2px; }
.viz-yearcols-svg { display: block; width: 100%; height: auto; }
.viz-yearcols-col--prov { opacity: 0.55; }
.viz-yearcols-total {
    fill: var(--color-fg-secondary);
    font-size: 10px; font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-anchor: middle;
}
.viz-yearcols-year {
    fill: var(--color-fg-muted);
    font-size: 10px;
    font-variant-numeric: tabular-nums;
    text-anchor: middle;
}

/* US proportional-symbol bubble map (js/shared/us-geo.js) */
.viz-map-wrap { margin: 10px 0 4px; }
.viz-map { display: block; width: 100%; height: auto; }
.viz-map-land {
    fill: var(--color-bg-muted);
    fill-opacity: 0.85;
    stroke: var(--color-fg-muted);
    stroke-width: 1.6;
    stroke-linejoin: round;
}
.viz-map-bubble circle { fill-opacity: 0.42; stroke-width: 1.2; stroke-opacity: 0.95; }
.viz-map-bubble:hover circle { fill-opacity: 0.62; }
.viz-map-lbl {
    fill: var(--color-fg-primary); font-weight: 700;
    text-anchor: middle; pointer-events: none;
    font-variant-numeric: tabular-nums;
}
.viz-map-legend {
    display: flex; flex-wrap: wrap; align-items: center; gap: 16px;
    margin-top: 6px; font-size: 12px; color: var(--color-fg-secondary);
}
.viz-map-legend-cap { color: var(--color-fg-muted); }
.viz-map-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.viz-map-legend-dot { display: inline-block; border-radius: 50%; opacity: 0.42; flex: none; }

/* Expandable ranked lists — show 5, step up to 25, then all (js: wireExpandables).
   State lives on data-state; CSS hides the overflow so collapse/expand is layout-
   only (no re-render). Works for <ul><li> lists and the card-style list used by
   Hidden portfolios (.ownership-explist-cards). */
.ownership-explist[data-state="0"] .ownership-ctrl-list > li:nth-child(n+6),
.ownership-explist[data-state="0"] .ownership-explist-cards > :nth-child(n+6) { display: none; }
.ownership-explist[data-state="1"] .ownership-ctrl-list > li:nth-child(n+26),
.ownership-explist[data-state="1"] .ownership-explist-cards > :nth-child(n+26) { display: none; }
.ownership-list-toggle {
    display: inline-flex; align-items: center; gap: 6px;
    margin-top: var(--space-2);
    padding: 6px 12px;
    font: inherit; font-size: 13px; font-weight: 600;
    color: var(--bp-accent, var(--color-accent));
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    cursor: pointer;
    transition: background-color 80ms ease, border-color 80ms ease;
}
.ownership-list-toggle:hover { background: var(--color-bg-muted); border-color: var(--bp-accent, var(--color-accent)); }

/* Interactive "explore the landscape" panel — bounds the filter bar + the only
   two sections it affects (tiles + concentration) in one tinted container, so the
   filter clearly scopes to this panel and not the citywide chapters below it. */
.bp-body .ownership-explorer {
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    margin-bottom: var(--space-5);
}
.ownership-explorer-head { margin-bottom: var(--space-3); }
.ownership-explorer-title {
    margin: 0 0 2px; font-size: 15px; font-weight: 700; color: var(--color-fg-primary);
}
.ownership-explorer-sub { margin: 0; font-size: 13px; color: var(--color-fg-muted); line-height: 1.4; }

/* Population filter bar (/ownership landscape) — combinable segmented controls +
   a council-district select that re-slice the tiles + concentration below. */
.ownership-filter-bar {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px 18px;
    margin-bottom: var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
}
.ownership-filter-group { display: flex; flex-direction: column; gap: 4px; }
.ownership-filter-label {
    font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--color-fg-muted);
}
.ownership-filter-seg {
    display: inline-flex; padding: 2px; gap: 2px;
    background: var(--color-bg-muted);
    border-radius: 999px;
}
.ownership-filter-btn {
    appearance: none; border: none; cursor: pointer;
    padding: 5px 11px; font: inherit; font-size: 12.5px; font-weight: 500;
    color: var(--color-fg-secondary);
    background: transparent; border-radius: 999px; white-space: nowrap;
    transition: background-color 80ms ease, color 80ms ease;
}
.ownership-filter-btn:hover { color: var(--color-fg-primary); }
.ownership-filter-btn.is-active {
    background: var(--color-bg-elevated);
    color: var(--bp-accent, var(--color-accent));
    box-shadow: var(--shadow-sm);
}
.ownership-filter-select {
    appearance: auto; font: inherit; font-size: 13px;
    padding: 6px 10px; color: var(--color-fg-primary);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border); border-radius: var(--radius-md, 8px);
    cursor: pointer;
}
.ownership-filter-context {
    margin-bottom: var(--space-3); font-size: 13px; color: var(--color-fg-secondary);
}
.ownership-filter-context-hint { color: var(--color-fg-muted); }
/* Loading state while a filtered slice is in flight — dim + freeze, no layout jump. */
.ownership-landscape-slot.is-loading { opacity: 0.45; pointer-events: none; transition: opacity 120ms ease; }
.op-search-result-llcs {
    margin-top: 3px;
    font-size: 13px;
    color: var(--color-fg-secondary);
    line-height: 1.35;
    /* Single-line + ellipsis so wide alias lists don't blow up row height. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.op-search-result-more {
    color: var(--color-fg-muted);
}
.op-search-result-count {
    margin-top: 4px;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.op-search-result mark {
    background: color-mix(in srgb, var(--color-accent) 25%, transparent);
    color: inherit;
    padding: 0 1px;
    border-radius: 2px;
    font-weight: inherit;
}

.ownership-header {
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}
.ownership-name {
    font-size: 26px;
    font-weight: 700;
    margin: 0 0 var(--space-1);
    line-height: 1.2;
    letter-spacing: -0.01em;
}
.ownership-subline {
    color: var(--color-fg-muted);
    font-size: 13px;
}
.ownership-badge {
    display: inline-block;
    margin-top: var(--space-2);
    padding: 2px var(--space-2);
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.ownership-badge-public {
    background: var(--color-bg-muted);
    color: var(--color-fg-muted);
    border: 1px solid var(--color-border-strong);
}
.ownership-badge-warn {
    background: var(--color-warning-bg, #fef3c7);
    color: #92400e;
    border: 1px solid var(--color-warning-border, #fde68a);
}
.ownership-badge-nonprofit {
    background: #e0f2fe;
    color: #075985;
    border: 1px solid #bae6fd;
}

.ownership-stats-strip {
    display: flex;
    gap: var(--space-4);
    margin-bottom: var(--space-5);
    padding: var(--space-4);
    background: var(--color-bg-muted);
    border-radius: var(--radius-lg);
}
.ownership-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
}
.ownership-stat + .ownership-stat {
    border-left: 1px solid var(--color-border);
    padding-left: var(--space-4);
}
.ownership-stat strong {
    font-size: 24px;
    font-weight: 700;
    color: var(--color-accent-strong);
    font-variant-numeric: tabular-nums;
}
.ownership-stat span {
    font-size: 12px;
    color: var(--color-fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}

.ownership-section {
    margin-bottom: var(--space-6);
}
.ownership-section-title {
    margin: 0 0 var(--space-3);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted);
}

/* Sub-chapters: inside a profile chapter, each category section reads as its own
   block — the bare full-width sections get the same card treatment the two-column
   .ownership-card items already have, so the chapter (big title) → sub-chapter
   (card) hierarchy is legible. Scoped to .bp-body so only the profile pages get it.
   The top rail is the chapter's accent color (same as the chapter-title underline),
   so every card reads as belonging to its parent chapter instead of floating as an
   island with no context. */
.bp-body .ownership-section:not(.ownership-card) {
    position: relative;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-top: 3px solid var(--bp-accent, var(--color-accent));
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    margin-bottom: var(--space-4);
}
/* Unify the two heading conventions used inside cards (.ownership-section-title is
   an <h2>, .ownership-section-subhead an <h3>) so peer cards in the same chapter
   read identically as sub-chapter headers — was inconsistent (one uppercase-muted,
   one sentence-case) which made the cards feel unrelated. */
.bp-body .ownership-section-title,
.bp-body .ownership-section-subhead {
    margin: 0 0 var(--space-3);
    font-size: 15px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: normal;
    color: var(--color-fg-primary);
}

/* Property-compliance chapter — BSEED record dimensions listed per affected
   property, each address linking to the parcel page in a new tab. */
.bp-body .ownership-compliance-group { margin: 0 0 var(--space-5); }
.bp-body .ownership-compliance-group.is-warn .ownership-section-subhead {
    color: var(--color-warning-accent);
}
.bp-body .ownership-compliance-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 0 var(--space-4);
}
.bp-body .ownership-compliance-item {
    display: flex;
    flex-direction: column;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--color-border);
    min-width: 0;
}
.bp-body .ownership-compliance-group.is-warn .ownership-compliance-item {
    border-bottom-color: var(--color-warning-border);
}
.bp-body .ownership-compliance-link {
    color: var(--color-accent);
    text-decoration: none;
    font-weight: 500;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bp-body .ownership-compliance-link:hover { text-decoration: underline; }
.bp-body .ownership-compliance-detail {
    font-size: 12px;
    color: var(--color-fg-secondary);
}
.bp-body .ownership-compliance-due {
    font-weight: 600;
    color: var(--color-warning-accent);
}
.bp-body .ownership-compliance-meta {
    color: var(--color-fg-muted);
    font-size: 12px;
}
.bp-body .ownership-compliance-more {
    grid-column: 1 / -1;
    color: var(--color-fg-muted);
    font-size: 12px;
    padding-top: var(--space-2);
}
/* Tighten the chapter→sub-chapter rhythm: a bit more air above each chapter, and
   nest the cards slightly under the chapter title so the grouping reads top-down. */
.bp-body .bp-chapter { margin-top: var(--space-6); }

/* The ownership pages float a fixed top-left nav chip cluster (.ownership-nav,
   "← Back to…" + det.land + account). The boundary pages don't, so their sticky
   chapter TOC sits at top:0 — but here it collides with that floating nav on
   scroll. Park the sticky TOC below the nav row so they stack instead of overlap. */
.ownership-page .bp-toc {
    top: 56px;
    z-index: 4;            /* under the nav (z 200) so the nav stays clickable */
    /* Opaque + contained so it reads as a layer ABOVE content instead of a
       translucent strip that smears the map (or whatever) bleeding through it. */
    background: var(--color-bg-elevated);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom: none;
    border-radius: var(--radius-md, 8px);
    box-shadow: var(--shadow-sm);
    padding: 6px 8px;
}
@media (max-width: 768px), (max-height: 600px) {
    .ownership-page .bp-toc { top: 50px; }
}

/* Vertical TOC rail (profileShell railTOC=true) — used by /ownership and every
   sub-profile page (owner/operator/lender/entity/address). The chapter nav becomes
   a sticky column down the left with the content beside it. Pages opt in by adding
   .ownership-page--rail (widens the page so the content column stays roomy next to
   the rail). Falls back to the horizontal top bar on phone-class widths (a left
   rail is impractical there). */
.ownership-page--rail { max-width: 1160px; }
.bp-layout--rail {
    display: grid;
    grid-template-columns: 190px minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
}
.bp-layout--rail .bp-toc {
    position: sticky;
    top: 64px;
    z-index: 4;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    max-height: calc(100vh - 88px);
    overflow-y: auto;
    overflow-x: hidden;
    /* reset the horizontal-bar chrome — the rail sits in flow, not as a floating strip */
    margin: 0;
    padding: 6px;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md, 8px);
    box-shadow: var(--shadow-sm);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
.bp-layout--rail .bp-toc__link {
    display: block;
    flex: 0 0 auto;
    text-align: left;
    white-space: normal;
    border-radius: var(--radius-sm, 6px);
    padding: 7px 10px;
}
@media (max-width: 768px), (max-height: 600px) {
    .bp-layout--rail { display: block; }
    .bp-layout--rail .bp-toc {
        position: sticky;
        top: 50px;
        flex-direction: row;
        align-items: center;
        overflow-x: auto;
        overflow-y: hidden;
        max-height: none;
        margin-bottom: 8px;
    }
    .bp-layout--rail .bp-toc__link { white-space: nowrap; }
}

/* Two-level rail TOC — chapter group headers with nested section links + a
   collapse-all control at the foot. */
.bp-layout--rail .bp-toc__group { display: flex; flex-direction: column; gap: 1px; margin-bottom: 5px; }
.bp-layout--rail .bp-toc__chapter {
    font-size: 12px; font-weight: 700; color: #495057;
    padding: 6px 10px 3px;
}
.bp-layout--rail .bp-toc__section {
    display: block; text-align: left; white-space: normal;
    font-size: 12.5px; color: #6c757d; text-decoration: none;
    padding: 4px 10px 4px 18px; border-radius: var(--radius-sm, 6px);
    transition: background 80ms ease, color 80ms ease;
}
.bp-layout--rail .bp-toc__section:hover { background: rgba(92,107,192,0.08); color: #5c6bc0; }
.bp-layout--rail .bp-toc__section.is-active { background: rgba(92,107,192,0.14); color: #3a44a0; font-weight: 600; }
.bp-layout--rail .bp-toc__chapter.is-active,
.bp-layout--rail .bp-toc__chapter.is-active-parent { color: #3a44a0; }
.bp-toc__collapse-all {
    margin-top: 4px; padding: 7px 10px; width: 100%;
    font-size: 12px; color: #6c757d;
    background: none; border: none; border-top: 1px solid var(--color-border);
    text-align: left; cursor: pointer;
}
.bp-toc__collapse-all:hover { color: #5c6bc0; }
body.theme-dark .bp-layout--rail .bp-toc__chapter { color: #c9d1d9; }
body.theme-dark .bp-layout--rail .bp-toc__section { color: #9aa0a6; }
body.theme-dark .bp-layout--rail .bp-toc__section:hover { background: rgba(130,177,255,0.10); color: #82b1ff; }
body.theme-dark .bp-layout--rail .bp-toc__section.is-active { background: rgba(130,177,255,0.16); color: #cdd9ff; }
body.theme-dark .bp-layout--rail .bp-toc__chapter.is-active,
body.theme-dark .bp-layout--rail .bp-toc__chapter.is-active-parent { color: #cdd9ff; }
/* Mobile: rail collapses to a horizontal chapter strip — hide the nested
   sections + collapse-all, keep chapter pills. */
@media (max-width: 768px), (max-height: 600px) {
    .bp-layout--rail .bp-toc__group { flex-direction: row; margin-bottom: 0; }
    .bp-layout--rail .bp-toc__section,
    .bp-toc__collapse-all { display: none; }
    .bp-layout--rail .bp-toc__chapter { padding: 6px 12px; white-space: nowrap; }
    .bp-layout--rail .bp-toc__chapter.is-active-parent { background: #5c6bc0; color: #fff; border-radius: 999px; }
}

/* Fourth-level hierarchy: sub-sections buried inside a sub-chapter card (e.g. the
   relationship-category groups in "Related people & entities") get a divider +
   small-caps label so they read as distinct nested sections, not one blur. */
.bp-body .ownership-assoc-group + .ownership-assoc-group {
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-3);
    margin-top: var(--space-3);
}
.bp-body .ownership-assoc-group-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-fg-muted);
}
/* Same treatment for the seller / disposition sub-blocks in "How this portfolio
   was built". */
.bp-body .ownership-acq-sellers + .ownership-acq-sellers {
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-3);
    margin-top: var(--space-3);
}

.ownership-people {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px;
    line-height: 1.6;
}
.ownership-person-link {
    color: var(--color-accent-strong);
    text-decoration: none;
    border-bottom: 1px dotted var(--color-border-strong);
    font-weight: 500;
    transition: color 0.15s, border-color 0.15s;
}
.ownership-person-link:hover {
    color: var(--color-accent);
    border-bottom-color: var(--color-accent);
}
.ownership-person-plain {
    color: var(--color-fg-secondary);
}
/* Actor-model role tags on person chips ("principal", "registered agent") +
   the separated paperwork-names row. Role tags are informational, muted —
   never a warning color: a role is a fact, not a judgment. */
.ownership-person-role {
    font-size: 11px;
    color: var(--color-fg-muted);
    margin-left: var(--space-1);
    font-weight: 400;
    letter-spacing: 0.02em;
}
.ownership-people-papers {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px;
    line-height: 1.6;
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px dashed var(--color-border);
}
.ownership-people-papers-label {
    font-size: 12px;
    color: var(--color-fg-muted);
    margin-right: var(--space-1);
}
.ownership-section-subtitle {
    font-size: 13px;
    color: var(--color-fg-muted);
    margin: 4px 0 12px;
    line-height: 1.5;
    max-width: 65ch;
}
.ownership-assoc-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
/* ── Synopsis + group headings (profile IA) ─────────────────────────── */
.ownership-synopsis {
    margin: 14px 0 8px;
    padding: 14px 16px;
    background: var(--color-bg-surface, #f7f8fa);
    border-radius: 8px;
    border-left: 3px solid var(--color-accent-strong, #00838f);
}
.ownership-synopsis p {
    margin: 0;
    font-size: 15px;
    line-height: 1.6;
    color: var(--color-fg-primary);
}
.ownership-synopsis-sub {
    background: transparent;
    border-left: none;
    padding: 0 0 4px;
    margin: 6px 0 12px;
}
.ownership-synopsis-sub p {
    font-size: 14px;
    color: var(--color-fg-secondary);
}
.ownership-group-heading {
    margin: 32px 0 4px;
    padding-bottom: 6px;
    border-bottom: 2px solid var(--color-border-strong, #e0e0e0);
}
.ownership-group-title {
    font-size: 19px;
    font-weight: 700;
    color: var(--color-fg-primary);
    margin: 0;
}
.ownership-group-subtitle {
    font-size: 13px;
    color: var(--color-fg-muted);
    margin-top: 2px;
}
.ownership-section-subhead {
    font-size: 15px;
    font-weight: 600;
    color: var(--color-fg-primary);
    margin: 0 0 2px;
}

/* ── Entity roster + key events ─────────────────────────────────────── */
.ownership-roster-agent {
    font-size: 14px;
    color: var(--color-fg-secondary);
    margin-bottom: 10px;
}
.ownership-roster-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.ownership-roster-row {
    padding: 7px 0;
    border-bottom: 1px solid var(--color-border, #f0f0f0);
}
.ownership-roster-name {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    font-size: 14px;
}
.ownership-roster-parcels {
    font-size: 12px;
    color: var(--color-fg-muted);
}
/* Parcel list under a bundled-transaction row — the individual parcels that
   moved together in one deed. Wraps under the header line. */
.ownership-bundle-parcels {
    margin-top: 4px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-fg-secondary);
}
.ownership-bundle-parcels a {
    color: var(--color-link, #2563eb);
}
.ownership-roster-agentline {
    font-size: 12px;
    color: var(--color-fg-muted);
    margin-top: 2px;
}
.ownership-roster-active {
    background: #d4edda;
    color: #155724;
}
/* /address hub page — owner rows are clickable links into /owner/:slug */
a.address-row-link {
    display: block;
    text-decoration: none;
    color: inherit;
    border-radius: var(--radius-sm, 6px);
    transition: background var(--motion-fast, 120ms) ease;
}
a.address-row-link:hover {
    background: var(--color-bg-subtle, #f6f7f9);
}
a.address-row-link .ownership-roster-name strong {
    color: var(--color-link, #2563eb);
}
.address-ent-ic {
    display: inline-flex;
    align-items: center;
    color: var(--color-fg-muted);
}
.address-ic { vertical-align: -0.15em; }
.ownership-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}
.ownership-address-link {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-link, #2563eb);
    text-decoration: none;
}
.ownership-address-link:hover { text-decoration: underline; }
.ownership-keyevents {
    margin: 8px 0 0;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.ownership-keyevents li {
    font-size: 14px;
    line-height: 1.5;
    color: var(--color-fg-secondary);
}

/* ── Ownership ledger table (entity page) ─────────────────────────────── */
.ownership-ledger-table-wrap {
    width: 100%;
    overflow-x: auto;
    margin-top: 8px;
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
}
.ownership-ledger-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    line-height: 1.45;
}
.ownership-ledger-table thead th {
    text-align: left;
    padding: 10px 12px;
    background: var(--color-bg-subtle);
    color: var(--color-fg-secondary);
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid var(--color-border-subtle);
    position: sticky;
    top: 0;
}
.ownership-ledger-table tbody td {
    padding: 10px 12px;
    border-bottom: 1px solid var(--color-border-subtle);
    color: var(--color-fg-primary);
    vertical-align: top;
}
.ownership-ledger-table tbody tr:last-child td { border-bottom: 0; }
.ownership-ledger-table tbody tr:hover td { background: var(--color-bg-subtle); }
.ownership-ledger-table a { color: var(--color-link); text-decoration: none; }
.ownership-ledger-table a:hover { text-decoration: underline; }
.ownership-ledger-hood {
    display: inline-block;
    margin-left: 6px;
    font-size: 11px;
    color: var(--color-fg-tertiary);
    background: var(--color-bg-subtle);
    padding: 1px 6px;
    border-radius: 8px;
}
.ownership-ledger-faded { color: var(--color-fg-tertiary); }
.ownership-ledger-current {
    display: inline-block;
    color: var(--color-fg-secondary);
    font-style: italic;
}
.ownership-ledger-event {
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.4;
}
.ownership-ledger-event-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-fg-tertiary);
    font-weight: 600;
}
.ownership-ledger-date {
    font-size: 14px;
    color: var(--color-fg-primary);
}
.ownership-ledger-sub {
    font-size: 12px;
    color: var(--color-fg-secondary);
}
.ownership-ledger-tag {
    display: inline-block;
    margin-top: 2px;
    font-size: 11px;
    color: var(--color-fg-tertiary);
    font-style: italic;
}
.ownership-ledger-event--retitle .ownership-ledger-event-label { color: #8a6d3b; }
.ownership-ledger-event--sale .ownership-ledger-event-label { color: var(--color-link); }

/* Acquisition-channel chip (pipeline transfer-kind labels). Neutral by
   default; the two distress channels borrow the maroon the timeline's
   Foreclosed chip already established. */
.ownership-ledger-kind {
    display: inline-block;
    margin-left: var(--space-1);
    padding: 0 var(--space-1);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
    color: var(--color-fg-tertiary);
    vertical-align: 1px;
}
.ownership-ledger-kind--tax_sale,
.ownership-ledger-kind--foreclosure_sale {
    color: #8a2f2f;
    border-color: rgba(138, 47, 47, 0.4);
}

/* ── Merge-candidate review queue (curator-only) ───────────────────────── */
/* Distinct background so the curator action area reads as separate from
   the public profile content below it. */
.ownership-section.merge-candidates {
    background: rgba(255, 200, 80, 0.06);
    border: 1px solid rgba(255, 200, 80, 0.4);
    border-radius: var(--radius-md);
    padding: 14px 16px;
    margin-bottom: 18px;
}
.merge-candidate-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.merge-candidate {
    padding: 10px 12px;
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-sm);
    background: var(--color-bg-primary);
    transition: opacity 0.5s;
}
.merge-candidate-headline {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 4px;
}
.merge-candidate-name {
    font-weight: 600;
    color: var(--color-link);
    text-decoration: none;
    font-size: 14px;
}
.merge-candidate-name:hover { text-decoration: underline; }
.merge-candidate-confidence {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-fg-secondary);
    background: var(--color-bg-subtle);
    padding: 2px 8px;
    border-radius: 10px;
}
.merge-candidate-meta {
    font-size: 12px;
    color: var(--color-fg-secondary);
    line-height: 1.5;
    margin-bottom: 8px;
}
.merge-candidate-actions { display: flex; gap: 8px; align-items: center; }
.merge-btn {
    font-size: 12px;
    padding: 6px 12px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border-subtle);
    cursor: pointer;
    background: var(--color-bg-primary);
    color: var(--color-fg-primary);
    font-weight: 500;
}
.merge-btn:hover:not(:disabled) { background: var(--color-bg-subtle); }
.merge-btn:disabled { opacity: 0.5; cursor: default; }
.merge-btn--confirm { border-color: #2e7d52; color: #2e7d52; }
.merge-btn--confirm:hover:not(:disabled) { background: rgba(46, 125, 82, 0.08); }
.merge-btn--reject { color: var(--color-fg-secondary); }
.merge-candidate-status {
    font-size: 11px;
    color: var(--color-fg-tertiary);
    font-style: italic;
}

/* ── /admin/merges queue page ──────────────────────────────────────── */
.mq-page .ownership-content { padding: 20px; max-width: 1100px; margin: 0 auto; }
.mq-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    flex-wrap: wrap;
    padding: 12px 14px;
    background: var(--color-bg-subtle);
    border-radius: var(--radius-md);
    margin-bottom: 16px;
}
.mq-filter { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--color-fg-secondary); }
.mq-filter select {
    padding: 6px 8px;
    font-size: 13px;
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-sm);
    background: var(--color-bg-primary);
    color: var(--color-fg-primary);
}
.mq-totals { flex: 1; text-align: right; font-size: 13px; color: var(--color-fg-secondary); }
.mq-curator-badge {
    display: inline-block;
    margin-left: 8px;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 600;
    background: #2e7d52;
    color: white;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.mq-curator-badge--readonly { background: var(--color-fg-tertiary); }
.mq-list { display: flex; flex-direction: column; gap: 8px; }
.mq-empty { padding: 32px; text-align: center; color: var(--color-fg-tertiary); }
.mq-row {
    display: grid;
    grid-template-columns: 90px 1fr auto;
    grid-template-areas:
        "conf pair actions"
        "conf meta meta";
    gap: 10px 16px;
    padding: 12px 14px;
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
    background: var(--color-bg-primary);
    transition: opacity 0.5s, background 0.3s;
}
.mq-row--auto { border-left: 3px solid #2e7d52; }
.mq-row--review { border-left: 3px solid rgba(255, 200, 80, 0.7); }
.mq-row--focused {
    outline: 2px solid var(--color-link);
    outline-offset: -2px;
    background: var(--color-bg-subtle);
}
.mq-row--pending { opacity: 0.7; }
.mq-row--done { opacity: 0.5; }
.mq-session-stats {
    display: flex;
    gap: 12px;
    align-items: center;
    font-size: 12px;
    color: var(--color-fg-secondary);
}
.mq-session-stats span { white-space: nowrap; }
.mq-shortcuts-hint {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    background: var(--color-bg-primary);
    border: 1px solid var(--color-border-subtle);
    padding: 2px 8px;
    border-radius: 6px;
    font-size: 11px;
    color: var(--color-fg-tertiary);
}
.mq-bulk-actions { display: flex; gap: 8px; }
.mq-btn--bulk {
    font-size: 12px;
    padding: 8px 14px;
    font-weight: 600;
    background: #2e7d52;
    color: white;
    border-color: #2e7d52;
}
.mq-btn--bulk:hover:not(:disabled) { background: #266b46; }
.mq-btn--bulk:disabled { background: var(--color-fg-tertiary); border-color: var(--color-fg-tertiary); }
.mq-row-confidence { grid-area: conf; text-align: center; }
.mq-row-confidence strong { display: block; font-size: 20px; color: var(--color-fg-primary); }
.mq-row-decision { font-size: 10px; text-transform: uppercase; color: var(--color-fg-tertiary); letter-spacing: 0.05em; }
.mq-row-pair { grid-area: pair; display: flex; gap: 16px; align-items: stretch; }
.mq-row-eq { color: var(--color-fg-tertiary); font-weight: 600; align-self: center; }
@media (max-width: 768px) {
    .mq-row-pair { flex-direction: column; }
    .mq-row-eq { display: none; }
}
.mq-entity { display: flex; flex-direction: column; min-width: 240px; flex: 1; }
.mq-entity-name { font-weight: 600; color: var(--color-link); text-decoration: none; font-size: 14px; }
.mq-entity-name:hover { text-decoration: underline; }
.mq-entity-parcels { font-size: 11px; color: var(--color-fg-secondary); }
.mq-entity-mail {
    font-size: 11px;
    color: var(--color-fg-secondary);
    background: var(--color-bg-subtle);
    padding: 2px 6px;
    border-radius: 4px;
    margin-top: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mq-entity-parcellist {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    font-size: 11px;
    line-height: 1.5;
}
.mq-entity-parcellist li {
    display: flex;
    align-items: baseline;
    gap: 6px;
}
.mq-entity-parcellist a {
    color: var(--color-fg-primary);
    text-decoration: none;
}
.mq-entity-parcellist a:hover { text-decoration: underline; }
.mq-entity-hood {
    font-size: 10px;
    color: var(--color-fg-tertiary);
    background: var(--color-bg-subtle);
    padding: 0 5px;
    border-radius: 6px;
}
.mq-row-meta { grid-area: meta; display: flex; gap: 16px; flex-wrap: wrap; font-size: 11px; color: var(--color-fg-secondary); }
.mq-row-meta strong { color: var(--color-fg-tertiary); font-weight: 500; text-transform: uppercase; font-size: 10px; letter-spacing: 0.05em; margin-right: 4px; }
.mq-row-actions { grid-area: actions; display: flex; align-items: center; gap: 8px; }
.mq-btn {
    font-size: 12px;
    padding: 6px 12px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border-subtle);
    cursor: pointer;
    background: var(--color-bg-primary);
    color: var(--color-fg-primary);
    font-weight: 500;
}
.mq-btn:hover:not(:disabled) { background: var(--color-bg-subtle); }
.mq-btn:disabled { opacity: 0.5; cursor: default; }
.mq-btn--confirm { border-color: #2e7d52; color: #2e7d52; }
.mq-btn--confirm:hover:not(:disabled) { background: rgba(46, 125, 82, 0.08); }
.mq-status { font-size: 11px; color: var(--color-fg-tertiary); font-style: italic; }
.mq-status--readonly { color: var(--color-fg-tertiary); }
.mq-pagination { display: flex; justify-content: center; align-items: center; gap: 16px; margin-top: 20px; }
.mq-page-info { font-size: 12px; color: var(--color-fg-secondary); }

/* ── Activity timeline chart ────────────────────────────────────────── */
.ownership-timeline-chart {
    width: 100%;
    margin: 10px 0 6px;
}
.ownership-timeline-chart svg {
    width: 100%;
    height: auto;
    display: block;
}
.ownership-timeline-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    font-size: 12px;
    color: var(--color-fg-muted);
}
.ownership-timeline-legend span {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.ownership-timeline-legend i {
    width: 11px;
    height: 11px;
    border-radius: 2px;
    display: inline-block;
}

/* ── Acquisition story band ─────────────────────────────────────────── */
.ownership-straw-callout {
    background: #fff8e6;
    border: 1px solid #ffe2a8;
    border-radius: 8px;
    padding: 12px 14px;
    margin: 12px 0;
}
.ownership-straw-headline {
    font-size: 14px;
    line-height: 1.5;
    color: var(--color-fg-primary);
}
.ownership-straw-detail {
    font-size: 12px;
    color: var(--color-fg-muted);
    margin-top: 4px;
}
.ownership-acq-channels {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 12px 0;
}
.ownership-acq-row {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.ownership-acq-bar-wrap {
    height: 6px;
    background: var(--color-bg-surface, #f0f0f0);
    border-radius: 3px;
    overflow: hidden;
}
.ownership-acq-bar {
    height: 100%;
    background: var(--color-accent-strong, #00838f);
    border-radius: 3px;
}
.ownership-acq-row-label {
    font-size: 13px;
    color: var(--color-fg-secondary);
}
.ownership-acq-pct {
    color: var(--color-fg-muted);
    margin-left: 6px;
    font-size: 12px;
}
.ownership-acq-sellers {
    margin-top: 12px;
}

/* ── Operating-behavior metric grid ─────────────────────────────────── */
.ownership-metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 10px;
    margin-top: 12px;
}
.ownership-metric {
    background: var(--color-bg-surface, #fafafa);
    border-radius: 8px;
    padding: 12px;
    text-align: center;
}
.ownership-metric-value {
    font-size: 22px;
    font-weight: 700;
    color: var(--color-fg-primary);
    line-height: 1.2;
}
.ownership-metric-compare {
    display: block;
    font-size: 11px;
    font-weight: 500;
    color: var(--color-fg-muted);
    margin-top: 2px;
}
.ownership-metric-label {
    font-size: 12px;
    color: var(--color-fg-muted);
    margin-top: 4px;
}
.ownership-metric-warn .ownership-metric-value { color: #c0392b; }
.ownership-metric-good .ownership-metric-value { color: #1e8449; }

.ownership-assoc-group {
    margin-top: 16px;
}
.ownership-assoc-group-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-fg-primary);
    margin: 0 0 2px;
}
.ownership-assoc-row {
    padding: 8px 10px;
    background: var(--color-bg-surface, #fafafa);
    border-left: 3px solid var(--color-border-strong, #ccc);
    border-radius: 4px;
}
.ownership-assoc-headline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}
.ownership-assoc-name {
    color: var(--color-fg-primary);
    font-size: 14px;
}
.ownership-assoc-meta {
    font-size: 12px;
    color: var(--color-fg-muted);
    margin-top: 3px;
}
.ownership-assoc-chip {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 500;
    line-height: 1.5;
}
.ownership-assoc-chip-individual { background: #d1ecf1; color: #0c5460; }
.ownership-assoc-chip-llc,
.ownership-assoc-chip-corp,
.ownership-assoc-chip-trust,
.ownership-assoc-chip-church { background: #fff3cd; color: #7d5e00; }
.ownership-assoc-chip-govt { background: #e9ecef; color: #6c757d; }
.ownership-assoc-chip-bridge { background: #ffe2e0; color: #721c24; }
.ownership-assoc-chip-operator { background: #ffeeba; color: #856404; }
/* A second, independent instrument class agreeing — a mortgage naming the
   same person at both properties. Green because it CORROBORATES, where
   -bridge is red because it only proposes. */
.ownership-assoc-chip-corroborated { background: #d4edda; color: #155724; }

/* "Also filed under" — the same person under another owner record.
   The rows carry more than a name (address, who the match rests on), so
   each item stacks rather than running on like the plain keyevents list. */
.ownership-same-person li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
}
.ownership-same-person .ownership-assoc-meta {
    margin-top: 0;
    flex-basis: 100%;
}
.ownership-same-person .ownership-roster-parcels { white-space: nowrap; }

/* Operator-network radial ego-graph (SVG). */
.ownership-net-wrap {
    margin-top: var(--space-2, 8px);
    width: 100%;
    overflow: hidden;
}
.ownership-net-svg {
    width: 100%;
    height: auto;
    max-height: 480px;
    display: block;
}
.ownership-net-label {
    font-size: 11px;
    fill: var(--color-fg-primary);
}
.ownership-net-center-label {
    font-size: 10px;
    font-weight: 600;
    fill: #fff;
}
.ownership-net-node { cursor: pointer; }
.ownership-net-node:hover circle { fill-opacity: 1; stroke: var(--color-fg-primary); stroke-width: 1.5; }
.ownership-net-node:hover .ownership-net-label { font-weight: 600; text-decoration: underline; }
.ownership-net-svg { max-height: 560px; }
.ownership-net-sub {
    font-size: 9.5px;
    fill: var(--color-fg-muted);
}
.ownership-net-head {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
/* "Financed by" lender band below the deal-flow diagram. */
.ownership-net-lenders {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px dashed var(--color-border-strong);
}
.ownership-net-lenders-head {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #2e7d52;
    margin-right: 4px;
}
.ownership-net-lender-chip {
    display: inline-block;
    padding: 3px 9px;
    border-radius: var(--radius-pill, 999px);
    border: 1px solid #2e7d52;
    color: #2e7d52;
    background: transparent;
    font-size: 12px;
    font-weight: 500;
    text-decoration: none;
}
a.ownership-net-lender-chip:hover { background: rgba(46,125,82,0.12); }
body.theme-dark .ownership-net-lender-chip { color: #6cc99a; border-color: #6cc99a; }
body.theme-dark .ownership-net-lenders-head { color: #6cc99a; }

/* Entity-graph 1-hop edge fingerprint — one pill per edge type. */
.ownership-graph-edges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2, 8px);
    margin-top: var(--space-2, 8px);
}
.ownership-graph-edge {
    display: inline-block;
    padding: 3px 10px;
    border-radius: var(--radius-pill, 12px);
    font-size: 12px;
    line-height: 1.5;
    background: var(--color-bg-subtle, #f1f3f5);
    color: var(--color-fg-primary);
    border: 1px solid var(--color-border, #dee2e6);
}
.ownership-graph-edge strong { font-weight: 600; }

.ownership-person-sep {
    color: var(--color-fg-muted);
    margin: 0 2px;
}

.ownership-twocol {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
    margin-bottom: var(--space-6);
}
.ownership-card {
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
}
.ownership-row-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.ownership-row-list li {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--color-border);
}
.ownership-row-list li:last-child { border-bottom: 0; }
.ownership-row-label {
    color: var(--color-fg-primary);
    font-size: 13px;
}
.ownership-row-count {
    color: var(--color-fg-secondary);
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    font-size: 13px;
}
.ownership-row-unit {
    color: var(--color-fg-muted);
    font-weight: 400;
    font-size: 12px;
}

/* Clickable breakdown row — a filterable dimension (Top neighborhoods,
 * Property mix). Hover surfaces a hand cursor + slight background;
 * --active is the bright "this filter is on" state that mirrors the
 * map's bright tier so the connection between the row and the
 * highlighted parcels reads at a glance. */
.ownership-row-filterable {
    cursor: pointer;
    margin: 0 calc(-1 * var(--space-3));
    padding-left: var(--space-3);
    padding-right: var(--space-3);
    border-radius: var(--radius-sm, 6px);
    transition: background var(--motion-fast, 120ms) ease;
}
.ownership-row-filterable:hover {
    background: var(--color-bg-hover, rgba(0, 128, 255, 0.06));
}
.ownership-row-filterable:focus-visible {
    outline: 2px solid var(--color-accent, #0080ff);
    outline-offset: -2px;
}
.ownership-row-filterable--active {
    background: rgba(0, 128, 255, 0.12);
}
.ownership-row-filterable--active .ownership-row-label {
    color: var(--color-accent, #0066cc);
    font-weight: 600;
}
.ownership-row-filterable--active .ownership-row-count {
    color: var(--color-accent, #0066cc);
}

/* Active-filter chip bar — sits above the embedded map and surfaces
 * the constraint(s) producing the bright map subset. Mirrors the
 * map's bright tier in color. Each chip's × clears its dimension. */
.ownership-active-filters {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin: var(--space-3) 0 var(--space-2);
    padding: var(--space-2) var(--space-3);
    background: rgba(0, 128, 255, 0.06);
    border: 1px solid rgba(0, 128, 255, 0.25);
    border-radius: var(--radius-md);
}
.ownership-active-filters-count {
    font-size: 12px;
    color: var(--color-fg-secondary);
    font-variant-numeric: tabular-nums;
}
.ownership-active-filters-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}
.ownership-filter-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 4px var(--space-2) 4px var(--space-3);
    border: 1px solid var(--color-accent, #0080ff);
    background: var(--color-bg-elevated);
    color: var(--color-accent, #0066cc);
    border-radius: var(--radius-pill);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    line-height: 1;
}
.ownership-filter-chip:hover {
    background: rgba(0, 128, 255, 0.08);
}
.ownership-filter-chip-label {
    color: var(--color-fg-muted);
    font-weight: 500;
}
.ownership-filter-chip-value {
    color: var(--color-accent, #0066cc);
    font-weight: 600;
}

.ownership-ctrl-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.ownership-ctrl-link {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    text-decoration: none;
    color: inherit;
    transition: border-color 0.15s, background-color 0.15s;
}
.ownership-ctrl-link:hover {
    border-color: var(--color-accent);
    background: var(--color-bg-hover);
}
.ownership-rank-num {
    flex: 0 0 auto;
    min-width: 22px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    font-size: 13px;
    color: var(--color-fg-muted);
}
.ownership-ctrl-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1 1 auto;
}
.ownership-ctrl-name {
    font-weight: 600;
    color: var(--color-fg-primary);
    font-size: 13.5px;
}
.ownership-ctrl-sub {
    font-size: 12px;
    color: var(--color-fg-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ownership-ctrl-metric {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex: 0 0 auto;
    line-height: 1.1;
}
.ownership-ctrl-metric-num {
    font-weight: 700;
    font-size: 16px;
    color: var(--color-fg-primary);
    font-variant-numeric: tabular-nums;
}
.ownership-ctrl-metric-label {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-fg-muted);
}

.ownership-parcel-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
    background: var(--color-border);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.ownership-parcel-link {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-elevated);
    text-decoration: none;
    color: inherit;
    transition: background-color 0.15s;
}
.ownership-parcel-link:hover {
    background: var(--color-bg-hover);
}
.ownership-parcel-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}
.ownership-parcel-address {
    color: var(--color-fg-primary);
    font-weight: 500;
    font-size: 13px;
}
.ownership-parcel-meta {
    color: var(--color-fg-muted);
    font-size: 12px;
}
.ownership-parcel-units {
    flex: 0 0 auto;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-fg-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.ownership-more-button {
    margin-top: var(--space-3);
    padding: var(--space-2) var(--space-4);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-md);
    color: var(--color-accent-strong);
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
    transition: background-color 0.15s, border-color 0.15s;
}
.ownership-more-button:hover {
    background: var(--color-bg-hover);
    border-color: var(--color-accent);
}
.ownership-properties-note {
    margin-top: var(--space-3);
    color: var(--color-fg-muted);
    font-size: 12px;
    font-style: italic;
}

@media (max-width: 768px), (max-height: 600px) {
    .ownership-page {
        /* Top padding still clears the back-chip (absolute here). */
        padding: var(--space-10) var(--space-3) var(--space-6);
    }
    .ownership-name { font-size: 22px; }
    .ownership-stats-strip { flex-direction: column; gap: var(--space-2); }
    /* Stacked stats: swap the cell divider from left to top. */
    .ownership-stat + .ownership-stat {
        border-left: 0;
        padding-left: 0;
        border-top: 1px solid var(--color-border);
        padding-top: var(--space-2);
    }
    .ownership-twocol { grid-template-columns: 1fr; }
    .ownership-nav {
        position: absolute;
        top: var(--space-2);
        left: var(--space-2);
    }
}

/* ─────────────────────────────────────────────────────────────
 * Ownership profile — slice 3b1 additions:
 *   - Controllers list rows are now real navigable links (no
 *     extra style change; existing .ownership-ctrl-link covers it)
 *   - "Underlying names" chip block, collapsed past 12 with a toggle
 * ───────────────────────────────────────────────────────────── */
.ownership-ctrl-flat {
    /* Fallback row when a controller doesn't have a slug — no hover,
     * no link affordance. Defensive; shouldn't fire in practice. */
    cursor: default;
}
.ownership-ctrl-flat:hover {
    border-color: var(--color-border);
    background: var(--color-bg-elevated);
}

.ownership-llc-chips,
.ownership-llc-hidden {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: var(--space-2);
}
.ownership-llc-hidden {
    display: none;
}
.ownership-llc-expanded .ownership-llc-hidden {
    display: flex;
}
.ownership-llc-chip {
    display: inline-block;
    padding: 3px var(--space-2);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    font-size: 11.5px;
    color: var(--color-fg-secondary);
    white-space: nowrap;
}
.ownership-llc-toggle {
    margin-top: 2px;
    padding: var(--space-1) var(--space-2);
    background: transparent;
    border: none;
    color: var(--color-accent-strong);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}
.ownership-llc-toggle:hover { color: var(--color-accent); }
.ownership-llc-toggle .ownership-llc-toggle-hide { display: none; }
.ownership-llc-expanded .ownership-llc-toggle .ownership-llc-toggle-show { display: none; }
.ownership-llc-expanded .ownership-llc-toggle .ownership-llc-toggle-hide { display: inline; }

/* ─────────────────────────────────────────────────────────────
 * Businesses operating here — Overture places geocoded to parcels.
 * ───────────────────────────────────────────────────────────── */
.ownership-section-count {
    display: inline-block;
    margin-left: var(--space-2);
    padding: 1px var(--space-2);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: 700;
    color: var(--color-fg-secondary);
}
.ownership-biz-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
}
.ownership-biz-chip {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-1);
    padding: 4px var(--space-2);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font-size: 12px;
}
.ownership-biz-name {
    font-weight: 600;
    color: var(--color-fg-primary);
}
.ownership-biz-loc {
    font-size: 10.5px;
    font-weight: 700;
    color: var(--color-fg-muted);
}
.ownership-biz-cat {
    font-size: 10.5px;
    color: var(--color-fg-muted);
    text-transform: lowercase;
}

/* ─────────────────────────────────────────────────────────────
 * Portfolio card slim mode (task #34) — single-parcel controllers.
 * ───────────────────────────────────────────────────────────── */
.portfolio-card-slim {
    padding: var(--space-2) var(--space-3);
    gap: var(--space-1);
    background: var(--color-bg-elevated);
}
.portfolio-slim-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-fg-muted);
}
.portfolio-slim-hint {
    font-size: 11.5px;
    color: var(--color-fg-muted);
    font-style: italic;
    margin-top: 2px;
}

/* ─────────────────────────────────────────────────────────────
 * Ownership profile filtered map (task #35).
 * Small map between the breakdowns and the property list, with
 * highlighted parcels in blue against a dim gray context fill.
 * ───────────────────────────────────────────────────────────── */
.ownership-map-mount {
    width: 100%;
    height: 360px;
    background: #f1f5f9;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    position: relative;
}
/* Fills its container. Height is owned by the container (e.g.
 * .ownership-map-mount sets 360px) — do NOT set height:100% here: this
 * class lands on the SAME element as the mount, and 100% resolves against
 * the auto-height parent section → 0, collapsing the map. */
.profile-map {
    width: 100%;
}
.profile-map-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--color-fg-muted);
    font-size: 13px;
    font-style: italic;
}

/* ─────────────────────────────────────────────────────────────
 * "How we connected this" provenance panel (owner/operator pages)
 * ───────────────────────────────────────────────────────────── */
.ownership-prov {
    border: 1px solid var(--color-border-strong);
    border-left-width: 3px;
    border-radius: var(--radius-lg);
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-muted);
}
.ownership-prov--ok {
    border-left-color: var(--color-success-accent);
}
.ownership-prov--caution {
    border-left-color: var(--color-warning-accent);
    background: var(--color-warning-bg);
}
.ownership-prov .ownership-section-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.ownership-prov-icon {
    color: var(--color-fg-muted);
    flex: 0 0 auto;
}
.ownership-prov-basis {
    margin: var(--space-2) 0 var(--space-3);
    color: var(--color-fg-primary);
    line-height: 1.45;
}
.ownership-prov-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    font-size: 13px;
    color: var(--color-fg-secondary);
    line-height: 1.4;
}
.ownership-prov-list .ownership-prov-ok { color: var(--color-success-fg); }
.ownership-prov-list .ownership-prov-caution { color: var(--color-warning-fg); }
.ownership-prov-caveat { color: var(--color-fg-muted); }
.ownership-prov-method {
    color: var(--color-accent);
    text-decoration: none;
    white-space: nowrap;
}
.ownership-prov-method:hover { text-decoration: underline; }

/* Curated-operator banner — sits ABOVE the algorithmic provenance block
   on /operator/:slug when operator_profile.is_curated = true. Visual
   distinct from .ownership-prov so investigators' hand-verification reads
   as a positive trust signal rather than auto-generated commentary. */
.ownership-curated {
    border: 1px solid var(--color-success-accent);
    border-left-width: 3px;
    border-radius: var(--radius-lg);
    padding: var(--space-3) var(--space-4);
    background: var(--color-success-bg, #e8f5e9);
}
.ownership-curated .ownership-section-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-success-fg, #1b5e20);
}
.ownership-curated-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: var(--space-2) 0 var(--space-3);
    font-size: 13px;
    color: var(--color-fg-secondary);
}
.ownership-curated-verified { font-variant-numeric: tabular-nums; }
.ownership-curated-confidence {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 2px var(--space-2);
    border-radius: 10px;
}
.ownership-curated-confidence--high   { background: #c8e6c9; color: #1b5e20; }
.ownership-curated-confidence--medium { background: #fff3c4; color: #8d6e00; }
.ownership-curated-confidence--low    { background: #ffcdd2; color: #b71c1c; }
.ownership-curated-notes {
    margin: 0 0 var(--space-2);
    color: var(--color-fg-primary);
    line-height: 1.5;
}
.ownership-curated-notes:last-of-type { margin-bottom: var(--space-3); }
.ownership-curated-caveat {
    margin: 0;
    color: var(--color-fg-muted);
    font-size: 13px;
}
.ownership-prov-badge {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 2px var(--space-2);
    border-radius: var(--radius-md);
    border: 1px solid transparent;
}
.ownership-prov-badge--ok {
    color: var(--color-success-fg);
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
}
.ownership-prov-badge--caution {
    color: var(--color-warning-fg);
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    text-transform: none;
    letter-spacing: 0;
}

/* ─────────────────────────────────────────────────────────────
 * Methodology explainer (/methodology, task #38). A reading page, so
 * it overrides the profile's tiny uppercase section titles with
 * legible headings and adds body-text + sources-list styling. Reuses
 * the .ownership-page shell so it sits on the same surface.
 * ───────────────────────────────────────────────────────────── */
.methodology-page .ownership-section {
    margin-bottom: var(--space-6);
}
.methodology-page .ownership-section-title {
    font-size: 16px;
    text-transform: none;
    letter-spacing: -0.01em;
    color: var(--color-fg-primary);
    margin-bottom: var(--space-3);
}
.methodology-body,
.methodology-lead p {
    margin: 0 0 var(--space-3);
    color: var(--color-fg-secondary);
    line-height: 1.6;
    font-size: 15px;
}
.methodology-body:last-child,
.methodology-lead p:last-child {
    margin-bottom: 0;
}
.methodology-caveat {
    border-left: 3px solid var(--color-warning-accent);
    background: var(--color-warning-bg);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
}
.methodology-sources {
    margin: 0;
    padding-left: var(--space-4);
    color: var(--color-fg-secondary);
    line-height: 1.7;
    font-size: 14px;
}
.methodology-sources strong { color: var(--color-fg-primary); }

/* Methodology page section dividers — major topic boundaries between
   Portfolio resolution / Confidence scores / Sources. Larger than the
   per-section .ownership-section-title and unstyled like a chapter
   marker so they read as a transition rather than a list item. */
.methodology-section-anchor {
    font-size: 22px;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.01em;
    color: var(--color-fg-primary);
    margin: var(--space-7) 0 var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border-muted);
}
/* The first divider on the page (Portfolio resolution) sits right
   under the lead paragraph — keep it tight, no extra top break. */
.methodology-page .methodology-lead + .methodology-section-anchor {
    margin-top: var(--space-4);
    padding-top: 0;
    border-top: none;
}

/* Inline sub-headings inside a confidence section ("Signals that raise
   it" / "lower it" / "What it can't tell you"). Smaller than the
   section title but visually distinct from body paragraphs. */
.methodology-subsection {
    font-size: 14px;
    font-weight: 700;
    color: var(--color-fg-primary);
    margin: var(--space-4) 0 var(--space-2);
    text-transform: none;
    letter-spacing: 0;
}
.methodology-subsection:first-child { margin-top: 0; }

/* ─────────────────────────────────────────────────────────────
 * Methodology flow graphic — the high-level "names -> controller ->
 * operator + layered signals" pipeline. Row on desktop; stacks to a
 * column on phones with the connector arrows rotated to point down.
 * ───────────────────────────────────────────────────────────── */
.methodology-figure {
    margin: 0 0 var(--space-6);
    padding: var(--space-5);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
}
.methodology-flow {
    display: flex;
    align-items: stretch;
    gap: var(--space-2);
}
.methodology-node {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-3);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}
.methodology-node-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-pill);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
}
.methodology-node-glyph { color: var(--color-accent); }
.methodology-node-body {
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.methodology-node-kicker {
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-accent-strong);
}
.methodology-node-title {
    font-size: 15px;
    font-weight: 700;
    color: var(--color-fg-primary);
}
.methodology-node-desc {
    font-size: 12px;
    line-height: 1.45;
    color: var(--color-fg-secondary);
}
.methodology-arrow {
    flex: 0 0 auto;
    align-self: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}
.methodology-arrow-glyph { color: var(--color-fg-muted); }
.methodology-arrow-label {
    font-size: 10px;
    font-weight: 600;
    line-height: 1.1;
    text-align: center;
    color: var(--color-fg-muted);
    max-width: 72px;
}
.methodology-signals {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px dashed var(--color-border);
}
.methodology-signal-label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-fg-muted);
    text-align: center;
}
.methodology-signal-chips {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2);
}
.methodology-signal-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 5px var(--space-3);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    font-size: 12px;
    color: var(--color-fg-secondary);
}
.methodology-signal-glyph {
    width: 16px;
    height: 16px;
    color: var(--color-accent);
    flex: 0 0 auto;
}
.methodology-figure-cap {
    margin: var(--space-4) 0 0;
    font-size: 12.5px;
    font-style: italic;
    line-height: 1.5;
    text-align: center;
    color: var(--color-fg-muted);
}

/* Phone-class: stack the flow vertically, point arrows down. */
@media (max-width: 768px), (max-height: 600px) {
    .methodology-flow { flex-direction: column; }
    .methodology-node { width: 100%; }
    .methodology-arrow-glyph { transform: rotate(90deg); }
    .methodology-arrow-label { max-width: none; }
}

/* ─────────────────────────────────────────────────────────────
 * City-wide overview — the landing surface in the selection panel.
 * The stat body renders through the shared .bstats-* boundary system
 * (one visual language with every other boundary). Below it sits a
 * single ownership teaser bridging to the full /ownership page.
 * ───────────────────────────────────────────────────────────── */
.city-own-teaser {
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-4);
}
.city-own-title {
    margin: 0 0 var(--space-2);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-fg-muted);
}
.city-own-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    border-radius: var(--radius-md);
    overflow: hidden;
    border: 1px solid var(--color-border);
    margin-bottom: var(--space-2);
}
.city-own-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: var(--color-bg-muted);
    text-decoration: none;
    transition: background var(--motion-fast, 0.12s) ease;
}
.city-own-row:hover {
    background: var(--color-bg-elevated);
}
.city-own-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-accent);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.city-own-meta {
    flex: none;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    color: var(--color-fg-muted);
}
.city-own-note {
    margin: 0 0 var(--space-3);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--color-fg-secondary);
}
.city-own-cta {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    background: var(--color-accent);
    color: var(--color-on-accent, #fff);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    transition: filter var(--motion-fast, 0.12s) ease;
}
.city-own-cta:hover {
    filter: brightness(1.08);
}

/* ─────────────────────────────────────────────────────────────
 * Portfolio records — aggregate stat cards (task #41).
 * Combined value / purchase price / blight / permits / demolitions /
 * rentals. "Under ownership" counts are scoped to each parcel's recorded
 * sale date; the caveat line explains the conservative undercount.
 * ───────────────────────────────────────────────────────────── */
.ownership-pstat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--space-2);
}
.ownership-pstat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-3);
    border-radius: var(--radius-md);
    background: var(--color-bg-muted);
    border: 1px solid var(--color-border);
}
.ownership-pstat--warn {
    border-color: var(--color-warning-border);
    background: var(--color-warning-bg);
}
.ownership-pstat--warn .ownership-pstat-value { color: var(--color-warning-accent); }
.ownership-pstat--danger {
    border-color: var(--color-danger-border);
    background: var(--color-danger-bg);
}
.ownership-pstat--danger .ownership-pstat-value { color: var(--color-danger-accent); }
.ownership-pstat--danger .ownership-pstat-label,
.ownership-pstat--danger .ownership-pstat-sub { color: var(--color-danger-fg); }
.ownership-pstat-value {
    font-size: 22px;
    font-weight: 700;
    line-height: 1.1;
    color: var(--color-fg-primary);
}
.ownership-pstat-label {
    font-size: 12px;
    color: var(--color-fg-secondary);
}
.ownership-pstat-sub {
    font-size: 11.5px;
    color: var(--color-fg-tertiary, var(--color-fg-secondary));
}
.ownership-pstat-caveat {
    margin: var(--space-2) 0 0;
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--color-fg-tertiary, var(--color-fg-secondary));
}
/* Filterable tile (its stats array on the profile is non-empty so a click
 * narrows the map + gallery to that subset). Subtle hover lift; --active
 * stamps the bright blue ring that mirrors the map's HIGHLIGHT tier so the
 * cause/effect reads at a glance. */
.ownership-pstat--filterable {
    cursor: pointer;
    transition:
        transform var(--motion-fast, 120ms) ease,
        box-shadow var(--motion-fast, 120ms) ease,
        border-color var(--motion-fast, 120ms) ease;
}
.ownership-pstat--filterable:hover {
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgba(15, 23, 42, 0.08);
    border-color: var(--color-accent, #0080ff);
}
.ownership-pstat--filterable:focus-visible {
    outline: 2px solid var(--color-accent, #0080ff);
    outline-offset: 2px;
}
.ownership-pstat--active {
    border-color: var(--color-accent, #0080ff);
    box-shadow: inset 0 0 0 1px var(--color-accent, #0080ff);
    background: rgba(0, 128, 255, 0.06);
}
.ownership-pstat--active .ownership-pstat-value {
    color: var(--color-accent, #0066cc);
}
/* Active beats warn/danger color on values — the filter selection is the
 * primary signal when the tile is on. */
.ownership-pstat--active.ownership-pstat--warn,
.ownership-pstat--active.ownership-pstat--danger {
    border-color: var(--color-accent, #0080ff);
}

/* ─────────────────────────────────────────────────────────────
 * Property gallery (task #43) — replaces the old text parcel list.
 * One Mapillary thumbnail per parcel with the address overlaid on the
 * image; placeholder paints first, then applyGalleryImages() swaps the
 * thumb in via background-image. Tile click opens /?parcel=<id> in a new
 * tab. Mirrors the survey dashboard's .gallery-tile pattern.
 * ───────────────────────────────────────────────────────────── */
.ownership-tile-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--space-3);
}
.ownership-tile {
    text-decoration: none;
    color: inherit;
    border-radius: var(--radius-md);
}
.ownership-tile-img {
    position: relative;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--color-bg-muted);
    background-size: cover;
    background-position: center;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.ownership-tile-img:not(.ownership-tile-loaded) {
    background-image: linear-gradient(135deg, var(--color-bg-muted) 0%, var(--color-bg-hover) 100%);
}
.ownership-tile:hover .ownership-tile-img {
    transform: translateY(-1px);
    box-shadow: var(--shadow-sm);
}
.ownership-tile-overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 16px var(--space-2) var(--space-2);
    background: linear-gradient(to top, rgba(0, 0, 0, 0.78) 0%, rgba(0, 0, 0, 0) 100%);
    color: #fff;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.ownership-tile-address {
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ownership-tile-meta {
    font-size: 11px;
    opacity: 0.85;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ownership-tile-units {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    z-index: 1;
    padding: 1px 6px;
    border-radius: var(--radius-sm);
    font-size: 10.5px;
    font-weight: 600;
    color: #fff;
    background: rgba(0, 0, 0, 0.6);
}

/* ── Neighborhood profile: on-market listing galleries ─────────────────────
   Street-view listing tiles for /neighborhood/:slug. The Mapillary thumb is
   painted in by mapillary-gallery.js (background-image); until .mly-thumb-loaded
   lands the placeholder gradient shows. Mirrors the ownership portfolio gallery,
   on the boundary-profile theme. */
.bp-listing-subhead {
    margin: var(--space-4) 0 var(--space-2);
    font-size: 14px;
    font-weight: 600;
}
.bp-listing-summary {
    margin: 0 0 var(--space-3);
    font-size: 13px;
    color: var(--color-fg-muted);
    line-height: 1.5;
}
.bp-listing-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--space-3);
}
.bp-listing-tile {
    text-decoration: none;
    color: inherit;
}
.bp-listing-tile__img {
    position: relative;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--color-bg-muted);
    background-size: cover;
    background-position: center;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.bp-listing-tile__img:not(.mly-thumb-loaded) {
    background-image: linear-gradient(135deg, var(--color-bg-muted) 0%, var(--color-bg-hover) 100%);
}
.bp-listing-tile:hover .bp-listing-tile__img {
    transform: translateY(-1px);
    box-shadow: var(--shadow-sm);
}
.bp-listing-tile__overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 18px var(--space-2) var(--space-2);
    background: linear-gradient(to top, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0) 100%);
    color: #fff;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.bp-listing-tile__price {
    font-size: 14px;
    font-weight: 700;
    line-height: 1.25;
}
.bp-listing-tile__addr {
    font-size: 11.5px;
    opacity: 0.9;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bp-listing-tile__badge {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    z-index: 1;
    padding: 2px 7px;
    border-radius: var(--radius-sm);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: #fff;
    background: rgba(126, 87, 194, 0.92);
}
/* "Sold <date>" variant on the recently-sold tiles — green, normal case so
   the date reads naturally. */
.bp-listing-tile__badge--sold {
    background: rgba(22, 163, 74, 0.92);
    text-transform: none;
    letter-spacing: 0;
}

/* ── /ownership redesign (2026-07-22): map, lenses, multi-line viz ───────── */
.own-map { margin-top: var(--space-2); }
.own-map-canvas { height: 420px; border-radius: var(--radius-md); overflow: hidden;
                  border: 1px solid var(--color-border); }
@media (max-width: 768px), (max-height: 600px) {
    .own-map-canvas { height: 300px; }
}
.own-map-head { display: flex; flex-wrap: wrap; align-items: baseline;
                gap: var(--space-2); margin-bottom: var(--space-2); }
.own-map-sub { font-size: 12px; color: var(--color-fg-tertiary); }
.own-map-fallback { padding: var(--space-4); font-size: 13px;
                    color: var(--color-fg-tertiary); }
.own-map-card { margin-top: var(--space-2); padding: var(--space-2) var(--space-3);
                border: 1px solid var(--color-border); border-radius: var(--radius-md);
                display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3);
                font-size: 12.5px; color: var(--color-fg-secondary); }
.own-map-card strong { color: var(--color-fg-primary); }
.own-map-tabs, .ownership-lens-tabs { display: flex; flex-wrap: wrap; gap: var(--space-1); }
/* Unified chip vocabulary — the housing map's .metric-chip recipe, applied
   to every /ownership control (lens tabs, cohort horizons, map metrics,
   explorer filters). Quiet by default (no border — the ink is the label),
   accent fill + on-accent text when active. */
.own-map-tabs button, .ownership-lens-tabs button {
    background: transparent; border: none; color: var(--color-fg-secondary);
    border-radius: var(--radius-pill); padding: 6px 10px;
    font-size: 12.5px; font-weight: 500; font-family: inherit; cursor: pointer;
}
.own-map-tabs button:hover, .ownership-lens-tabs button:hover {
    background: var(--color-bg-hover); color: var(--color-fg-primary);
}
.own-map-tabs button.on, .ownership-lens-tabs button.on {
    background: var(--color-accent); color: var(--color-on-accent);
}
.ownership-lens-tabs { margin-bottom: var(--space-2); }
.ownership-lenses > .ownership-lens > .ownership-section { margin-top: 0; }
.ownership-viz-caption { font-size: 12px; color: var(--color-fg-tertiary);
                         margin-top: var(--space-1); }
.ownership-viz-caption .viz-spark { vertical-align: middle; }
.ownership-viz-capnum { font-variant-numeric: tabular-nums; }
.ownership-street-toggle { margin-left: var(--space-2); font-size: 12px;
                           color: var(--color-fg-secondary); cursor: pointer; }
.viz-mline svg { display: block; }
.viz-mline-path { fill: none; stroke-width: 2; }
.viz-mline-pt { fill: var(--color-bg); stroke-width: 1.6; opacity: 0;
                transition: opacity var(--motion-fast, 120ms); }
.viz-mline svg:hover .viz-mline-pt { opacity: 1; }
.viz-mline-label { font-size: 10.5px; font-weight: 600; }
.viz-mline-base { stroke: var(--color-border); stroke-width: 1; }

/* Chart legibility (2026-07-22): SVG text scales with the viewBox, so cap
   how far the charts stretch on wide desktop cells (labels balloon) — the
   phone-side fix is render-time (narrower viewBox via viz.js isPhoneViz). */
.viz-mline svg { max-width: 620px; }
.viz-yearcols-svg { max-width: 720px; }
@media (max-width: 768px), (max-height: 600px) {
    /* Bar rows: three columns can't fit a phone — stack the label above,
       let the value cell wrap instead of overflowing the row. */
    .viz-bar-row {
        grid-template-columns: 1fr auto;
        grid-template-areas: "label label" "track value";
        row-gap: 2px;
    }
    .viz-bar-label { grid-area: label; white-space: normal; }
    .viz-bar-track { grid-area: track; }
    .viz-bar-val { grid-area: value; white-space: normal;
                                   max-width: 46vw; text-align: right; }
    .own-map-card { font-size: 12px; }
}

/* ── Map-first /ownership (2026-07-23) ──────────────────────────────────── */
.own-map--hero .own-map-canvas { height: 480px; }
@media (max-width: 768px), (max-height: 600px) {
    .own-map--hero .own-map-canvas { height: 55vh; min-height: 300px; }
}
.ownership-section--hero { margin-top: 0; }
.own-map-row { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.own-map-row + .own-map-row { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--color-border); }
.own-map-rowlabel { flex: 0 0 auto; width: 62px; font-size: 10px; font-weight: 700;
    letter-spacing: 0.05em; text-transform: uppercase; color: var(--color-fg-muted);
    white-space: nowrap; line-height: 1.2; }
.own-map-row .own-map-groups, .own-map-row .own-map-tabs {
    flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin; }
.own-map-row .own-map-groups button, .own-map-row .own-map-tabs button { flex: 0 0 auto; white-space: nowrap; }
.own-map-groups { display: flex; flex-wrap: wrap; gap: var(--space-1);
                  margin-bottom: var(--space-1); }
/* Question groups: same chip recipe, one weight heavier — hierarchy comes
   from type, not from a different control style. */
.own-map-groups button {
    background: transparent; border: none; color: var(--color-fg-secondary);
    border-radius: var(--radius-pill); padding: 6px 12px;
    font-size: 13px; font-weight: 600; font-family: inherit; cursor: pointer;
}
.own-map-groups button:hover {
    background: var(--color-bg-hover); color: var(--color-fg-primary);
}
.own-map-groups button.on {
    background: var(--color-accent); color: var(--color-on-accent);
}
.own-map-head { flex-direction: column; align-items: flex-start;
                gap: var(--space-1); }
/* Rows fill the card, they do not size to their own content.
   `align-items: flex-start` let each row take its max-content width, and a
   float-card capped at 78vw on a phone then had a 526px chip row inside a
   304px card: the last two lenses ("Tax-auction sales", "Investor buying")
   painted outside the card, over the map, and the map section's
   `overflow: hidden` cut them off — unreachable, because the row's own
   `overflow-x: auto` never engaged (scrollWidth === clientWidth: nothing
   was overflowing the ROW, the row was overflowing the CARD).
   Stretched to the card, the chip row is once again the scroller it was
   written to be, and every lens can be reached by swiping it. */
.own-map-head > .own-map-row,
.own-map-head > .own-map-subrow,
.own-map-head > .own-map-parcel-legend { align-self: stretch; min-width: 0; max-width: 100%; }

/* Pulse strip — four anchor tiles under the hero map. */
.ownership-pulse { display: grid; gap: var(--space-2);
                   grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
                   margin: var(--space-3) 0; }
/* Pulse tiles ARE pstats (same surface as the landscape tiles) — the only
   addition is link affordance: the number takes the accent on hover. */
.ownership-pulse-tile { text-decoration: none; }
.ownership-pulse-tile .ownership-pstat-value {
    transition: color var(--motion-fast, 120ms);
}
.ownership-pulse-tile:hover .ownership-pstat-value,
.ownership-pulse-tile:focus-visible .ownership-pstat-value {
    color: var(--color-accent);
}

/* Explorer re-skin: the pill segments could not wrap (single 999px-radius
   pill), so a five-option row bled off phones. Chips — the same vocabulary
   as the lens tabs — wrap naturally and align the tool with the page. */
.ownership-filter-seg {
    display: flex; flex-wrap: wrap; gap: var(--space-1);
    background: none; padding: 0; border-radius: 0;
}
.ownership-filter-btn {
    border: none; border-radius: var(--radius-pill);
    background: transparent; padding: 6px 10px; font-size: 12.5px;
    font-weight: 500; font-family: inherit;
    color: var(--color-fg-secondary); cursor: pointer;
}
.ownership-filter-btn:hover {
    background: var(--color-bg-hover); color: var(--color-fg-primary);
}
.ownership-filter-btn.is-active {
    background: var(--color-accent); color: var(--color-on-accent);
}

/* ── Tufte batch-1 (2026-07-23 audit) ───────────────────────────────────── */
/* R2 navigation: barList rows with hrefs are links — house arrow, quiet
   until hover. Inert rows never get the affordance. */
.viz-bar-row--link { text-decoration: none; color: inherit; cursor: pointer; }
.viz-bar-row--link:hover { background: var(--color-bg-hover); }
.viz-bar-row--link:hover .viz-bar-label { color: var(--color-accent); }
.viz-bar-go { color: var(--color-fg-tertiary); }
.viz-bar-row--link:hover .viz-bar-go { color: var(--color-accent); }
.ownership-pstat-go { color: var(--color-fg-tertiary); }
.ownership-pulse-tile:hover .ownership-pstat-go { color: var(--color-accent); }

/* Track honesty: without an explicit denominator the gray track reads as a
   false "100%" — unframed lists paint only the fill. */
.viz-bars:not(.viz-bars--framed) .viz-bar-track { background: transparent; }
.viz-bar-fill { min-width: 1px; }
.viz-bar-track { position: relative; }
.viz-bar-ref { position: absolute; top: -2px; bottom: -2px; width: 1px;
               background: var(--color-fg-secondary); }

/* Inline sparkline sized to ride inside a caption line. */
.viz-spark-wrap--inline { display: inline-block; width: 110px;
                          vertical-align: middle; }

/* multiLine end labels that navigate: underline on hover (SVG <a>). */
.viz-mline a:hover .viz-mline-label--link { text-decoration: underline; }

/* The biggest digits on the page should not jitter. */
.ownership-pstat-value, .ownership-pstat-sub { font-variant-numeric: tabular-nums; }

/* ── Tufte batch-2 (2026-07-23): flatten the overview's card chrome ─────── */
/* Scoped to the overview page ONLY (profile pages keep their cards). The
   audit's call: typographic hierarchy carries the separation; the accent
   rails and elevated boxes were non-data ink stacked three deep. The
   explorer keeps its container — the one place a boundary MEANS something
   (everything inside it re-slices; everything outside stays citywide). */
.ownership-page--flat .bp-body .ownership-section:not(.ownership-card):not(.ownership-explorer) {
    background: none;
    border: none;
    border-radius: 0;
    padding: 0;
    margin-bottom: var(--space-6);
}
/* Real heading tiers: section titles lead, subheads step down — they were
   the same size, which made peer cards read as unrelated islands. */
.ownership-page--flat .bp-body .ownership-section-title {
    font-size: 14.5px;
    font-weight: 650;
    color: var(--color-fg-primary);
    text-transform: none;
    letter-spacing: 0;
}
.ownership-page--flat .bp-body .ownership-section-subhead {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--color-fg-secondary);
    text-transform: none;
    letter-spacing: 0;
}
/* Followup subtitles (mid-section second lede) get rhythm from a class,
   not inline styles. */
.ownership-section-subtitle--followup { margin-top: var(--space-5); }
/* Badge rule: the count chip states the ROW count of the list it heads —
   quiet, tabular, never a parcel total. */
.ownership-page--flat .ownership-section-count {
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    color: var(--color-fg-tertiary);
}

/* Map legend: five ramp steps between formatted extremes. */
.own-map-subrow { display: flex; flex-wrap: wrap; align-items: center;
                  gap: var(--space-2); }
/* Text for assistive tech only. Added for the bar-table's mini-bar column: its
   header was genuinely empty (axe `empty-table-header`), and a column with no
   name is unannounced to a screen-reader user even though it is the column the
   sighted reader gets the magnitude from. Clipped rather than
   `display: none` — hidden text is not read.

   DELIBERATELY IN FLOW, not `position: absolute`. The usual recipe takes the
   label out of flow, and an out-of-flow box resolves against the nearest
   POSITIONED ancestor — which, for a label inside a horizontally scrolling
   table, is outside the scroll box. Measured on /sales at 320px the moment
   these three labels were added: they escaped `.table-scroll` and gave the
   PAGE a 48px horizontal scroll, which is the exact defect `.table-scroll`
   exists to prevent. One pixel in flow cannot leave its cell. */
.u-visually-hidden {
    display: inline-block; width: 1px; height: 1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.own-map-legend { display: inline-flex; align-items: center; gap: 2px; }
.own-map-legend-step { width: 16px; height: 8px; border-radius: 1px; }
.own-map-legend-val { font-size: 11px; color: var(--color-fg-tertiary);
                      font-variant-numeric: tabular-nums; padding: 0 3px; }
/* The no-data class, keyed. It is a separate group so it reads as a class
   apart from the ramp rather than a sixth step on it, and it wraps to its own
   line in the narrow vertical panel. The swatch is the map's actual no-data
   fill — a translucent grey that would be invisible on the card, so it gets a
   hairline so a reader can see the colour they are being told about. */
.own-map-legend-nodata { display: inline-flex; align-items: center; gap: 1px; margin-left: 8px; }
.own-map-legend-step--nodata { border: 1px solid var(--color-border-strong); }

/* Parcel overlay toggle — a compact segmented control that switches the
   zoom-in parcel layer (choropleth-only / distress / foreclosed). */
.own-map-parcels { display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
                   margin-top: 2px; }
.own-map-parcels-label { font-size: 10.5px; font-weight: 700; text-transform: uppercase;
                         letter-spacing: 0.05em; color: var(--color-fg-muted); margin-right: 2px; }
.own-map-parcels button {
    background: transparent; border: 1px solid var(--color-border); color: var(--color-fg-secondary);
    border-radius: var(--radius-pill); padding: 3px 10px;
    font-size: 12px; font-weight: 600; font-family: inherit; cursor: pointer;
}
.own-map-parcels button:hover { background: var(--color-bg-hover); color: var(--color-fg-primary); }
.own-map-parcels button.on { background: var(--color-accent); color: var(--color-on-accent);
                             border-color: var(--color-accent); }
.own-map-parcel-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
                         margin-top: 3px; }
.own-map-plegend-item { display: inline-flex; align-items: center; gap: 4px;
                        font-size: 11px; color: var(--color-fg-tertiary); }
.own-map-plegend-dot { width: 9px; height: 9px; border-radius: 2px; flex: none;
                       box-shadow: 0 0 0 1px rgba(0,0,0,0.12) inset; }

/* Summary count strip under the topic map (foreclosure). */
.fc-pulse-section { margin-top: var(--space-2); margin-bottom: var(--space-6); }
.fc-asof { font-size: 11.5px; color: var(--color-fg-muted); margin: 0 0 var(--space-2);
           font-variant-numeric: tabular-nums; }

/* ── /foreclosure ladder + resources (2026-07-23) ──────────────────────── */
:root {
    --fc-ramp1: #d4956f; --fc-ramp2: #c26a4f; --fc-ramp3: #a8432f; --fc-ramp4: #7f2018;
}
body.theme-dark {
    --fc-ramp1: #7c4a33; --fc-ramp2: #9c5540; --fc-ramp3: #bf5e48; --fc-ramp4: #d67669;
}
.ladder { display: flex; flex-direction: column; gap: 0; }
/* `minmax(0, 1fr)`, not `1fr`, on the card track. A `1fr` track floors at its
   item's min-content, and a grid item's own `min-width` defaults to `auto` —
   so ONE unwrappable descendant anywhere in a rung sizes the whole ladder and
   pushes the page into horizontal scroll. That is what a `white-space: nowrap`
   on /sales's rung links did at 320px (css/sales.css): 70px of body scroll.
   The nowrap is gone; this makes the track unable to carry the next one. */
.ladder .rung { display: grid; grid-template-columns: 58px minmax(0, 1fr); align-items: stretch; gap: 12px; }
.ladder .ry { font-size: 11px; color: var(--color-fg-tertiary); text-align: right;
              padding-top: 12px; font-weight: 600; min-width: 0; overflow-wrap: break-word; }
.ladder .rc { position: relative; border: 1px solid var(--color-border);
              border-left: 4px solid var(--rc); border-radius: var(--radius-md);
              padding: 9px 13px; margin: 5px 0; background: var(--color-bg-elevated); }
/* The connecting spine, and why it lives in the GUTTER.
   The rung is a `58px 1fr` grid with a 12px gap, so the card's left edge is
   70px in and the label column occupies -70px to -12px in these coordinates.
   At `left: -38px` the spine landed 20px inside that column — dead centre of
   the label — and because `.rc` is positioned, its ::before paints OVER the
   in-flow label. Every rung on /sales and /foreclosure rendered with its
   label struck through (measured in Chromium: "Contract" inked 97→145 with
   the spine at 119; a label would have to fit inside 20px to escape it).
   Moved into the 12px gutter it still joins rung to rung — consecutive
   spines meet through the ±5px overhang — and crosses no text at all. */
.ladder .rc::before { content: ""; position: absolute; left: -8px; top: -5px; bottom: -5px;
                      width: 2px; background: var(--color-border); }
.ladder .rc b { font-size: 13.5px; color: var(--color-fg-primary); }
/* The rung's own colour, as a NUMBER. A semantic accent is chosen to work as a
   fill — the cyan the deed rung uses measures 3.68:1 on the card, and 15px/700
   is not "large text" — so the one figure on the rung was the least legible
   thing on it. Darkened from the SAME token so the number keeps the rung's
   identity (§4.2: colour follows the entity), and the rung's border keeps the
   pure token. 80% clears 4.5:1 for every accent in the palette, worst case
   amber at 4.73. Dark theme takes the token unmixed: there the accents are the
   light end of the pair and darkening them would undo the contrast. */
.ladder .rc .rn { float: right; font-size: 15px; font-weight: 700;
                  color: color-mix(in srgb, var(--rc) 80%, #000);
                  font-variant-numeric: tabular-nums; }
body.theme-dark .ladder .rc .rn { color: var(--rc); }
.ladder .rc .rn a { color: inherit; text-decoration: none; }
.ladder .rc .rd { display: block; font-size: 11.5px; color: var(--color-fg-tertiary); margin-top: 2px; }
/* A link inside the rung detail had no color rule anywhere, so it fell
   through to the browser default (#0000EE) — off the palette in light and
   unreadable on the dark surface. Same treatment as every other in-copy
   link on the site. */
.ladder .rc .rd a { color: var(--color-link); text-decoration: none; font-weight: 600; }
.ladder .rc .rd a:hover { text-decoration: underline; }
.ladder .rc .rd em { color: var(--color-good, #3a6b35); font-style: normal; font-weight: 600; }
.ladder .cliff { display: flex; align-items: center; gap: 10px; margin: 8px 0 8px 70px;
                 padding: 7px 13px; border-radius: var(--radius-md);
                 background: color-mix(in srgb, var(--color-danger-accent) 12%, transparent);
                 border: 1px dashed color-mix(in srgb, var(--color-danger-accent) 55%, transparent); }
.ladder .cliffdate { font-size: 14px; font-weight: 800; color: var(--color-danger-accent); }
.ladder .clifflabel { font-size: 12px; color: var(--color-fg-secondary); font-weight: 600; }
.ownership-pstat--good { border-color: color-mix(in srgb, var(--color-good, #3a6b35) 40%, var(--color-border)); }
.ownership-pstat--good .ownership-pstat-value { color: var(--color-good, #3a6b35); }
.fc-pipeline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: var(--space-2); }
.fc-pipe { color: #fff; font-weight: 600; font-size: 12.5px; padding: 7px 14px;
           border-radius: var(--radius-md); font-variant-numeric: tabular-nums; }
.fc-arrow { color: var(--color-fg-tertiary); font-weight: 700; }
@media (max-width: 768px), (max-height: 600px) {
    /* 40px could not hold "Contract" (48px at this size), so the label spilled
       right, out of its column and back over the spine in the gutter. The
       column now fits the longest single word these ladders use; anything
       longer wraps instead of overflowing. */
    .ladder .rung { grid-template-columns: 50px minmax(0, 1fr); gap: 8px; }
    /* same rule as above, in the 8px phone gutter */
    .ladder .rc::before { left: -4px; }
    /* 58px, not 48: the phone rung is a `50px 1fr` grid with an 8px gap, so a
       card's left edge is 58px in. The same 10px drift the /sales turn marker
       had (css/sales.css) — the cliff banner sits between two rungs and has to
       line up with them. */
    .ladder .cliff { margin-left: 58px; }
    .ladder .rc .rn { float: none; display: block; margin-top: 2px; }
    .fc-pipeline { flex-direction: column; align-items: stretch; }
    .fc-arrow { text-align: center; }
}

/* ── Topic pages (/sales, /foreclosure): housing-style full-bleed map ───── */
/* Top padding clears the position:fixed .ownership-nav chip (~48px tall) so it
 * never overlaps the eyebrow/h1 — same clearance the .ownership-page uses. */
.topic-masthead { max-width: 900px; margin: 0 auto; padding: var(--space-10) 18px 10px; }
.topic-eyebrow { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.06em;
                 color: var(--color-fg-tertiary); font-weight: 600; margin: 0 0 2px; }
.topic-masthead h1 { font-size: 22px; margin: 0; letter-spacing: -0.01em; }
.topic-asof { font-size: 12px; color: var(--color-fg-muted); margin: 4px 0 0; font-variant-numeric: tabular-nums; }
/* ladder rung per-100 rate suffix (§4.4 denominator) */
.ladder .rn-rate { font-size: 10.5px; font-weight: 400; color: var(--color-fg-muted); white-space: nowrap; }

/* ══ THE CAVEAT TAG (DESIGN-SYSTEM §7 rule 2) ═════════════════════════
   Four words at most, deep-linking to the canonical statement on
   pipelines.det.land. It has to read as a REFERENCE MARK rather than as
   prose, or it becomes the thing it replaced — so it is smaller than body
   copy, set in the muted tone, and carries a dotted underline instead of a
   page's solid link treatment. It never wraps away from the sentence it
   qualifies.

   SHARED AND UNSCOPED (moved here from css/sales.css, 2026-08-01). /sales
   grew the mark; /foreclosure's benchmark block carries it too. A tag means
   the same thing on every page, so it may not look different on any of them —
   and a page-scoped copy per page is how it would. A page that gives a
   container its own link colour must exempt the tag there
   (`a:not(.sl-tag)`), because a container rule carries more specificity than
   this one does. */
/* `break-inside: avoid` sits beside `white-space: nowrap` for the same
   reason and is now load-bearing: the method text under a full-width figure
   is set in COLUMNS (`--measure-col`), and a mark that a column break can cut
   in half is not one mark. */
.sl-tag {
    display: inline-block; white-space: nowrap; break-inside: avoid;
    font-size: 0.86em; line-height: 1.2; vertical-align: baseline;
    color: var(--color-fg-muted); text-decoration: none;
    border-bottom: 1px dotted var(--color-border-strong);
    padding: 0 1px;
}
.sl-tag::before { content: "\2192\00a0"; opacity: 0.7; }
.sl-tag:hover { color: var(--color-link); border-bottom-color: var(--color-link); }
.sl-tag:focus-visible { outline: 2px solid var(--color-link); outline-offset: 2px; border-radius: 2px; }

/* small "profile →" jump inside actor-page filter rows (actor→place edge) */
.ownership-row-jump { font-size: 10.5px; margin-left: 6px; color: var(--color-accent); text-decoration: none; opacity: 0; transition: opacity .12s; }
.ownership-row-filterable:hover .ownership-row-jump, .ownership-row-jump:focus { opacity: 1; }
.topic-map-section { position: relative; height: calc(100vh - 150px); min-height: 380px;
                     margin-bottom: var(--space-3); }
.own-map--fullbleed { position: absolute; inset: 0; height: 100%; }
.own-map--fullbleed .own-map-canvas { position: absolute; inset: 0; height: 100%;
                                      border-radius: 0; border: none; border-top: 1px solid var(--color-border);
                                      border-bottom: 1px solid var(--color-border); }
.own-map--fullbleed .own-map-head { position: absolute; top: 12px; left: 12px; z-index: 3;
    max-width: min(620px, 92vw); margin: 0; background: var(--color-bg-elevated);
    border: 1px solid var(--color-border); border-radius: var(--radius-lg);
    padding: 10px 13px; box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,.12)); }
.own-map--fullbleed .own-map-card { position: absolute; left: 12px; bottom: 52px; z-index: 3;
    box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,.12)); }
.own-map-loading { position: absolute; inset: 0; display: flex; align-items: center;
    justify-content: center; color: var(--color-fg-tertiary); font-size: 13px; }
.topic-scroll-cue { position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
    z-index: 3; display: flex; align-items: center; gap: 6px; cursor: pointer;
    background: var(--color-bg-elevated); border: 1px solid var(--color-border);
    border-radius: var(--radius-pill); padding: 6px 15px; font-size: 12.5px; font-weight: 600;
    color: var(--color-fg-secondary); box-shadow: var(--shadow-sm, 0 2px 8px rgba(0,0,0,.10)); }
.topic-scroll-cue:hover { color: var(--color-fg-primary); background: var(--color-bg-hover); }
.topic-stats { max-width: 900px; margin: 0 auto; padding: 0 18px 90px; }
.topic-lede { color: var(--color-fg-secondary); font-size: 13.5px; max-width: 64ch;
              margin: 0 0 var(--space-5); }
/* Each section leads with its own descriptive title now (the redundant chapter
 * wrapper is gone), so give it a clean, legible weight like the housing
 * panel-title — not the tiny uppercase eyebrow the rail-TOC layout used. */
.topic-stats .ownership-section-title { font-size: 15px; font-weight: 650;
    text-transform: none; letter-spacing: -0.005em; color: var(--color-fg-primary); }
@media (max-width: 768px), (max-height: 600px) {
    .topic-map-section { height: 72vh; }
    .own-map--fullbleed .own-map-head { max-width: 78vw; }
    /* The label STAYS on phones. Hiding it left a bare chevron parked beside
       the zoom cluster with nothing to say what it did; /outages kept its
       label — only because its markup omits .cue-label, so this rule never
       matched it — and reads as the one comprehensible cue on a phone. A
       control worth keeping is worth naming. The position above still steps
       aside from the zoom controls, and the drill-sheet rule still hides the
       whole cue when a sheet covers it. */
}

/* ── Corner-anchored map chrome (mountOwnershipMap opts.cornerChrome) ──
   THE CONTROL IN ONE CORNER, WHAT THE MAP MEANS IN THE OTHER — the layout
   /eviction and /housing already use, and the reason the layer list can stop
   being capped. The card that held a control AND a key had to be tall; a tall
   card had to be squeezed when the drill panel opened; and the squeeze was
   written as a percentage of viewport height with `overflow: hidden`, inside a
   query whose `(max-height: 600px)` arm has no width bound. Measured on the
   shipped page: at 820×600 and 1440×600 — ordinary short desktop windows — the
   layer list rendered 35px tall with all three layers unreachable, and at
   390×844 two of three. Both arms of that mechanism are gone below; nothing
   replaces them, because with the key in its own corner there is nothing left
   to squeeze. */
.own-map--corner .own-map-key {
    position: absolute; top: 12px; right: 12px; z-index: 3;
    width: min(268px, 72vw); max-width: min(268px, 72vw);
    max-height: calc(100% - 24px); overflow-y: auto;
    margin: 0; padding: 10px 13px;
    background: var(--color-bg-elevated); border: 1px solid var(--color-border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-md, 0 4px 16px rgba(0,0,0,.12));
}
.own-map--corner .own-map-key[hidden] { display: none; }
.own-map-key-title {
    margin: 0 0 2px; font-size: 12.5px; font-weight: 650; line-height: 1.25;
    color: var(--color-fg-primary);
}
/* The citywide reference (§4.1) — the number the ramp is read against. */
.own-map-key-ref {
    margin: 0 0 6px; font-size: 11px; line-height: 1.4; color: var(--color-fg-muted);
    font-variant-numeric: tabular-nums;
}
.own-map-key-ref[hidden] { display: none; }
.own-map-key .own-map-subrow { margin: 0; }
.own-map-key .own-map-stamp { margin-top: 6px; }
/* The ramp is one row; the no-data key takes its own, rather than wrapping its
   label three words to a line beside the swatches in a 268px card. */
.own-map-key .own-map-legend { flex-wrap: wrap; }
.own-map-key .own-map-legend-nodata { flex-basis: 100%; margin-left: 0; margin-top: 5px; }
/* The control card keeps only its title and its layers, so it is short at
   every viewport and never needs a height cap of its own. */
.own-map--corner .own-map-head--vert { max-height: calc(100% - 24px); }

/* ── The VERTICAL layer panel (mountOwnershipMap opts.vertical) ───────
   Why vertical, and why this is not a style preference: the horizontal chip
   row sizes to its own content, so a float-card capped at 78vw held a 526px
   row and clipped every lens past the card's edge — the row's own overflow-x
   never engaged because it was never stretched to its container. Stacking the
   layers turns label length from a WIDTH problem (which overflows) into a
   HEIGHT one (which the panel absorbs, and scrolls if it must). So:
     · the panel has a FIXED width and each row is `width: 100%`;
     · `white-space: normal` — a long name wraps INSIDE its row;
     · `min-width: 0` everywhere, so nothing can push the panel wider;
     · `max-height` + `overflow-y` on the list, so a fourth or fifth layer
       scrolls the list rather than growing the card off the map.
   Adding a layer with a longer name can therefore never re-open the bug. */
/* `flex-wrap: nowrap` IS LOad-BEARING, not tidiness. `.own-map-head` sets
   `flex-wrap: wrap` for the horizontal chip layout, and this panel carries
   both classes. A WRAPPING COLUMN flex container that is given a height cap
   does not scroll — it spills into a SECOND COLUMN beside the first. Measured
   the moment the drilled cap below was added: the layer list jumped from
   x 25 to x 225, 185px past the card's own right edge (and clipped away by
   the cap's `overflow: hidden`), i.e. the height constraint converted itself
   straight back into the width overflow this panel exists to prevent (§6). */
.own-map-head--vert { display: flex; flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 8px; }
.own-map--fullbleed .own-map-head--vert {
    width: min(272px, 72vw); max-width: min(272px, 72vw);
    max-height: calc(100% - 24px); overflow: hidden;
}
.own-map-headline {
    margin: 0; font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--color-fg-muted);
}
.own-map-layers {
    display: flex; flex-direction: column; gap: 2px; min-width: 0;
    max-height: min(46vh, 320px); overflow-y: auto;
}
.own-map-layer {
    /* positioned so the phone breakpoint's visually-hidden sub-line (below)
       is clipped against its own row, not the nearest positioned ancestor */
    position: relative;
    display: block; width: 100%; min-width: 0; text-align: left; cursor: pointer;
    padding: 7px 9px; border: 1px solid transparent; border-radius: var(--radius-md);
    background: none; color: var(--color-fg-secondary); font-family: inherit;
}
.own-map-layer:hover { background: var(--color-bg-hover); color: var(--color-fg-primary); }
.own-map-layer:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.own-map-layer.on {
    background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent);
}
.own-map-layer-name {
    display: block; font-size: 12.5px; font-weight: 600; line-height: 1.3;
    /* wrap, never clip — the whole point of the vertical panel */
    white-space: normal; overflow-wrap: anywhere;
}
.own-map-layer-sub {
    display: block; margin-top: 1px; font-size: 10.5px; line-height: 1.4;
    white-space: normal; overflow-wrap: anywhere; color: var(--color-fg-muted);
}
.own-map-layer.on .own-map-layer-sub { color: inherit; opacity: 0.9; }

/* ── The SELECTED map control, in both themes ─────────────────────────
   `--color-accent` is a FILL colour, and white on it is not readable at
   control type. Measured by axe on /sales, on the row that names the painted
   lens and carries its denominator (§4.4) — the most important control on the
   map, and the worst-contrast text on the page:

     light  #fff on #0080ff = 3.79   ·  its sub-line = 3.01
     dark   #fff on #4dabf7 = 2.47   ·  its sub-line = 2.12

   Both themes failed, and they failed for OPPOSITE reasons, so one value
   cannot fix both. In light the accent is dark enough to carry white text
   once it is the *strong* accent (#0066cc → 5.57). In dark the accent is a
   bright sky blue; nothing white belongs on it, and the legible pairing is
   the surface colour ON the accent (#1e1e1e on #4dabf7 → 6.73). The sub-line
   rides the same foreground at 0.9 rather than a second hardcoded tint, so it
   can never drift from the row it sits in (light 4.83, dark 5.62).

   Every selected control in the map family is listed, not just the /sales
   row: they are the same widget in different clothes and they all inherited
   the same pairing. */
.own-map-layer.on,
.own-map-tabs button.on, .ownership-lens-tabs button.on,
.own-map-groups button.on, .own-map-parcels button.on,
.ownership-filter-btn.is-active {
    background: var(--color-accent-strong); border-color: var(--color-accent-strong);
    color: var(--color-on-accent);
}
body.theme-dark .own-map-layer.on,
body.theme-dark .own-map-tabs button.on, body.theme-dark .ownership-lens-tabs button.on,
body.theme-dark .own-map-groups button.on, body.theme-dark .own-map-parcels button.on,
body.theme-dark .ownership-filter-btn.is-active {
    background: var(--color-accent); border-color: var(--color-accent);
    color: var(--color-bg-elevated);
}
.own-map-layer-group {
    margin: 6px 0 1px; font-size: 10px; font-weight: 700; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--color-fg-tertiary);
}
.own-map-layer-group:first-child { margin-top: 0; }
/* Freshness stamp (§7 / §4.7) — belongs to the ACTIVE layer, because the
   layers on one map can be fed by feeds that stop on different dates. */
.own-map-stamp {
    margin: 0; padding-top: 6px; border-top: 1px solid var(--color-border);
    font-size: 10px; line-height: 1.5; color: var(--color-fg-tertiary);
    font-variant-numeric: tabular-nums;
}
.own-map-stamp[hidden] { display: none; }
.own-map-head--vert .own-map-subrow { min-width: 0; }
.own-map-head--vert .own-map-legend { flex-wrap: wrap; }

/* ── Parcel drill-down panel — SHARED chrome ──────────────────────────
   mountOwnershipMap creates .own-map-drill for any page that passes a `drill`
   config (/foreclosure, /sales). It lived scoped under .foreclosure-page until
   the second page opted in; it lives here now so the two cannot drift. Only
   the CONTENT inside .own-map-drill-body is per-page (.fc-drill-* /
   .sl-drill-*). Token-only, so dark mode arrives via body.theme-dark. */
.own-map-drill {
    position: absolute; top: 12px; right: 12px; z-index: var(--z-panel);
    width: 360px; max-height: calc(100% - 24px);
    display: flex; flex-direction: column; overflow: hidden;
    background: var(--color-bg-elevated); border: 1px solid var(--color-border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
    color: var(--color-fg-primary);
}
.own-map-drill[hidden] { display: none; }
.own-map-drill-head {
    display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2);
    padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-border); flex: 0 0 auto;
}
.own-map-drill-title { margin: 0; font-size: 16px; font-weight: 650; letter-spacing: -0.2px; }
.own-map-drill-sub {
    font-size: 11.5px; color: var(--color-fg-muted); margin-top: 2px; font-variant-numeric: tabular-nums;
}
.own-map-drill-close {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; padding: 0; cursor: pointer;
    background: none; border: 1px solid var(--color-border); border-radius: var(--radius-sm);
    color: var(--color-fg-muted);
}
.own-map-drill-close:hover { background: var(--color-bg-hover); color: var(--color-fg-primary); }
.own-map-drill-close svg { width: 15px; height: 15px; display: block; }
/* Point legend — the map encoding, stated. Sits between header and list so it
   reads before the rows it explains. */
.own-map-drill-legend {
    display: flex; flex-wrap: wrap; gap: 4px var(--space-3); flex: 0 0 auto;
    padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--color-border-muted);
    font-size: 10.5px; color: var(--color-fg-muted);
}
.own-map-drill-legend[hidden] { display: none; }
.own-map-drill-lgd { display: inline-flex; align-items: center; gap: 5px; }
.own-map-drill-dot {
    width: 9px; height: 9px; border-radius: 50%; flex: none;
    border: 1px solid color-mix(in srgb, var(--color-fg-primary) 18%, transparent);
}
.own-map-drill-body {
    padding: var(--space-4); overflow-y: auto; font-size: 12.5px; color: var(--color-fg-secondary);
}
/* §9 loading / empty share one quiet inline treatment; error uses .load-error,
   forced visible here since almanac copy defaults it off. */
.own-map-drill-note {
    color: var(--color-fg-muted); padding: var(--space-5) 0; text-align: center; line-height: 1.55;
}
.own-map-drill-body .load-error { display: block; margin: 0; font-size: 12.5px; }

/* KEEP THE MAP CONTROLS REACHABLE WHILE THE PANEL IS OPEN.
   MapLibre parks its zoom buttons and attribution in the bottom-right corner;
   the drill panel is a near-full-height right-hand column on desktop and a
   bottom sheet on a phone, and it landed squarely on top of them. Measured at
   390px with the sheet open: zoom in, zoom out, the attribution AND the scroll
   cue were all covered, and elementFromPoint returned the panel — the controls
   existed and could not be clicked. `own-map--drilled` is set by
   mountOwnershipMap while the panel is open, so the corner moves out of the
   way and moves back when it closes. Desktop slides the corner left of the
   360px panel; the phone lifts it above the sheet. */
.own-map--drilled .maplibregl-ctrl-bottom-right { right: 372px; }

/* The HORIZONTAL head (/foreclosure, /ownership) is content-width up to 620px,
   and the drill panel is a 360px column pinned 12px from the right — so on a
   narrow desktop the two met: at 820px wide the head reached x=652 and the
   panel began at x=448. It was already reachable at ~900px before this pass,
   and binding the bottom sheet to width (above) brought 820×600 into the same
   case, so it is fixed rather than inherited. While drilled, the head stops
   short of the column; unconstrained the moment the panel closes. The vertical
   panel is 272px and never came close, but the rule is written for the head in
   general so a longer lens row cannot re-open it. */
@media (min-width: 769px) {
    .own-map--drilled.own-map--fullbleed .own-map-head {
        max-width: min(620px, calc(100% - 396px));
    }
}

/* THE BOTTOM SHEET IS A NARROW-SCREEN LAYOUT, AND ONLY THAT.
   This query used to read `(max-width: 768px), (max-height: 600px)`, and the
   second arm carried no width bound — so an 820×600 or 1440×600 desktop window
   was handed the phone's bottom sheet, and with it the drilled height cap that
   collapsed the layer list to 35px. A short window is not a phone: it has the
   full width the right-hand column was designed for, and the column costs it
   no vertical room at all. Bound to width, a short desktop keeps the column
   and the bug has nowhere left to happen. */
@media (max-width: 768px) {
    /* Phone: the panel becomes a bottom sheet (§9 responsive). */
    .own-map-drill {
        top: auto; left: 12px; right: 12px; bottom: 12px; width: auto; max-height: 58%;
    }
    /* The sheet is bottom-anchored at 12px and at most 58% tall, so anything
       above 58% + a margin clears it at every height it can take. */
    .own-map--drilled .maplibregl-ctrl-bottom-right { right: 0; bottom: calc(58% + 20px); }
    /* The vertical panel keeps its own width rather than inheriting the
       horizontal head's 78vw — a 78vw panel on a 390px phone leaves the map a
       sliver, and the layer names already wrap at 232px. */
    .own-map--fullbleed .own-map-head--vert {
        width: min(232px, 62vw); max-width: min(232px, 62vw);
    }

    /* WHILE THE SHEET IS OPEN, THE CONTROL CARD LIES DOWN.
       It used to merely compact — every layer kept a full-height row and the
       inactive layers' normalization lines were clipped — and that was enough
       to keep the list reachable, which is what that pass was measuring. It was
       nowhere near enough to keep the MAP usable. The card is 192px tall and
       198px wide on a 320px phone, pinned top-left; the sheet takes the bottom
       58%; between them the canvas has a 26px strip left. Measured on a drill
       with 150 addresses: 35 of them were hit-testable at 320x800 and 39 at
       390x844 — `elementFromPoint` returned THIS CARD for 115 and 111 of them.
       Plotting the addresses is the drill's whole purpose.
       A tall card in the corner cannot be padded around: the fit has to clear
       either its height (leaving 26px) or its width (leaving 78px), and both
       are worse than the occlusion. So while the sheet is open the card stops
       being a column and becomes a full-width strip of equal chips across the
       top — 320px of chrome that is ~110px tall instead of 198x192 — and the
       canvas keeps a real band underneath it. Nothing is removed and nothing
       is capped: three chips, three hit targets, each still `width: 100%` of
       its own track, still wrapping its label rather than clipping it.
       The sub-lines all go visually — including the active layer's, which the
       compact state kept. In THIS state the choropleth has faded to 0.1 (the
       drill's own fade band) and the points are what is being read, with the
       drill legend naming them; the denominator of a tint nobody is reading is
       not worth 60px of map. `clip` rather than `display: none`, so every one
       of them stays in the accessibility tree exactly as before. */
    .own-map--drilled .own-map-layer .own-map-layer-sub,
    .own-map--drilled .own-map-head--vert .own-map-headline {
        position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
        overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
    }
    .own-map--drilled.own-map--fullbleed .own-map-head--vert {
        width: auto; max-width: none; right: 12px;
    }
    .own-map--drilled .own-map-head--vert .own-map-layers {
        flex-direction: row; flex-wrap: nowrap; gap: 4px;
        max-height: none; overflow: visible;
    }
    /* `flex: 1 1 0` + `min-width: 0` — equal tracks that shrink, so a fourth
       layer or a longer label makes the chips narrower and taller, never the
       strip wider. This is the vertical panel's own rule (turn a WIDTH problem
       into a HEIGHT one) applied along the other axis. */
    .own-map--drilled .own-map-head--vert .own-map-layer {
        flex: 1 1 0; min-width: 0; text-align: center; padding: 7px 5px;
    }
}

/* THE KEY MOVES CORNERS WHEN THE TWO CARDS WILL NOT FIT SIDE BY SIDE, and 620
   is where that stops being true — not 768.
   It goes bottom-left (where /eviction puts its legend), above the scroll cue
   at bottom-centre and clear of MapLibre's own bottom-right corner; it still
   swaps with the drill panel, so it is never under the sheet. That is the
   right layout at 320 and 390, where a 232px control and a 232px key cannot
   share a row. It was written into the sheet's `max-width: 768px` query for
   convenience, and that overshot: at 768 both cards are 232px wide, so
   12 + 232 + 232 + 12 = 488 of 768 — the tablet was paying a phone's price for
   space it had, and the brief's "control top-left, key top-right" quietly did
   not hold at two of the six viewports it is checked at. 620 is the narrowest
   width that leaves a gap between them wide enough to still read as a map
   rather than as a wall of chrome. */
@media (max-width: 620px) {
    .own-map--corner .own-map-key {
        top: auto; bottom: 12px; right: auto; left: 12px;
        width: min(232px, 62vw); max-width: min(232px, 62vw); max-height: 46%;
    }
}

/* ── Parcel figure kit (js/shared/parcel-viz.js — §5 primitives) ──────────
   The hand-rolled SVG figures that replaced Chart.js on the parcel
   surfaces (paired delta, year mini-series, dot-metric rows). Every
   color below is a token, so body.theme-dark inherits the dark variant
   from the token flips at the top of this file — there is deliberately
   no separate dark block for the kit (§9: dark ships in the same change;
   here it ships in the same declaration). */
.pv-fig { margin: 0; }
.pv-fig + .pv-fig { margin-top: var(--space-4); }
.pv-fig__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: var(--space-1);
}
.pv-fig__label { font-size: 12.5px; font-weight: 600; color: var(--color-fg-secondary); }
/* Vintage stamp (§4.5) — the figure's data year, visible, not buried. */
.pv-vintage {
    font-size: 10px;
    color: var(--color-fg-muted);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill);
    padding: 0 var(--space-2);
    background: var(--color-bg-muted);
    white-space: nowrap;
}
.pv-fig svg { display: block; width: 100%; height: auto; }
/* SVG type: muted ink for axes/pole labels, primary + tabular for the
   direct value labels at the marks (§4.3, §4.6). */
.pv-fig text { font-family: inherit; font-size: 10.5px; fill: var(--color-fg-muted); }
.pv-fig text.pv-val {
    font-size: 11.5px;
    font-weight: 700;
    fill: var(--color-fg-primary);
    font-variant-numeric: tabular-nums;
}
.pv-fig text.pv-delta { font-size: 11px; font-weight: 600; }
/* A faint baseline with sparse ticks carries the scale — no gridlines. */
.pv-fig .pv-axis { stroke: var(--color-border-strong); }
.pv-fig .pv-refline { stroke: var(--color-fg-muted); stroke-dasharray: 3 3; opacity: 0.6; }
/* Multi-year line series (renderMultiYearLines): entity lines carry their
   identity color inline; the reference series is dashed + muted, and the
   direct end labels pair a colored entity name with a primary-ink value. */
.pv-fig .pv-line { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.pv-fig .pv-line--ref { stroke: var(--color-fg-muted); stroke-dasharray: 4 4; opacity: 0.65; }
.pv-fig circle.pv-dot--ref { fill: var(--color-fg-muted); opacity: 0.65; }
.pv-fig tspan.pv-line-name { font-weight: 600; }
.pv-fig tspan.pv-line-val {
    font-weight: 700;
    fill: var(--color-fg-primary);
    font-variant-numeric: tabular-nums;
}
/* Breakdown / method line under a figure ("34 building · 82 trade"). */
.pv-fig__note {
    margin: var(--space-2) 0 0;
    font-size: 11.5px;
    color: var(--color-fg-muted);
    font-variant-numeric: tabular-nums;
    max-width: var(--measure-deck);
}
/* Single-scalar fallback for one-recorded-year series — a chart with one
   point is a fake; a labeled number is not. */
.pv-single { display: flex; align-items: baseline; gap: var(--space-2); }
.pv-single__num {
    font-size: 22px;
    font-weight: 700;
    color: var(--color-fg-primary);
    font-variant-numeric: tabular-nums;
}
.pv-single__label { font-size: 12px; color: var(--color-fg-muted); }
/* Dot rows rendered inside an elevated card: the dot's separating ring
   must match the card surface, not the page surface .cmp-bars assumes. */
.pv-cmp { --cmp-surface: var(--color-bg-elevated); }
body.theme-dark .pv-cmp { --cmp-surface: var(--color-bg-elevated); }
/* Parcel dot rows name EVENTS ("May 2004 · DLBA auction"), not short
   neighborhood names — give the name column room so the source (the
   non-color encoding of source identity) survives; the track absorbs
   the difference. */
.pv-cmp .cmp-bar { grid-template-columns: minmax(84px, 180px) 1fr minmax(50px, auto); }

/* ═══════════════════════════════════════════════════════════════════
   Parcel surface — structural pass (redesign PR 3).
   The sidebar panel + expanded dashboard render one shared module set
   (hero → masthead → topic strip → the module stack); these are the ported
   mockup patterns (_mockups/parcel-{sidebar,dashboard}-2026-08-06.html)
   on the real tokens. Everything is scoped under .parcel-shell (the
   module-stack wrapper both hosts emit) except the two expansion nubs,
   which are viewport chrome. Dark mode is inherited via tokens.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Expand-to-dashboard nub — a drawer handle on the panel's left edge.
      Replaces the old ⤢ #panel-expand button. Wired to the same
      selection-store posture toggle; `F` still works. Hidden exactly
      where the dashboard is force-disabled (see FORCE_SIDEBAR_* in
      js/ui/expanded-dashboard.js — the JS gate and the media query
      below MUST stay in sync so nobody strands in an unreachable
      posture). */
#expand-nub {
    position: fixed;
    top: 24vh;
    right: calc(var(--sidebar-width) - 10px);
    z-index: var(--z-panel);
    width: 20px;
    height: 56px;
    padding: 0;
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--color-bg-elevated);
    color: var(--color-fg-muted);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-lg) 0 0 var(--radius-lg);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: background var(--motion-fast), color var(--motion-fast);
}
body.sidebar-visible #expand-nub { display: flex; }
body.dashboard-visible #expand-nub,
body.survey-mode #expand-nub { display: none; }
#expand-nub:hover { background: var(--color-bg-hover); color: var(--color-accent-text); }
#expand-nub svg { width: 14px; height: 14px; }
/* Phone-class (per CLAUDE.md pair): the sidebar is a bottom sheet, so
   the nub becomes a handle centered on the sheet's top edge, chevron
   pointing up. */
@media (max-width: 768px), (max-height: 600px) {
    #expand-nub {
        top: auto;
        bottom: calc(40% - 10px);
        right: auto;
        left: 50%;
        transform: translateX(-50%);
        width: 56px;
        height: 20px;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    }
    #expand-nub svg { transform: rotate(90deg); }
}
/* The dashboard itself is force-disabled below 600px wide or 600px tall
   (expanded-dashboard.js enforceBreakpoint) — hide its entry point in
   exactly that region. */
@media (max-width: 599px), (max-height: 600px) {
    #expand-nub { display: none !important; }
}

/* Collapse-back nub — the mirrored handle on the dashboard's left edge. */
#collapse-nub {
    position: fixed;
    top: 24vh;
    left: 0;
    z-index: var(--z-in-panel-strip);
    width: 20px;
    height: 56px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-bg-elevated);
    color: var(--color-fg-muted);
    border: 1px solid var(--color-border-strong);
    border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: background var(--motion-fast), color var(--motion-fast);
}
#collapse-nub:hover { background: var(--color-bg-hover); color: var(--color-accent-text); }
#collapse-nub svg { width: 14px; height: 14px; }

/* ── Street-view hero — full-bleed media at the panel's very top. The
      mapillary container mounts into #mobile-mapillary-slot inside;
      the scrim is a sibling overlay (pointer-events pass through). */
.parcel-shell .sv-hero {
    position: relative;
    /* Overflow visible on purpose: the in-frame date pill's drill-down
       menu opens below the frame. Nothing to clip — the hero has no
       rounding in the flush sidebar. */
    /* Dark media frame, NOT the elevated panel color: before any photo
       paints (first open, cold OG edge, placeholder in flight) the hero
       shows this surface, and a white hole above "Loading parcel…" reads
       broken (Dexter's 2026-08-07 phone recording, frames f025–f030). */
    background: var(--color-media-frame);
    /* Full bleed: cancel #panel-body's padding (--space-3/--space-4). */
    margin: calc(-1 * var(--space-3)) calc(-1 * var(--space-4)) 0;
}
.parcel-shell .sv-hero .sv-scrim,
.dash-mapillary-pane .sv-scrim {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    /* Bare legibility gradient under the in-frame vintage pills. It
       carries no text of its own (the provenance line moved into the
       Reference module in the chrome-diet pass), so the footprint is an
       explicit height rather than content + padding. */
    height: var(--space-10);
    background: linear-gradient(to top, var(--scrim-strong), var(--scrim-soft) 55%, transparent);
    pointer-events: none;
}
/* The dash pane's scrim sits above the reparented viewer container
   (which carries z-index:1 in-dashboard); events pass through to the
   viewer + its in-frame pills. */
.dash-mapillary-pane .sv-scrim { z-index: 2; }

/* Report-a-problem flag — ONE element, two homes. In the masthead slot
   it's a quiet inline icon; at its structural home (sidebar root, shown
   for neighborhood selections) it floats top-right beside the × close. */
#panel-report-bug {
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
    color: var(--color-fg-muted);
    padding: 0;
}
#panel-report-bug:hover {
    background: var(--color-bg-hover);
    color: var(--color-fg-secondary);
}
#panel-report-bug[hidden] { display: none; }
#details-sidebar > #panel-report-bug {
    position: absolute;
    top: var(--space-2);
    /* Clear of the 24px × close button + its gutter. */
    right: calc(var(--space-2) * 2 + 24px);
    z-index: var(--z-in-panel-strip);
}

/* §8 — one lineage link per surface: the module stack's reference tail
   carries it; the legacy renderers' per-section source lines are
   suppressed inside the shell (they still render on the owner-profile
   docked panel, which hasn't migrated yet). */
.parcel-shell .section-source { display: none; }
/* The dated instrument line under the lead owner name is DATA, not a
   source attribution — it must survive the shell's source-line purge. */
.record-line {
    font-size: 11px;
    color: var(--color-fg-muted);
    margin: 2px 0 var(--space-2);
}
.parcel-shell .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
}

/* ── Masthead — identity block directly beneath the hero. */
.parcel-shell .masthead {
    position: relative;
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--color-border);
}
.parcel-shell .masthead h1 {
    margin: 0;
    font-size: 19px;
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -0.2px;
    padding-right: 28px;
    color: var(--color-fg-primary);
}
.parcel-shell .masthead .sub {
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-fg-muted);
}
/* The bug reporter (#panel-report-bug) is REPARENTED into this slot on
   parcel renders — same element, same wiring, new home. */
.parcel-shell .masthead .bug-slot {
    position: absolute;
    top: var(--space-4);
    right: 0;
}
/* Favorite star sits left of the bug button in the masthead's top-right
   corner; same live-element reparent pattern (see favorite-star.js). */
.parcel-shell .masthead .fav-slot {
    position: absolute;
    top: var(--space-4);
    right: 34px;
}
.fav-star {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--color-text-muted);
    font-size: 17px;
    cursor: pointer;
    transition: color var(--motion-fast), background var(--motion-fast);
}
.fav-star--bp {
    display: inline-grid;       /* stays on the title line */
    font-size: 0.55em;          /* sized to the h1 it sits inside */
    margin-left: var(--space-2);
    vertical-align: 0.15em;
}

/* phone-class = narrow OR short (canonical pair): honor the touch floor */
@media (max-width: 768px), (max-height: 600px) {
    .fav-star--bp {
        width: 44px;
        height: 44px;
    }
}

.fav-star:hover {
    background: var(--color-bg-hover);
    color: var(--color-text);
}
.fav-star--on {
    color: var(--color-warning-accent);
}
.fav-star--on svg {
    fill: currentColor;
}
.fav-star-tip {
    position: absolute;
    top: calc(var(--space-4) + 32px);
    right: 0;
    z-index: var(--z-map-control);
    padding: 2px var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--color-text);
    color: var(--color-bg-elevated);
    font-size: 0.7rem;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--motion-fast);
}
.fav-star-tip--show {
    opacity: 1;
}
/* §2 — three beats, visually separated: WHERE it is (address + record line
   + the boundary chips that navigate there), WHO owns it, and WHAT IS UP
   with it. The boundary chips are navigation and the flag chips are state;
   before this they were two identical rows of pills and read as one pile. */
.parcel-shell .mh-where {
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-border);
}
.parcel-shell .bchips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-top: var(--space-3);
}
.parcel-shell .bchip {
    font-size: 11px;
    color: var(--color-fg-secondary);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill);
    padding: 1px 9px;
    background: var(--color-bg-muted);
    text-decoration: none;
    font-family: inherit;
    cursor: pointer;
}
.parcel-shell .bchip:hover {
    border-color: var(--color-accent);
    color: var(--color-accent-text);
}
.parcel-shell .owner-line {
    margin-top: var(--space-4);
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.parcel-shell .owner-kicker {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-fg-muted);
    width: 100%;
}
.parcel-shell .owner-line .who { font-size: 14px; font-weight: 600; }
.parcel-shell .owner-line .who a { color: var(--color-accent-text); text-decoration: none; }
.parcel-shell .owner-line .who a:hover { text-decoration: underline; }
/* The owner line is a DATED public record, not a resolution verdict. */
.parcel-shell .owner-dated { font-size: 10.5px; color: var(--color-fg-muted); }

/* ── Scorecard chips — the above-the-fold live risk state. */
.parcel-shell .score-chips,
.dash-hero .score-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-3);
}
/* Programmes and designations sit beside the tax numbers they act on, not
   in the masthead beside a warning about unpaid blight (§2). */
.parcel-shell .mod-body > .score-chips--designations { margin: 0 0 var(--space-3); }
.schip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    border-radius: var(--radius-pill);
    padding: 2px 10px;
    border: 1px solid var(--color-border-strong);
    color: var(--color-fg-secondary);
    background: var(--color-bg-muted);
}
.schip .ic { color: var(--color-fg-muted); }
.schip--good { background: var(--color-success-bg); border-color: var(--color-success-border); color: var(--color-success-fg); }
.schip--good .ic { color: var(--color-success-accent); }
.schip--warn { background: var(--color-warning-bg); border-color: var(--color-warning-border); color: var(--color-warning-fg); }
.schip--warn .ic { color: var(--color-warning-accent); }
.schip .ic,
.parcel-shell .ic {
    display: inline-flex;
    width: 1em;
    height: 1em;
    vertical-align: -0.12em;
}
.schip .ic svg,
.parcel-shell .ic svg { width: 100%; height: 100%; }

/* ── Module chrome: always-visible sections with sticky mini-headers. */
.parcel-shell .mod {
    border-bottom: 1px solid var(--color-border);
    scroll-margin-top: 44px;
}
.parcel-shell .mod-head {
    position: sticky;
    top: 0;
    z-index: var(--z-in-panel-head);
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-3) 0 var(--space-2);
    background: var(--color-bg-elevated);
}
.parcel-shell .mod-head h2 {
    margin: 0;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--color-fg-secondary);
}
/* When the topic strip is present it sticks first; the module heads
   stack just beneath it. Quiet parcels (no strip) stick at the top. */
.parcel-shell:has(.topic-strip) .mod-head { top: 37px; }
.parcel-shell .mod-stamp {
    font-size: 10.5px;
    color: var(--color-fg-muted);
    white-space: nowrap;
}
.parcel-shell .mod-body { padding: 0 0 var(--space-5); }

/* ── §3: a module that folds. The head becomes the <summary>, so it keeps
      the sticky mini-header it already had, and the summary line stays
      readable whether the body is open or shut — a collapsed section that
      says only its own title costs the reader a click to learn nothing. */
.parcel-shell .mod-fold > summary.mod-head {
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.parcel-shell .mod-fold > summary.mod-head::-webkit-details-marker { display: none; }
.parcel-shell .mod-fold > summary.mod-head:hover h2 { color: var(--color-accent-text); }
.parcel-shell .mod-fold > summary.mod-head:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}
.parcel-shell .mod-sum {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 11px;
    color: var(--color-fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.parcel-shell .mod-chev {
    flex: none;
    color: var(--color-fg-muted);
    transition: transform var(--motion-fast, 120ms) ease;
}
.parcel-shell .mod-fold[open] > summary.mod-head .mod-chev { transform: rotate(180deg); }
/* A folded module's own bottom padding belongs to the body, so a shut one
   collapses to the height of its head. */
.parcel-shell .mod--fold .mod-head { padding-bottom: var(--space-3); }
.parcel-shell .mod h3 {
    font-size: 12px;
    margin: var(--space-4) 0 var(--space-2);
    color: var(--color-fg-secondary);
}

/* ── Lineage line — ONE per surface (§8), lives in the reference tail. */
.parcel-shell .lineage {
    margin-top: var(--space-4);
    font-size: 10.5px;
    color: var(--color-fg-muted);
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-3);
}
.parcel-shell .lineage a { color: var(--color-accent-text); text-decoration: none; }
.parcel-shell .lineage a:hover { text-decoration: underline; }

/* ── Dashboard host: the same modules, roomier. Two-column card grid;
      mini-headers don't stick (the dashboard has its own nav rail). */
.dash-parcel-shell {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
    align-items: start;
}
.dash-parcel-shell > .mod {
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-3) var(--space-5) 0;
    box-shadow: var(--shadow-sm);
    min-width: 0;
    scroll-margin-top: var(--space-3);
}
.dash-parcel-shell > .mod--wide { grid-column: 1 / -1; }
.dash-parcel-shell .mod-head {
    position: static; /* the dashboard's nav rail handles wayfinding */
    background: transparent;
}
.dash-parcel-shell .sv-hero { display: none; } /* media row owns street view */
@media (max-width: 1023px) {
    .dash-parcel-shell { grid-template-columns: 1fr; }
}

/* Phone-class panel (bottom sheet): keep the hero flush. */
@media (max-width: 768px), (max-height: 600px) {
    .parcel-shell .mod { scroll-margin-top: 40px; }
}

/* ============================================================
   MAP-PAGE PANEL OVERRIDES + PERMIT CARDS
   (moved from the index.html <style> block, style-consolidation)
   ============================================================
   Scoping note — why :where(#details-sidebar, #expanded-dashboard):
   theme.css is shared with app.html (SPA routes), whose docked
   profile parcel panel renders the SAME .permit-card / .detail-row
   markup but has never loaded these rules (they lived in
   index.html's inline <style>, map page only). The :where() wrapper
   contributes ZERO specificity, so each rule keeps the exact
   specificity it had inline — including losing to the
   body.theme-dark variants below, exactly as before — while only
   matching inside the map page's two panel roots. Restyling the
   app.html docked panel to match would be an intentional visual
   change; take it separately if wanted.

   Positioned at the END of theme.css on purpose: the inline block
   these came from sat after every <link>, so end-of-file preserves
   its order-tie wins over earlier equal-specificity rules (e.g. the
   base .detail-row above).

   Colors/sizes stay literal on purpose: the live values (6px radius,
   #eee/#888/#aaa greys) either have no exact token equivalent, or —
   like #eee vs --color-border — the token flips under
   body.theme-dark while the live rendering does not. Tokenizing
   would be a visual change. */

/* Map-page .detail-row rule, mirrored VERBATIM from index.html — all five
   declarations, not just the deltas over the base rule above. The full
   mirror is load-bearing: as the last .detail-row rule in the cascade it
   wins every equal-specificity order-tie the inline block used to win.
   Concretely, .portfolio-section-header (~L5542) tries to neutralize the
   1px bottom border, but the inline rule always re-imposed it — the live
   app has ALWAYS shown that divider, so this rule must keep re-imposing
   it. Dropping the "redundant" border-bottom here flips that tie and
   removes the divider (caught by the parity harness). */
:where(#details-sidebar, #expanded-dashboard) .detail-row {
    margin-bottom: 8px;
    padding: 4px 0;
    border-bottom: 1px solid #eee;
    display: flex;
    justify-content: space-between;
}
:where(#details-sidebar, #expanded-dashboard) .detail-row:last-child {
    border-bottom: none;
}

/* Permit cards */
:where(#details-sidebar, #expanded-dashboard) .permit-range-note {
    font-size: 0.75em;
    font-weight: normal;
    color: #888;
    margin-left: 6px;
}
:where(#details-sidebar, #expanded-dashboard) .permit-group-label {
    font-size: 0.75em;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #888;
    margin: 10px 0 4px;
}
:where(#details-sidebar, #expanded-dashboard) .permit-card {
    border: 1px solid #e0e0e0;
    border-radius: 6px;
    margin-bottom: 6px;
    font-size: 0.875em;
    overflow: hidden;
}
:where(#details-sidebar, #expanded-dashboard) .permit-card[open] {
    border-color: #aaa;
}
:where(#details-sidebar, #expanded-dashboard) .permit-summary {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    padding: 7px 10px;
    cursor: pointer;
    list-style: none;
    user-select: none;
}
:where(#details-sidebar, #expanded-dashboard) .permit-summary::-webkit-details-marker { display: none; }
:where(#details-sidebar, #expanded-dashboard) .permit-summary::after {
    content: '›';
    font-size: 1.1em;
    color: #999;
    transition: transform 0.15s;
    flex-shrink: 0;
}
:where(#details-sidebar, #expanded-dashboard) .permit-card[open] .permit-summary::after {
    transform: rotate(90deg);
}
:where(#details-sidebar, #expanded-dashboard) .permit-type {
    font-weight: 600;
    flex-shrink: 0;
}
:where(#details-sidebar, #expanded-dashboard) .permit-summary-meta {
    color: #666;
    font-size: 0.85em;
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Flex-child shrink behaviour. .permit-summary is a flex
       row; without min-width:0 the meta refuses to shrink
       below its content's natural width and the ellipsis
       never triggers — long meta strings push the row past
       the panel edge. */
    flex: 1 1 auto;
    min-width: 0;
}
:where(#details-sidebar, #expanded-dashboard) .permit-desc {
    color: #444;
    line-height: 1.4;
    margin: 0 10px 8px;
}
:where(#details-sidebar, #expanded-dashboard) .permit-desc-list {
    color: #444;
    line-height: 1.5;
    margin: 4px 10px 8px 28px;
    padding: 0;
}
:where(#details-sidebar, #expanded-dashboard) .permit-meta {
    color: #666;
    font-size: 0.82em;
    padding: 0 10px 6px;
}
:where(#details-sidebar, #expanded-dashboard) .permit-id {
    color: #999;
}
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-card {
    border-color: #444;
}
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-card[open] {
    border-color: #666;
}
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-summary-meta,
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-meta,
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-group-label,
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-range-note {
    color: #aaa;
}
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-desc,
body.theme-dark :where(#details-sidebar, #expanded-dashboard) .permit-desc-list {
    color: #ccc;
}

/* .detail-section — the shared section wrapper emitted by the parcel
   renderers (selection-panel.js + bigquery-service.js formatParcelDataHtml,
   so it renders on all three parcel surfaces including app.html's docked
   profile panel). The class previously had NO rule anywhere; every
   instance carried an inline style="margin-bottom:20px;". This rule is
   that inline value, given one home. (--space-5 = 20px, static in both
   themes.) One instance in selection-panel.js pins margin-bottom:0 inline
   to keep its pre-consolidation computed value. */
.detail-section {
    margin-bottom: var(--space-5);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE EVENTS MODEL — parcel panel
   A parcel is a stream of EVENTS; its "property information" is the state
   that stream left behind. Three shapes get three renderings and are never
   flattened into one another: a CHAIN is a card with stations, a SPAN is a
   bar that is a shape rather than a scale, an INSTANT is one row.
   Tokens only; both themes inherit through the vars.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Part 1 — property information (state, with citations) ─────────────── */
.state { margin: 0; }
.state-row {
    display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: var(--space-3);
    padding: 7px 0; border-bottom: 1px solid var(--color-border-muted);
}
.state-row:last-child { border-bottom: 0; }
.state dt {
    font-size: 11px; color: var(--color-fg-muted); text-transform: uppercase;
    letter-spacing: .05em; padding-top: 2px;
}
.state dd { margin: 0; font-size: 13.5px; color: var(--color-fg-primary); min-width: 0; overflow-wrap: anywhere; }
.state dd b { font-weight: 650; }
.state dd .muted { color: var(--color-fg-muted); font-weight: 400; }
.state-cite { display: inline-block; white-space: nowrap; }
a.state-cite__link, .state-cite__link--none {
    font-size: 10.5px; color: var(--color-accent-text); text-decoration: none;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill);
    padding: 0 7px; background: var(--color-bg-muted); vertical-align: 1px;
}
a.state-cite__link:hover { border-color: var(--color-accent); background: var(--color-bg-hover); }
.state-cite__link--none { color: var(--color-fg-muted); }
/* A contradiction is printed next to the line it concerns, never smoothed. */
.state-note {
    margin-top: 3px; font-size: 11.5px; color: var(--color-warning-fg);
    background: var(--color-warning-bg); border: 1px solid var(--color-warning-border);
    border-radius: var(--radius-md); padding: var(--space-1) var(--space-2);
}
.state-note b { font-weight: 650; }
/* Two records contradicting each other is amber. Us reporting how sure we are
   is not — 92,280 Detroit parcels resolve at LOW confidence and an amber flag
   on a quarter of the city would stop meaning anything. */
.state-note--qual {
    color: var(--color-fg-secondary); background: var(--color-bg-subtle);
    border-color: var(--color-border);
}

/* ── The owning party ──────────────────────────────────────────────────── */
/* Ownership is a set. However many members it has, the masthead stays two
   lines: the names, then one line of meta. Everything else — which record
   names whom, and the exact string each one used — is behind a disclosure. */
.owner-amp { color: var(--color-fg-muted); font-weight: 400; padding: 0 1px; }
.owner-meta { margin-top: 2px; font-size: 11.5px; color: var(--color-fg-muted); }
.state dd .ev-disc { margin-top: var(--space-2); }
.owner-party { padding: var(--space-2) 0; border-top: 1px solid var(--color-border-muted); }
.owner-party:first-of-type { border-top: 0; }
.owner-party__name { font-size: 12.5px; font-weight: 650; color: var(--color-fg-primary); }
.owner-party__ev { font-size: 11px; color: var(--color-fg-muted); margin-bottom: 3px; }
.owner-src {
    display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: var(--space-2);
    font-size: 11.5px; padding: 1px 0;
}
.owner-src__k { color: var(--color-fg-muted); }
.owner-src__v { color: var(--color-fg-primary); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }

/* A phone gets the same content; the source rows just stop being a grid. */
@media (max-width: 768px), (max-height: 600px) {
    .owner-src { grid-template-columns: 1fr; gap: 0; }
    .owner-src__k { font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; }
}

.sub-h { font-size: 12px; font-weight: 700; margin: var(--space-5) 0 var(--space-2); color: var(--color-fg-secondary); }

/* A stated fact that replaces a chart we refuse to draw. */
.stated {
    margin: 0 0 var(--space-3); padding: var(--space-3);
    background: var(--color-bg-subtle); border: 1px solid var(--color-border);
    border-left: 3px solid var(--color-fg-muted); border-radius: var(--radius-md);
    font-size: 12.5px; color: var(--color-fg-secondary); max-width: var(--measure-deck);
}
.stated b { color: var(--color-fg-primary); font-weight: 650; }
.stated--warn {
    border-left-color: var(--color-warning-accent); background: var(--color-warning-bg);
    border-color: var(--color-warning-border); color: var(--color-warning-fg);
}
.stated--warn b { color: inherit; }

/* ── Disclosures — the record, kept but demoted ────────────────────────── */
.ev-disc { margin-top: var(--space-3); border-top: 1px solid var(--color-border); }
.ev-disc > summary {
    cursor: pointer; list-style: none; padding: var(--space-2) 0;
    font-size: 11.5px; color: var(--color-fg-secondary); display: flex; align-items: center; gap: 6px;
}
.ev-disc > summary::-webkit-details-marker { display: none; }
.ev-disc > summary .chev {
    color: var(--color-fg-muted); transition: transform var(--motion-fast);
    display: inline-flex; width: 12px; height: 12px;
}
.ev-disc > summary .chev svg { width: 100%; height: 100%; }
.ev-disc[open] > summary .chev { transform: rotate(180deg); }
.ev-disc > summary .n { margin-left: auto; color: var(--color-fg-muted); font-variant-numeric: tabular-nums; font-size: 10.5px; }
.ev-disc__body { padding-bottom: var(--space-3); }

/* A caveat is a four-word tag, never a paragraph. */
.ev-tag {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--color-fg-muted);
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill);
    padding: 0 6px; margin-left: 6px; white-space: nowrap; text-decoration: none;
}
a.ev-tag:hover { border-color: var(--color-accent); color: var(--color-accent-text); }
.legal { font-size: 11px; color: var(--color-fg-secondary); overflow-wrap: anywhere; }
.table-scroll { overflow-x: auto; }
table.rec { width: 100%; border-collapse: collapse; margin-top: var(--space-3); font-size: 12px; }
table.rec caption { text-align: left; font-size: 11.5px; font-weight: 650; color: var(--color-fg-secondary); padding-bottom: var(--space-2); }
table.rec th {
    text-align: right; font-size: 10.5px; font-weight: 600; color: var(--color-fg-muted);
    text-transform: uppercase; letter-spacing: .04em; padding: 0 0 4px;
    border-bottom: 1px solid var(--color-border-strong);
}
table.rec th:first-child, table.rec td:first-child { text-align: left; }
table.rec td { text-align: right; padding: 4px 0 4px var(--space-3); border-bottom: 1px solid var(--color-border-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The label column is the one that may wrap; without this a two-record
   split ledger runs "$296,500$192,229" together in a 348px rail. */
table.rec td:first-child { padding-left: 0; white-space: normal; }
table.rec tr.cur td { font-weight: 650; }

/* ── Part 3 — the event surface ────────────────────────────────────────── */
.viewswitch {
    display: inline-flex; border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill); overflow: hidden; margin-bottom: var(--space-4);
}
.viewswitch button {
    font-family: inherit; font-size: 11.5px; padding: 3px 12px; cursor: pointer;
    background: var(--color-bg-elevated); color: var(--color-fg-secondary); border: 0;
    border-left: 1px solid var(--color-border-strong);
}
.viewswitch button:first-child { border-left: 0; }
.viewswitch button[aria-pressed="true"] { background: var(--color-accent-strong); color: var(--color-on-accent); }

.track { margin-top: var(--space-6); }
.track:first-of-type { margin-top: 0; }
.track-h { font-size: 13px; font-weight: 700; margin: 0; display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.track-n {
    font-size: 10.5px; font-weight: 600; color: var(--color-fg-muted);
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill);
    padding: 0 7px; font-variant-numeric: tabular-nums;
}
.track-deck { margin: 2px 0 0; font-size: 11px; color: var(--color-fg-muted); }
.track-roll { margin: var(--space-2) 0 var(--space-3); font-size: 12.5px; color: var(--color-fg-secondary); max-width: var(--measure-deck); }
/* An empty track says so: WHICH tracks are empty is information. */
.track-roll--none { color: var(--color-fg-muted); font-style: italic; }

.evlist { display: flex; flex-direction: column; gap: var(--space-2); }
.evlist--after { margin-top: var(--space-2); }
.ev {
    background: var(--color-bg-elevated); border: 1px solid var(--color-border);
    border-radius: var(--radius-md); padding: var(--space-3); scroll-margin-top: 96px;
}
.ev:target { border-color: var(--color-accent); box-shadow: 0 0 0 3px rgba(0, 128, 255, .16); }
.ev-when { font-size: 10.5px; color: var(--color-fg-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A span whose start the record does not give says so in the date column,
   and that phrase is wider than a date — it wraps rather than pushing the row. */
.ev--span .ev-when { white-space: normal; font-style: italic; }
.ev-title { font-size: 13px; font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.ev-title .amt, .step-x .amt { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--color-fg-primary); }
.ev-sub { font-size: 11.5px; color: var(--color-fg-muted); margin: 2px 0 0; }
.ev-note { font-size: 11px; color: var(--color-fg-muted); margin: var(--space-2) 0 0; max-width: var(--measure-deck); }
.ev-note--flag {
    font-size: 11.5px; color: var(--color-warning-fg); background: var(--color-warning-bg);
    border: 1px solid var(--color-warning-border); border-radius: var(--radius-md);
    padding: var(--space-1) var(--space-2);
}
.ev-note--flag b { font-weight: 650; }
/* `.ev-basis` was the per-chain "Joined because parties agree." line — ten of
   them on the golden case, each explaining our join rather than the property.
   Removed with the markup; do not reintroduce a per-row provenance line. */
.ev-out { margin: var(--space-2) 0 0; font-size: 12px; color: var(--color-fg-secondary); }
.ev-out b { font-weight: 650; color: var(--color-fg-primary); }

/* A span and an instant are one row; a chain is a card with steps.
   The track tag (merged view only) drops BELOW the content rather than
   taking a third column: "CONDITIONS & ENFORCEMENT" against a 380px rail
   starved the title column badly enough to wrap it one word per line. */
.ev--span, .ev--instant { display: grid; grid-template-columns: 78px minmax(0, 1fr); gap: var(--space-2) var(--space-3); align-items: start; }
.ev--span .ev-when, .ev--instant .ev-when { padding-top: 2px; }
.ev--span > .tracktag, .ev--instant > .tracktag { grid-column: 2; justify-self: start; }
.ev--chain .ev-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.ev--chain .ev-head .ev-title { flex: 1 1 auto; }
.ev--chain.is-foreign { border-style: dashed; opacity: .9; }

.badge {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; white-space: nowrap;
    border-radius: var(--radius-pill); padding: 1px 8px; border: 1px solid;
}
.badge--ok   { color: var(--color-success-fg); background: var(--color-success-bg); border-color: var(--color-success-border); }
.badge--warn { color: var(--color-warning-fg); background: var(--color-warning-bg); border-color: var(--color-warning-border); }
.badge--bad  { color: var(--color-danger-fg);  background: var(--color-danger-bg);  border-color: var(--color-danger-border); }
.tracktag {
    font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; white-space: nowrap;
    color: var(--color-fg-muted); border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-pill); padding: 0 7px; align-self: start;
}

/* The chain: one real-world event, its recordings as stations. A missing
   station is a hollow ring on a dotted rail — the gap IS the information. */
.steps { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.step { position: relative; padding: 0 0 var(--space-2) var(--space-5); }
.step:last-child { padding-bottom: 0; }
.step::before {
    content: ""; position: absolute; left: 5px; top: 11px; bottom: -2px; width: 0;
    border-left: 2px solid var(--color-border-strong);
}
.step:last-child::before { display: none; }
.step.is-off::before { border-left-style: dotted; }
.step-dot {
    position: absolute; left: 0; top: 4px; width: 12px; height: 12px; border-radius: 50%;
    background: var(--accent-indigo); box-shadow: 0 0 0 2px var(--color-bg-elevated);
}
.step.is-off .step-dot { background: transparent; border: 2px dashed var(--color-border-strong); }
.step-body { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.step-k { font-size: 11.5px; font-weight: 650; color: var(--color-fg-secondary); }
.step.is-off .step-k { color: var(--color-fg-muted); font-weight: 500; }
.step-d { font-size: 10.5px; color: var(--color-fg-muted); font-variant-numeric: tabular-nums; }
.step-x { font-size: 11.5px; color: var(--color-fg-primary); flex: 1 1 100%; margin-top: -2px; }
/* One purchase can carry more than one recorded loan. Extra lines under the
   same station, so the chain stays five stations long and no loan is dropped
   to keep it that way. */
.step-x--more { margin-top: 1px; }
.step-gap { color: var(--color-fg-muted); font-style: italic; }

/* A span reads as a bar: where the record is silent, it is hatched. */
.spanbar {
    display: inline-flex; width: 96px; height: 7px; border-radius: var(--radius-pill);
    overflow: hidden; vertical-align: middle; margin: 4px 6px 0 0; background: var(--color-bg-elevated-2);
}
.spanbar b { flex: 1 1 auto; background: var(--color-fg-muted); }
.spanbar i { flex: 0 0 22px; background-image: repeating-linear-gradient(45deg, var(--color-border-strong) 0 3px, transparent 3px 6px); }
.spanbar i:not(.hatch) { flex: 0 0 0; }
.spanbar--ok b  { background: var(--color-success-accent); }
.spanbar--bad b { background: var(--color-border-strong); }
.spanlabel { font-size: 10.5px; }
.spanlabel--ok  { color: var(--color-success-fg); }
.spanlabel--bad { color: var(--color-fg-muted); }
.spanlabel--unk { color: var(--color-fg-muted); font-style: italic; }

/* ── Density ───────────────────────────────────────────────────────────── */
/* The quiet parcel. Four headed tracks saying "nothing on record" reads as
   a broken page; one paragraph naming what we looked for reads as settled. */
.settled-lead { font-size: 14px; color: var(--color-fg-primary); margin: 0 0 var(--space-3); max-width: var(--measure-deck); }
.settled-lead b { font-weight: 650; }
.settled-none {
    margin: var(--space-3) 0 0; padding: var(--space-3);
    background: var(--color-bg-subtle); border: 1px solid var(--color-border);
    border-radius: var(--radius-md); font-size: 12px; color: var(--color-fg-secondary);
    max-width: var(--measure-deck);
}
.settled-none b { color: var(--color-fg-primary); font-weight: 650; }
.settled-none p { margin: 0 0 var(--space-2); }
.settled-none p:last-child { margin-bottom: 0; }

/* A grouped row stands for N records. The count is ON the row, never implied. */
.grp-n {
    font-size: 10.5px; font-weight: 700; color: var(--color-fg-secondary);
    background: var(--color-bg-elevated-2); border-radius: var(--radius-pill);
    padding: 0 7px; margin-left: 6px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ev.is-grp { border-left: 3px solid var(--color-border-strong); }
/* Identical records stack: the bar shows depth so one row cannot be
   mistaken for one record. */
.spanbar--stack {
    box-shadow: 2px 2px 0 -1px var(--color-bg-elevated), 3px 3px 0 -1px var(--color-border-strong),
                5px 5px 0 -1px var(--color-bg-elevated), 6px 6px 0 -1px var(--color-border-strong);
}
.sharebar { display: flex; height: 8px; border-radius: var(--radius-pill); overflow: hidden; margin: 5px 0 3px; background: var(--color-bg-elevated-2); }
.sharebar span { display: block; height: 100%; }
.sharebar .s-cur { background: var(--color-success-accent); }
.sharebar .s-lap { background: var(--color-border-strong); }
.sharebar .s-unk { background-image: repeating-linear-gradient(45deg, var(--color-border-strong) 0 3px, transparent 3px 6px); }
.sharekey { font-size: 10.5px; color: var(--color-fg-muted); }
.sharekey b { color: var(--color-fg-secondary); font-weight: 650; }

/* A repetitive feed becomes a SERIES: a figure and a breakdown, not 400 rows. */
.ev-series {
    background: var(--color-bg-elevated); border: 1px solid var(--color-border);
    border-radius: var(--radius-md); padding: var(--space-3); margin-bottom: var(--space-2);
}
.ev-series--quiet { background: var(--color-bg-subtle); }
.ev-series__h { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.ev-series__title { font-size: 13px; font-weight: 650; }
.ev-series__n {
    font-size: 10.5px; font-weight: 600; color: var(--color-fg-muted); font-variant-numeric: tabular-nums;
    border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill); padding: 0 7px;
}
.ev-series__lead { font-size: 12.5px; color: var(--color-fg-secondary); margin: var(--space-2) 0 0; max-width: var(--measure-deck); }
.ev-series__lead b { color: var(--color-fg-primary); font-weight: 700; }
.ev-series__note { font-size: 11px; color: var(--color-fg-muted); margin: var(--space-2) 0 0; max-width: var(--measure-deck); }
.mixlist { margin: var(--space-3) 0 0; display: flex; flex-direction: column; gap: 3px; }
.mixrow { display: grid; grid-template-columns: minmax(0, 1fr) 68px 34px; align-items: center; gap: var(--space-2); font-size: 11.5px; }
.mixrow .m-k { color: var(--color-fg-secondary); min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mixrow .m-b { height: 7px; background: var(--color-bg-elevated-2); border-radius: var(--radius-pill); overflow: hidden; }
.mixrow .m-b i { display: block; height: 100%; background: var(--accent-indigo); }
.mixrow .m-n { text-align: right; font-variant-numeric: tabular-nums; font-weight: 650; color: var(--color-fg-primary); }
.mixrow.is-lead .m-k { font-weight: 650; color: var(--color-fg-primary); }
/* The leading bucket is marked by weight and by bar length, NOT by a danger
   colour. On a fire feed the biggest bucket is "alarm with no fire", and
   painting that red would re-alarm the exact number this section exists to
   defuse. Rank is position; the reader supplies the sign. */
.mixrow.is-unk .m-k { color: var(--color-fg-muted); font-style: italic; }
/* "We don't know" is hatched wherever it appears — the same language the
   span bars and the share bar use for a record that does not say. A flat
   light fill made the hospital's largest single bucket (158 runs) read as
   an empty track. */
.mixrow.is-unk .m-b i {
    background-color: var(--color-bg-elevated-2);
    background-image: repeating-linear-gradient(45deg, var(--color-fg-muted) 0 3px, transparent 3px 6px);
    opacity: .75;
}

/* Figures — hand-rolled SVG, tokens only. */
.ev-fig { margin: var(--space-3) 0 0; }
.ev-fig svg { display: block; width: 100%; height: auto; }
.ev-fig text { font-family: inherit; font-size: 10.5px; fill: var(--color-fg-muted); }
.ev-fig text.val { font-size: 11px; font-weight: 700; fill: var(--color-fg-primary); font-variant-numeric: tabular-nums; }
.ev-fig .axis { stroke: var(--color-border-strong); }
.ev-fig .refline { stroke: var(--color-fg-muted); stroke-dasharray: 3 3; opacity: .6; }
.ev-fig__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.ev-fig__label { font-size: 12px; font-weight: 600; color: var(--color-fg-secondary); }
.ev-fig__vintage { font-size: 10px; color: var(--color-fg-muted); }
.ev-fig__caption { font-size: 11px; color: var(--color-fg-muted); margin-top: var(--space-2); max-width: var(--measure-deck); }

/* The strip of springs. One mark per year across the whole stretch — the
   information is WHICH years, so every mark is the same height and presence
   is the only variable.

   The grammar is the chain's, reused rather than reinvented: SOLID means the
   record has it, HOLLOW means it does not. So a forfeiture the record shows
   being paid is solid, and a newest forfeiture with no ending on it is drawn
   hollow, exactly like an absent chain station. No good/bad palette — being
   forfeited in 2011 and being forfeited last spring are the same kind of
   fact, and position is what tells them apart. */
.ev-fig--cycle .cyc-on   { fill: var(--accent-indigo); }
.ev-fig--cycle .cyc-open { fill: none; stroke: var(--accent-indigo); stroke-width: 1.2; stroke-dasharray: 2.5 2; }
.ev-fig--cycle .cyc-none { fill: var(--color-border-strong); }

/* ── Period groups ──────────────────────────────────────────────────────────
   A stream long enough to scroll folds into periods that COLLAPSE, and the
   summary is the contract: span, count and mix of kinds, so a folded group
   never hides the existence of history. Only the newest opens. The header is
   a <summary>, so it is keyboard- and screen-reader-native and survives the
   citation jump (openEnclosingFolds opens every enclosing <details>). */
.periodgrp { margin-top: var(--space-4); }
.periodgrp:first-of-type { margin-top: 0; }
.period-h {
    display: flex; align-items: baseline; gap: var(--space-2); cursor: pointer;
    list-style: none; border-bottom: 2px solid var(--color-fg-primary);
    padding-bottom: 2px; margin-bottom: var(--space-1);
}
.period-h::-webkit-details-marker { display: none; }
.period-h:hover .period-y { color: var(--accent-indigo); }
.period-h:focus-visible { outline: 2px solid var(--accent-indigo); outline-offset: 2px; }
.period-h .chev {
    width: 12px; height: 12px; flex: 0 0 auto; color: var(--color-fg-muted);
    transition: transform var(--motion-fast) ease; align-self: center;
}
.period-h .chev svg { width: 100%; height: 100%; }
.periodgrp[open] > .period-h .chev { transform: rotate(180deg); }
.period-head { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-width: 0; }
.period-line { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
.period-y { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.period-sub { font-size: 11px; color: var(--color-fg-muted); font-variant-numeric: tabular-nums; }
.period-n { font-size: 10.5px; color: var(--color-fg-muted); font-variant-numeric: tabular-nums; white-space: nowrap; margin-left: auto; }
/* The mix rides in the <summary>, so a folded group still names what it holds. */
.period-mix { font-size: 11px; color: var(--color-fg-muted); margin: 0; max-width: var(--measure-deck); font-weight: 400; }
.periodgrp__body { padding-bottom: var(--space-2); }

/* Merged view at scale: by period, and within a period by track, so no single
   busy feed can crowd every other track out of the page. */
.yearblock { margin-top: var(--space-5); }
.yearblock:first-of-type { margin-top: 0; }
.year-h {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2);
    border-bottom: 2px solid var(--color-fg-primary); padding-bottom: 2px; margin-bottom: var(--space-1);
}
.year-y { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.year-n { font-size: 10.5px; color: var(--color-fg-muted); font-variant-numeric: tabular-nums; }
.year-mix { font-size: 11px; color: var(--color-fg-muted); margin: 0 0 var(--space-2); max-width: var(--measure-deck); }
.dens-note { font-size: 11px; color: var(--color-fg-muted); margin: var(--space-2) 0; max-width: var(--measure-deck); }
.dens-note b { color: var(--color-fg-secondary); font-weight: 650; }
/* State the cap ONCE, above the tracks; each affected heading tags back. */
.capnote {
    display: flex; gap: var(--space-2); align-items: flex-start;
    margin: var(--space-2) 0; padding: var(--space-2) var(--space-3);
    background: var(--color-bg-subtle); border: 1px solid var(--color-border-strong);
    border-left: 3px solid var(--color-fg-muted);
    border-radius: var(--radius-md); font-size: 11.5px; color: var(--color-fg-secondary);
    max-width: var(--measure-deck); scroll-margin-top: 96px;
}
.capnote svg { width: 14px; height: 14px; flex: 0 0 auto; margin-top: 2px; color: var(--color-fg-muted); }
.capnote b { font-weight: 650; color: var(--color-fg-primary); }

/* ── Part 2 — surroundings (proximity) ─────────────────────────────────── */
.prox-verdict {
    margin: 0 0 var(--space-4); padding: var(--space-3);
    background: var(--color-bg-subtle); border: 1px solid var(--color-border);
    border-left: 3px solid var(--accent-indigo); border-radius: var(--radius-md);
    max-width: var(--measure-deck);
}
.prox-band { font-size: 15px; font-weight: 650; color: var(--color-fg-primary); line-height: 1.3; }
.prox-arith { font-size: 12.5px; color: var(--color-fg-secondary); margin-top: var(--space-1); }
.prox-extremes { font-size: 11.5px; color: var(--color-fg-muted); margin-top: var(--space-2); }
.prox-extremes b { color: var(--color-fg-secondary); font-weight: 650; }
.prox-group { margin-top: var(--space-4); }
.prox-grid > .prox-group:first-child { margin-top: 0; }
.prox-gh {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2);
    padding-bottom: 3px; border-bottom: 1px solid var(--color-border-strong); margin-bottom: var(--space-2);
}
.prox-gname { font-size: 12px; font-weight: 650; color: var(--color-fg-secondary); }
.prox-gcount { font-size: 10.5px; color: var(--color-fg-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.prox-row {
    --cmp-surface: var(--color-bg-elevated);
    display: grid; grid-template-columns: minmax(0, 1fr) 104px minmax(46px, auto);
    align-items: center; gap: var(--space-2); padding: 3px 0;
}
.prox-name { font-size: 12px; color: var(--color-fg-primary); min-width: 0; overflow-wrap: anywhere; }
.prox-val { font-size: 12px; font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; color: var(--color-fg-primary); white-space: nowrap; }
.prox-val small { font-weight: 400; font-size: 10px; color: var(--color-fg-muted); margin-left: 2px; }
.prox-sub { grid-column: 1 / -1; font-size: 10.5px; color: var(--color-fg-muted); margin: -3px 0 3px; overflow-wrap: anywhere; }
/* The centre line is the citywide median for THIS category — a ratio axis,
   because three minutes to a park and three minutes to an ER are not the
   same fact and a shared minute axis would be the vendor dump in chart form. */
.prox-track {
    position: relative; height: 15px;
    background: linear-gradient(to bottom,
        transparent calc(50% - .5px), var(--color-border-strong) calc(50% - .5px),
        var(--color-border-strong) calc(50% + .5px), transparent calc(50% + .5px));
}
/* No median to compare against → no axis. Drawing an empty track would
   imply a comparison the row cannot make. */
.prox-track--bare { background: none; }
.prox-med { position: absolute; top: 1px; bottom: 1px; left: 50%; width: 0; border-left: 1px solid var(--color-fg-muted); opacity: .75; }
/* A median over a fifth of the city is not a citywide median. */
.prox-med--partial { border-left-style: dashed; }
/* No colour semantics on the dot, deliberately: being near a park and being
   near a check casher are not the same kind of near, and the panel does not
   get to decide which is an amenity. Position carries the comparison. */
.prox-dot {
    position: absolute; top: 50%; width: 10px; height: 10px; margin-left: -5px;
    border-radius: 50%; transform: translateY(-50%); background: var(--accent-indigo);
    box-shadow: 0 0 0 2px var(--cmp-surface), 0 0 0 3.5px rgba(92, 107, 192, .32);
}
/* An island is the routing graph failing to connect, not a long journey. */
.prox-island { position: absolute; inset: 1px 0; background-image: repeating-linear-gradient(45deg, var(--color-border-strong) 0 3px, transparent 3px 6px); }
.prox-key {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px var(--space-3);
    font-size: 10.5px; color: var(--color-fg-muted); margin-bottom: var(--space-3);
    padding-bottom: var(--space-2); border-bottom: 1px solid var(--color-border);
}
.prox-key .k-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent-indigo); display: inline-block; margin-right: 2px; }
.prox-key .k-med { width: 0; height: 11px; border-left: 1px solid var(--color-fg-muted); display: inline-block; margin-right: 4px; }
.prox-key .k-dir { margin-left: auto; letter-spacing: .03em; }
/* `.prox-foot` / `.prox-note` are gone with the four footnotes under the
   proximity grid — the log-scale legend, the partial-coverage note, the
   island note and "a drive time is a time only for a household with a car".
   Each row states its own qualification; the grid needs no paragraph. */
.prox-tags { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: var(--space-2); }
.prox-tags .ev-tag { margin-left: 0; }

/* Sub-chips in the topic strip are the tracks inside Events. */
.tchip--sub { font-size: 10.5px; opacity: .85; }
.tchip--sub b { font-weight: 500; }

/* ── Dashboard: same model, roomier ────────────────────────────────────── */
@media (min-width: 769px) and (min-height: 601px) {
    /* The chain runs ACROSS at this width — the stations read as a process. */
    .dash-parcel-shell .steps { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0; margin-top: var(--space-4); }
    .dash-parcel-shell .step { padding: var(--space-5) var(--space-3) 0 0; }
    .dash-parcel-shell .step::before { left: 12px; right: 0; top: 5px; bottom: auto; width: auto; height: 0; border-left: 0; border-top: 2px solid var(--color-border-strong); }
    .dash-parcel-shell .step:last-child::before { display: none; }
    .dash-parcel-shell .step.is-off::before { border-top-style: dotted; border-left: 0; }
    .dash-parcel-shell .step-dot { top: 0; left: 0; }
    .dash-parcel-shell .step-body { display: block; }
    .dash-parcel-shell .step-k, .dash-parcel-shell .step-d, .dash-parcel-shell .step-x { display: block; }
    .dash-parcel-shell .step-x { margin-top: 1px; }
    /* At dashboard width there IS room for the tag beside the content. */
    .dash-parcel-shell .ev--span, .dash-parcel-shell .ev--instant { grid-template-columns: 110px minmax(0, 1fr) auto; }
    .dash-parcel-shell .ev--span > .tracktag, .dash-parcel-shell .ev--instant > .tracktag { grid-column: 3; justify-self: end; }
    .dash-parcel-shell .state-row { grid-template-columns: 120px minmax(0, 1fr); }
    /* Two columns for the proximity strip; a group never splits across
       them, so a heading can never be orphaned from its rows. */
    .dash-parcel-shell .prox-grid { column-count: 2; column-gap: var(--space-8); }
    .dash-parcel-shell .prox-group { break-inside: avoid; -webkit-column-break-inside: avoid; margin-top: 0; margin-bottom: var(--space-5); }
    .dash-parcel-shell .prox-verdict { max-width: none; }
}

/* Phone: narrow OR short (a landscape phone is a phone). */
@media (max-width: 768px), (max-height: 600px) {
    .ev--span, .ev--instant { grid-template-columns: 64px minmax(0, 1fr); }
    .state-row { grid-template-columns: 84px minmax(0, 1fr); gap: var(--space-2); }
    .prox-row { grid-template-columns: minmax(0, 1fr) 76px minmax(40px, auto); }
    .mixrow { grid-template-columns: minmax(0, 1fr) 48px 30px; }
}

/* ============================================================
   TOUCH PLATFORM
   One owner for the rules that only exist because the visitor is
   holding the site rather than pointing at it. Three separate
   problems, three separate queries — deliberately not merged,
   because they key off different things:

     - iOS zoom is a PHONE-CLASS problem (the viewport it zooms is
       the phone's), so it uses the CLAUDE.md breakpoint pair.
     - Hit area is a POINTER problem. A touch laptop has a fat
       finger at 1440px, so it keys off `pointer: coarse`.
     - Hover-only reveals are a HOVER problem. `hover: none` is the
       only honest test for "this affordance will never appear".

   Verified at 390x844 and 844x390 (housing-drafts/mobile-audit-2026-08-09.md).
   ============================================================ */

/* ── 1. iOS zooms the page on focus, and never zooms back ──────────────
   Safari enlarges the visual viewport when a focused text field computes
   under 16px, and leaves it there when the field blurs — so one tap on a
   search box costs the visitor a pinch. Every text field on the site was
   under 16px: the landing search at 15, /ownership's owner search at 15,
   /housing's neighborhood filter at 12.5, and the filter panel's number
   and date inputs at the 13.33px UA default because nothing set them.
   16px is not a type choice here; it is the threshold. `<select>` is
   deliberately absent — modern iOS opens a picker for it and does not
   zoom, and forcing 16px there would widen every compact popover. */
@media (max-width: 768px), (max-height: 600px) {
    input[type="text"],
    input[type="search"],
    input[type="number"],
    input[type="date"],
    input[type="email"],
    input[type="tel"],
    input[type="url"],
    input[type="password"],
    input:not([type]),
    textarea {
        font-size: 16px;
    }
}

/* ── 2. A thumb is 44px; these controls were 16 to 36 ──────────────────
   The painted size stays exactly as designed — the pseudo-element paints
   nothing and only enlarges the hit box, centred on the control. That
   matters: several of these are deliberately quiet chrome (the vintage
   pill over a photo, the × on a companion tab), and making them visually
   bigger would be a worse trade than making them easier to hit.

   `::before` throughout, because `.icon-rail-button.has-active::after`
   already carries the active dot. `.time-pill` is the one exception —
   its `::before` is the bullet glyph — so it takes `::after` below.

   Every selector here is a control the map or a topic page cannot be
   used without: dismissing the sheet that covers 40% of the map,
   getting past the landing overlay, getting search back, the rail. */
@media (pointer: coarse) {
    #panel-close::before,
    #panel-report-bug::before,
    .panel-close-btn::before,
    .icon-rail-button::before,
    .search-icon-button::before,
    .account-chip-trigger::before,
    .al-close::before,
    .al-go::before,
    .tab-close::before,
    .gallery-pill::before,
    .time-pill::after,
    .bchip::before,
    .metric-chip::before,
    .maplibregl-ctrl-zoom-in::before,
    .maplibregl-ctrl-zoom-out::before,
    .maplibregl-ctrl-attrib-button::before {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: max(100%, 44px);
        height: max(100%, 44px);
        transform: translate(-50%, -50%);
        /* Hit area only — no paint, and no border-radius, so the target is
           the full 44px rectangle rather than a rounded subset of it. A tap
           on a pseudo-element IS a tap on its host, so nothing needs wiring
           in JS; leave pointer-events alone (it inherits `auto` from the
           button) or the halo stops collecting anything. */
        background: transparent;
    }
    /* The pseudo-element resolves against the nearest positioned
       ancestor, so each host has to be one. #panel-close is already
       absolute and .icon-rail-button already relative; the rest are
       static inline-flex buttons with no absolutely-positioned children,
       so relative is a no-op beyond providing the containing block. */
    #panel-report-bug,
    .panel-close-btn,
    .search-icon-button,
    .account-chip-trigger,
    .al-close,
    .al-go,
    .tab-close,
    .gallery-pill,
    .time-pill,
    .bchip,
    .metric-chip,
    .maplibregl-ctrl-zoom-in,
    .maplibregl-ctrl-zoom-out,
    .maplibregl-ctrl-attrib-button {
        position: relative;
    }
    /* The × on a companion tab is 16x14 of glyph at the right-hand end of a
       141x24 tab, and it is the one control that cannot take a full 44px
       halo: centred on the ×, a 44-wide target reaches back across the
       label and turns "select this boundary" into "remove it". So the TAB
       grows to 44 tall and the × takes a 32x40 target inside it — four
       times the area, still clear of the label. */
    .sidebar-tab { min-height: 44px; }
    .tab-close::before { width: 32px; height: 40px; }
}

/* ── 3. An affordance that only exists on hover does not exist ─────────
   Three controls rested at `opacity: 0` and were revealed by `:hover`
   alone. On a phone the row-jump link — a real navigation path off an
   actor page — simply was not there. */
@media (hover: none) {
    .ownership-row-jump,
    .sources-entry__anchor,
    .viz-mline-pt {
        opacity: 1;
    }
}
