/* Design tokens (colors, .glass, scrollbar) now live in theme.css, shared
   with one-pager.html and dashboard.html. This file keeps only index.html's
   own layout/component rules. */

/* Reset & Global Styles */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: var(--font-body);
    background-color: var(--bg-main);
    color: var(--text-primary);
    overflow: hidden;
    height: 100vh;
    height: 100dvh; /* tracks Safari's collapsing address bar/toolbar instead of the largest-possible viewport */
    width: 100vw;
    -webkit-font-smoothing: antialiased;
    /* Buttons/tabs/the sheet handle all have their own tap feedback (color,
       transform) -- the browser's default gray flash on top of that reads
       as janky rather than responsive. */
    -webkit-tap-highlight-color: transparent;
}

/* Layout Container */
.app-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    width: 100vw;
    position: relative;
    background: radial-gradient(circle at top right, rgba(16, 185, 129, 0.05), transparent 40%),
                radial-gradient(circle at bottom left, rgba(59, 130, 246, 0.05), transparent 40%);
}

/* Header */
.app-header {
    height: 64px;
    background-color: rgba(7, 9, 14, 0.8);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--border-color);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 24px;
    z-index: 1000;
}

.logo-area {
    display: flex;
    align-items: center;
    gap: 12px;
}

.logo-icon {
    color: var(--color-emerald);
    width: 28px;
    height: 28px;
    filter: drop-shadow(0 0 8px var(--color-emerald-glow));
}

.logo-text h1 {
    font-size: 20px;
    font-weight: 700;
    line-height: 1.1;
    background: linear-gradient(135deg, #ffffff 30%, var(--text-secondary) 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.logo-subtitle {
    font-size: 11px;
    color: var(--text-muted);
    font-weight: 500;
}

/* Header actions */
.header-actions {
    display: flex;
    align-items: center;
    gap: 16px;
}

.one-pager-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    text-decoration: none;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    background: var(--color-emerald-glow);
    border: 1px solid var(--color-emerald);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-4);
    transition: background var(--dur-fast) var(--ease-standard);
    white-space: nowrap;
}

.one-pager-link:hover {
    background: rgba(16, 185, 129, 0.25);
}

.one-pager-link i {
    width: 16px;
    height: 16px;
    color: var(--color-emerald);
}

.one-pager-link.dashboard-link {
    background: var(--color-blue-glow);
    border-color: var(--color-blue);
}

.one-pager-link.dashboard-link:hover {
    background: rgba(59, 130, 246, 0.25);
}

.one-pager-link.dashboard-link i {
    color: var(--color-blue);
}

/* Select Picker Styling */
.neighborhood-picker-container {
    display: flex;
    align-items: center;
    gap: 12px;
}

.picker-label {
    font-size: 13px;
    color: var(--text-secondary);
    font-weight: 500;
}

.select-wrapper {
    position: relative;
    display: flex;
    align-items: center;
}

#neighborhood-select {
    appearance: none;
    -webkit-appearance: none;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    padding: 8px 36px 8px 16px;
    font-size: 13px;
    font-weight: 500;
    border-radius: 8px;
    cursor: pointer;
    font-family: var(--font-body);
    transition: var(--transition-interactive);
    min-width: 180px;
}

#neighborhood-select:focus, #neighborhood-select:hover {
    outline: none;
    border-color: var(--color-emerald);
    box-shadow: 0 0 0 2px var(--color-emerald-glow);
}

.select-arrow {
    position: absolute;
    right: 12px;
    pointer-events: none;
    color: var(--text-secondary);
    width: 16px;
    height: 16px;
}

/* Main Panels Layout — a floating "workspace": the two side panels and the
   map are separate rounded glass surfaces with a consistent gutter between
   them, floating on the app background, rather than three columns fused by
   hairline dividers. The panels never overlap the map, so no backdrop-blur
   ever has to re-sample a panning map (that would jank map interaction). */
.app-main {
    flex: 1;
    display: grid;
    grid-template-columns: 380px 1fr 380px;
    gap: var(--space-4);
    padding: var(--space-4);
    overflow: hidden;
    height: calc(100vh - 64px);
    transition: grid-template-columns 280ms var(--ease-standard);
}

.app-main.left-collapsed {
    grid-template-columns: 44px 1fr 380px;
}

.app-main.right-collapsed {
    grid-template-columns: 380px 1fr 44px;
}

.app-main.left-collapsed.right-collapsed {
    grid-template-columns: 44px 1fr 44px;
}

.panel {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    background-color: var(--bg-panel);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-xl);
    position: relative;
}

/* Explicit column AND row placement decouples visual position from DOM
   order -- panel-map is first in the markup (so it can fill the whole
   viewport behind the mobile sheet), but still needs to render as the
   middle column on desktop. Column alone isn't enough: panel-map (column 2)
   comes before panel-left (column 1) in source order, and CSS Grid's sparse
   auto-placement cursor never moves backward, so without an explicit row,
   panel-left gets pushed into a second implicit row while panel-map is left
   alone in the first -- which collapses to 0 height since nothing else
   shares it, leaving the map invisible despite otherwise working normally. */
.panel-left {
    grid-column: 1;
    grid-row: 1;
    padding: var(--space-5);
    padding-top: 0; /* the sticky search header supplies its own top padding */
    z-index: 10;
}

.panel-right {
    grid-column: 3;
    grid-row: 1;
    padding: var(--space-5);
    padding-top: 0; /* the sticky insights header supplies its own top padding */
    z-index: 10;
    /* Lets .metric-cards-grid query this panel's actual rendered width
       (fixed 400px sidebar on desktop, a wider bottom sheet on mobile)
       instead of the viewport's. */
    container-type: inline-size;
}

.panel-map {
    grid-column: 2;
    grid-row: 1;
    padding: 0;
    position: relative;
    overflow: hidden; /* rounds the map tiles to the panel's corners */
    background-color: var(--bg-main);
    /* The map is opaque — blurring what's behind it is pure wasted work. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Wrapper around the mobile-only tabs + Property/Insights panels. Inert on
   desktop/tablet -- its children lay out as if it weren't there -- and only
   becomes a real, positioned bottom sheet inside the mobile media query. */
.mobile-sheet {
    display: contents;
}

/* ---- Panel collapse / expand ------------------------------------------ */

/* Clip overflowing content when panel is narrowed to 44px */
.panel-left.collapsed,
.panel-right.collapsed {
    overflow: hidden;
}

/* The collapsed strip covers the entire panel when collapsed.
   Invisible (opacity 0, pointer-events none) in normal state. */
.panel-collapsed-strip {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    opacity: 0;
    pointer-events: none;
    z-index: 200;
    transition: opacity 200ms var(--ease-standard);
    background-color: var(--bg-panel);
    border-radius: inherit;
}

.panel-left.collapsed .panel-collapsed-strip,
.panel-right.collapsed .panel-collapsed-strip {
    opacity: 1;
    pointer-events: auto;
}

.panel-collapsed-strip .strip-expand-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--bg-card);
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--transition-interactive);
}

.panel-collapsed-strip .strip-expand-btn:hover {
    color: var(--color-emerald);
    border-color: var(--color-emerald);
    background: var(--color-emerald-glow);
}

.panel-collapsed-strip .strip-expand-btn svg {
    width: 15px;
    height: 15px;
}

.panel-collapsed-label {
    writing-mode: vertical-rl;
    text-orientation: mixed;
    transform: rotate(180deg);
    font-size: 10px;
    font-weight: 700;
    color: var(--text-muted);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* ---- Panel collapse toggle button (inside each panel header) ----------- */

.panel-collapse-toggle {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
    transition: var(--transition-interactive);
}

.panel-collapse-toggle:hover {
    color: var(--text-primary);
    border-color: var(--border-hover);
    background: var(--bg-card);
}

.panel-collapse-toggle svg {
    width: 14px;
    height: 14px;
}

/* Row wrappers inside panel headers that hold the title + collapse btn */
.search-section-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.search-section-row .search-bar-container {
    flex: 1;
    min-width: 0;
}

.panel-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: 2px;
}

/* Right panel's fixed header: stays pinned while the insights scroll under
   it, with its own frosted fill so content sliding beneath stays readable.
   Bleeds to the panel edges (negative margins) so nothing peeks out beside
   it in the panel's padding gutters. */
.panel-header-sticky {
    position: sticky;
    top: 0;
    z-index: 50;
    margin: 0 calc(-1 * var(--space-5)) var(--space-4);
    padding: var(--space-4) var(--space-5) var(--space-3);
    background-color: rgba(10, 14, 22, 0.82);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border-color);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}

.panel-title {
    font-size: 18px;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 8px;
}

.panel-icon {
    color: var(--color-blue);
    width: 20px;
    height: 20px;
}

.panel-disclaimer {
    font-size: 11px;
    line-height: 1.5;
    color: var(--text-muted);
    margin-top: var(--space-2);
}

.hover-lift:hover {
    transform: translateY(-2px);
    background-color: var(--bg-card-hover);
    border-color: var(--border-hover);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}

/* Search Bar styling — pinned atop the left panel the same way the right
   panel's header is, so the primary action never scrolls away. */
.search-section {
    position: sticky;
    top: 0;
    margin: 0 calc(-1 * var(--space-5)) var(--space-4);
    padding: var(--space-4) var(--space-5) var(--space-3);
    background-color: rgba(10, 14, 22, 0.82);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border-color);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    z-index: 100;
}

.search-bar-container {
    position: relative;
    display: flex;
    align-items: center;
}

.search-icon {
    position: absolute;
    left: var(--space-4);
    color: var(--text-secondary);
    width: 18px;
    height: 18px;
}

#search-input {
    width: 100%;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    padding: var(--space-3) var(--space-7) var(--space-3) var(--space-8);
    font-size: 14px;
    border-radius: var(--radius-md);
    outline: none;
    font-family: var(--font-body);
    transition: var(--transition-interactive);
}

#search-input:focus {
    border-color: var(--color-emerald);
    box-shadow: 0 0 12px rgba(16, 185, 129, 0.2);
    background-color: rgba(22, 32, 54, 0.9);
}

.clear-btn {
    position: absolute;
    right: 12px;
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 4px;
}

.clear-btn:hover {
    color: var(--text-primary);
}

.clear-btn svg {
    width: 16px;
    height: 16px;
}

/* Autocomplete Suggestions */
.search-suggestions-dropdown {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    right: 0;
    background-color: rgba(16, 23, 38, 0.95);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid var(--border-hover);
    border-radius: var(--radius-md);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
    max-height: 250px;
    overflow-y: auto;
    z-index: 999;
}

.suggestion-item {
    padding: var(--space-2) var(--space-4);
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 2px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
    transition: var(--transition-interactive);
}

.suggestion-item:last-child {
    border-bottom: none;
}

.suggestion-item:hover {
    background-color: var(--color-emerald-glow);
}

.suggestion-address {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
}

.suggestion-details {
    font-size: 11px;
    color: var(--text-secondary);
}

/* Empty State */
.empty-state {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--space-5);
}

.empty-state-content {
    max-width: 280px;
}

.empty-icon {
    width: 48px;
    height: 48px;
    color: var(--text-muted);
    margin-bottom: 16px;
}

.pulse {
    animation: markerPulse 2s infinite ease-in-out;
}

.empty-state-content h3 {
    font-size: 16px;
    margin-bottom: 8px;
    color: var(--text-primary);
}

.empty-state-content p {
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.5;
    margin-bottom: var(--space-4);
}

.suggestion-chips {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
}

.chip {
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    padding: var(--space-2) var(--space-3);
    border-radius: 20px;
    font-size: 11px;
    font-weight: 500;
    cursor: pointer;
    transition: var(--transition-interactive);
}

.chip:hover {
    border-color: var(--color-emerald);
    background-color: var(--color-emerald-glow);
}

/* Property Report Card */
.property-card-container {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    animation: slideIn var(--dur-slow) var(--ease-out);
}

.property-card-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
}

.address-title h2 {
    font-size: 20px;
    font-weight: 700;
}

.address-title span {
    font-size: 13px;
    color: var(--text-secondary);
}

.badge {
    padding: var(--space-1) var(--space-2);
    border-radius: 6px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
}

.badge-primary {
    background-color: var(--color-blue-glow);
    color: var(--color-blue);
    border: 1px solid rgba(59, 130, 246, 0.3);
}

/* Quick Specs Grid */
.property-specs-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-2);
}

.spec-item {
    background-color: rgba(255, 255, 255, 0.02);
    border: 1px solid var(--border-color);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-lg);
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.spec-item svg {
    color: var(--text-secondary);
    width: 20px;
    height: 20px;
}

.spec-info {
    display: flex;
    flex-direction: column;
}

.spec-label {
    font-size: 11px;
    color: var(--text-muted);
}

.spec-value {
    font-size: 13px;
    font-weight: 600;
}

/* Valuation Section */
.valuation-section {
    background-color: rgba(255, 255, 255, 0.02);
    border: 1px solid var(--border-color);
    padding: var(--space-4);
    border-radius: var(--radius-lg);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.section-title-wrapper {
    display: flex;
    align-items: center;
    gap: 8px;
}

.section-title-wrapper h3 {
    font-size: 14px;
    font-weight: 600;
}

.section-icon {
    width: 18px;
    height: 18px;
}

.section-icon.emerald {
    color: var(--color-emerald);
}

.valuation-row {
    display: flex;
    justify-content: space-between;
}

.val-metric {
    display: flex;
    flex-direction: column;
}

.val-label {
    font-size: 11px;
    color: var(--text-secondary);
}

.val-value {
    font-size: 20px;
    font-weight: 700;
}

.val-value.emerald {
    color: var(--color-emerald);
}

.valuation-bar-container {
    height: 8px;
    border-radius: 4px;
    overflow: hidden;
    background-color: rgba(255, 255, 255, 0.08);
    display: flex;
}

.val-bar.land {
    background-color: var(--color-blue);
}

.val-bar.building {
    background-color: var(--color-emerald);
}

.valuation-legend {
    display: flex;
    gap: 16px;
    font-size: 11px;
    color: var(--text-secondary);
}

.legend-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: var(--space-1);
}

.legend-dot.land {
    background-color: var(--color-blue);
}

.legend-dot.building {
    background-color: var(--color-emerald);
}

/* Owner & Parcel Details */
.owner-section {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 0 4px;
}

/* Address Risk & Vulnerability panel (Milestone 1). The header doubles as
   the accordion toggle for the technical stat grid; hazard flags stay
   outside the accordion so warnings are never hidden. */
.health-risk-section {
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-card);
    padding: var(--space-4);
}

.health-risk-header {
    display: flex;
    align-items: center;
    width: 100%;
    gap: var(--space-2);
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
    text-align: left;
}

.health-risk-header span {
    flex: 1;
}

.health-risk-header i,
.health-risk-header svg {
    width: 16px;
    height: 16px;
    color: var(--color-rose);
    flex-shrink: 0;
}

.health-risk-header .disclosure-chevron {
    color: var(--text-secondary);
}

.health-risk-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-2);
    padding-top: var(--space-3);
}

.health-risk-card {
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    text-align: center;
    align-items: center;
}

.health-risk-label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.health-risk-value {
    font-size: 20px;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1.2;
}

.health-risk-sub {
    font-size: 10px;
    color: var(--text-secondary);
}

.hazard-flags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-3);
}

.hazard-flags:empty {
    display: none;
}

.hazard-flag {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: 11px;
    font-weight: 500;
    color: var(--color-rose);
    background: var(--color-rose-glow);
    border: 1px solid rgba(239, 68, 68, 0.25);
    border-radius: var(--radius-sm);
    padding: var(--space-1) var(--space-2);
}

.hazard-flag i {
    width: 12px;
    height: 12px;
}

.info-row {
    display: flex;
    justify-content: space-between;
    font-size: 13px;
}

.info-label {
    color: var(--text-secondary);
}

.info-value {
    font-weight: 500;
    text-align: right;
}

/* Clickable owner name -> portfolio mapping */
.owner-clickable {
    cursor: pointer;
    color: var(--color-emerald);
    text-decoration: underline;
    text-decoration-style: dotted;
    text-underline-offset: 3px;
    transition: var(--transition-interactive);
}

.owner-clickable:hover {
    color: var(--text-primary);
    text-decoration-style: solid;
}

/* Owner portfolio summary card (map overlay) */
.owner-portfolio-card {
    position: absolute;
    top: var(--space-4);
    left: 64px; /* clear the Leaflet zoom controls */
    z-index: 800;
    /* Leave room for the zoom controls (left) and Map Visuals card (right). */
    max-width: min(440px, calc(100% - 360px));
    padding: var(--space-4);
    border-radius: var(--radius-lg);
    border: 1px solid var(--color-teal);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

.owner-portfolio-close {
    position: absolute;
    top: 8px;
    right: 8px;
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 2px;
    line-height: 0;
}

.owner-portfolio-close:hover { color: var(--text-primary); }
.owner-portfolio-close i { width: 16px; height: 16px; }

.owner-portfolio-label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-teal);
    font-weight: 700;
}

.owner-portfolio-name {
    font-size: 15px;
    font-weight: 700;
    color: var(--text-primary);
    margin: 2px 0 var(--space-2);
    padding-right: var(--space-4);
    line-height: 1.25;
}

/* Collapsible on mobile only (see the drag handle below); desktop's JS
   leaves max-height at "none" so this never clips there. */
.owner-portfolio-body {
    overflow: hidden;
    transition: max-height var(--dur-slow) var(--ease-sheet);
}

.owner-portfolio-body.dragging {
    transition: none;
}

.owner-portfolio-handle {
    display: none;
}

.owner-portfolio-stats {
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
}

.owner-portfolio-stat {
    display: flex;
    flex-direction: column;
}

.owner-portfolio-num {
    font-size: 20px;
    font-weight: 800;
    color: var(--color-teal);
    line-height: 1.1;
}

.owner-portfolio-unit {
    font-size: 10px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.owner-portfolio-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-top: var(--space-3);
    padding-top: var(--space-2);
    border-top: 1px solid var(--border-color);
}

.owner-portfolio-legend .legend-key {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 11px;
    color: var(--text-secondary);
}

.owner-portfolio-legend .legend-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 1px solid white;
}

.owner-portfolio-legend .legend-dot.rose {
    background-color: var(--color-rose);
    box-shadow: 0 0 5px var(--color-rose-glow);
}

.owner-portfolio-legend .legend-dot.teal {
    background-color: var(--color-teal);
    box-shadow: 0 0 5px var(--color-teal-glow);
}

/* Timeline Tabs */
.timeline-tabs {
    display: flex;
    border-bottom: 1px solid var(--border-color);
    margin-top: var(--space-2);
}

.tab-btn {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--text-secondary);
    padding: var(--space-2) var(--space-1);
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    transition: var(--transition-interactive);
}

.tab-btn:hover {
    color: var(--text-primary);
}

.tab-btn.active {
    color: var(--color-emerald);
    border-bottom-color: var(--color-emerald);
}

.tab-btn svg {
    width: 14px;
    height: 14px;
}

/* Timeline Lists */
.timeline-container {
    display: none;
    max-height: 350px;
    overflow-y: auto;
    padding-top: var(--space-4);
}

.timeline-container.active {
    display: block;
}

.timeline-list {
    position: relative;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.timeline-list::before {
    content: '';
    position: absolute;
    left: 4px;
    top: 6px;
    bottom: 6px;
    width: 2px;
    background-color: var(--border-color);
}

.timeline-item {
    position: relative;
}

.timeline-marker {
    position: absolute;
    left: -20px;
    top: 4px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid var(--bg-panel);
    background-color: var(--text-muted);
}

.timeline-marker.active-warning {
    background-color: var(--color-rose);
    box-shadow: 0 0 8px var(--color-rose-glow);
}

.timeline-marker.active-complied {
    background-color: var(--color-emerald);
}

.timeline-marker.permit-item {
    background-color: var(--cat-permit);
    box-shadow: 0 0 8px var(--color-blue-glow);
}

.timeline-content {
    background-color: rgba(255, 255, 255, 0.015);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.timeline-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.timeline-tag {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
}

.timeline-tag.rose {
    color: var(--color-rose);
}

.timeline-tag.emerald {
    color: var(--color-emerald);
}

.timeline-tag.amber {
    color: var(--color-amber);
}

.timeline-tag.blue {
    color: var(--cat-permit);
}

.timeline-date {
    font-size: 11px;
    color: var(--text-muted);
}

.timeline-body {
    font-size: 12px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--text-primary);
}

.timeline-footer {
    font-size: 11px;
    color: var(--text-secondary);
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
}

/* Map Section */
#map {
    width: 100%;
    height: 100%;
    background-color: var(--bg-main);
}

/* Overriding Leaflet Map styles for Premium dark UI */
.leaflet-container {
    background: var(--bg-main) !important;
}

.leaflet-bar {
    border: 1px solid var(--border-color) !important;
    box-shadow: none !important;
    border-radius: 8px !important;
    overflow: hidden;
}

.leaflet-bar a {
    background-color: var(--bg-card) !important;
    color: var(--text-primary) !important;
    border-bottom: 1px solid var(--border-color) !important;
    transition: var(--transition-interactive);
}

.leaflet-bar a:hover {
    background-color: var(--bg-card-hover) !important;
    color: var(--color-emerald) !important;
}

.leaflet-control-attribution {
    background-color: rgba(7, 9, 14, 0.7) !important;
    color: var(--text-muted) !important;
    backdrop-filter: blur(4px);
}

.leaflet-control-attribution a {
    color: var(--text-secondary) !important;
}

/* Map popup styled to match dashboard */
.leaflet-popup-content-wrapper {
    background-color: rgba(18, 25, 41, 0.95) !important;
    backdrop-filter: blur(12px);
    border: 1px solid var(--border-hover);
    color: var(--text-primary) !important;
    border-radius: 10px !important;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4) !important;
}

.leaflet-popup-tip {
    background-color: rgba(18, 25, 41, 0.95) !important;
    border: 1px solid var(--border-hover);
}

.leaflet-popup-content {
    margin: 12px 16px !important;
    font-family: var(--font-body) !important;
    font-size: 13px !important;
    line-height: 1.4 !important;
}

.leaflet-popup-content h4 {
    font-family: var(--font-heading);
    font-size: 14px;
    font-weight: 700;
    margin-bottom: 6px;
    color: var(--color-emerald);
}

.map-popup-stat {
    font-size: 11px;
    color: var(--text-secondary);
    margin-top: 4px;
}

/* Map Overlay Card */
.map-overlay-card {
    position: absolute;
    top: var(--space-4);
    right: var(--space-4);
    z-index: 1000;
    width: 200px;
    padding: var(--space-4);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.map-overlay-header {
    display: flex;
    align-items: center;
    width: 100%;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
    border-bottom: 1px solid var(--border-color);
    padding: 0 0 var(--space-2) 0;
    background: none;
    border-left: none;
    border-right: none;
    border-top: none;
    color: inherit;
    font-family: inherit;
    cursor: pointer;
}

.overlay-icon {
    width: 16px;
    height: 16px;
    color: var(--color-blue);
    flex-shrink: 0;
}

.map-overlay-header h4 {
    flex: 1;
    text-align: left;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.overlay-chevron {
    width: 14px;
    height: 14px;
    color: var(--text-secondary);
    flex-shrink: 0;
    transition: transform var(--dur-base) var(--ease-standard);
}

.map-overlay-card.collapsed .overlay-chevron {
    transform: rotate(180deg);
}

.map-overlay-card.collapsed .map-overlay-header {
    margin-bottom: 0;
    border-bottom: none;
    padding-bottom: 0;
}

.map-overlay-card.collapsed .overlay-controls {
    display: none;
}

.overlay-controls {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* Legend for the categorically-colored crime pins, tucked under the
   "Recent Crimes" toggle and shown only while that layer is on (falls back
   to always-visible in browsers without :has()). */
.crime-pin-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-2);
    padding: 0 var(--space-2) var(--space-1) var(--space-2);
}

.control-item:has(#show-crimes-layer:not(:checked)) + .crime-pin-legend {
    display: none;
}

.crime-pin-legend .legend-key {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: 10px;
    color: var(--text-muted);
}

.crime-pin-legend .legend-dot {
    width: 8px;
    height: 8px;
    margin: 0;
    border-radius: 50%;
}

.legend-dot.cat-violent { background-color: var(--cat-crime-violent); }
.legend-dot.cat-property { background-color: var(--cat-crime-property); }
.legend-dot.cat-other { background-color: var(--cat-crime-mixed); }

/* Scope toggle: switches Violations/Permits/Crimes pins between the active
   neighborhood only and citywide (matching how Parks/Schools/Business
   Corridors are always citywide). */
.scope-toggle {
    display: flex;
    background-color: rgba(255, 255, 255, 0.06);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: 2px;
    margin-bottom: var(--space-1);
}

.scope-toggle-btn {
    flex: 1;
    border: none;
    background: none;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    padding: var(--space-2) var(--space-1);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: var(--transition-interactive);
}

.scope-toggle-btn:hover {
    color: var(--text-primary);
}

.scope-toggle-btn.active {
    background-color: var(--color-blue);
    color: #fff;
}

/* Separates the newer points-of-interest toggles from the original
   property-activity ones -- 8 flat switches in a row read as an
   undifferentiated wall of controls, so give the panel real structure
   instead of just a longer list. */
.overlay-group-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px solid var(--border-color);
}

.control-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-2);
    margin: 0 calc(-1 * var(--space-2));
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: var(--transition-interactive);
}

.control-item:hover {
    background-color: rgba(255, 255, 255, 0.05);
}

.control-label {
    font-size: 12px;
    color: var(--text-secondary);
    font-weight: 500;
    user-select: none;
}

/* Map Visuals toggle switches. Each layer's track color matches that
   layer's actual marker color on the map (see PALETTE in app.js), so the
   panel doubles as a legend instead of using an arbitrary generic blue. */
.control-item input[type="checkbox"] {
    /* display:none would remove this from the tab order entirely --
       keyboard users could never reach or toggle these. Visually hidden
       but still focusable, so Tab + Space work like a native checkbox. */
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

.control-item input[type="checkbox"]:focus-visible ~ .toggle-switch {
    outline: 2px solid var(--color-blue);
    outline-offset: 2px;
}

.toggle-switch {
    width: 30px;
    height: 17px;
    flex-shrink: 0;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    background-color: rgba(255, 255, 255, 0.06);
    display: inline-block;
    position: relative;
    transition: var(--transition-interactive);
}

.toggle-switch::after {
    content: '';
    position: absolute;
    top: 1px;
    left: 1px;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    background-color: var(--text-secondary);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
    transition: var(--transition-interactive);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch::after {
    background-color: #fff;
    transform: translateX(13px);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.rose {
    background-color: var(--color-rose);
    border-color: var(--color-rose);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.amber {
    background-color: var(--color-amber);
    border-color: var(--color-amber);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.pink {
    background-color: var(--color-pink);
    border-color: var(--color-pink);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.teal {
    background-color: var(--color-teal);
    border-color: var(--color-teal);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.blue {
    background-color: var(--color-blue);
    border-color: var(--color-blue);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.emerald {
    background-color: var(--color-emerald);
    border-color: var(--color-emerald);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.indigo {
    background-color: var(--color-indigo);
    border-color: var(--color-indigo);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.slate {
    background-color: var(--color-slate);
    border-color: var(--color-slate);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.lime {
    background-color: var(--color-lime);
    border-color: var(--color-lime);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.cyan {
    background-color: var(--color-cyan);
    border-color: var(--color-cyan);
}

.control-item input[type="checkbox"]:checked ~ .toggle-switch.gold {
    background-color: var(--color-gold);
    border-color: var(--color-gold);
}

/* Heatmap Legend */
.heatmap-legend {
    position: absolute;
    bottom: var(--space-5);
    left: var(--space-5);
    z-index: 1000;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-lg);
}

.heatmap-legend-label {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.heatmap-legend-bar {
    width: 120px;
    height: 8px;
    border-radius: 4px;
    background: linear-gradient(
        to right,
        var(--color-emerald) 0%,
        var(--color-amber) 50%,
        var(--color-rose) 85%,
        #ffffff 100%
    );
}

/* Neighborhood boundary hover tooltip on the map */
.neighborhood-border-tooltip {
    background-color: rgba(18, 25, 41, 0.95) !important;
    border: 1px solid var(--border-hover) !important;
    color: var(--text-primary) !important;
    font-family: var(--font-body) !important;
    font-size: 12px !important;
    font-weight: 600;
    padding: 4px 10px !important;
    border-radius: 6px !important;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}

.neighborhood-border-tooltip::before {
    border-top-color: rgba(18, 25, 41, 0.95) !important;
}

/* Leaflet makes clicked vector paths (e.g. neighborhood borders) focusable
   for accessibility. The browser's default focus rectangle boxes the path's
   bounding box instead of following its shape, which reads as a stray
   rectangle on the map, so replace it with a subtle glow on the SVG layer. */
.leaflet-interactive:focus {
    outline: none;
}
path.leaflet-interactive:focus {
    filter: drop-shadow(0 0 4px rgba(100, 116, 139, 0.8));
}

/* Loader Overlay */
.loader-overlay {
    position: absolute;
    inset: 0;
    background-color: rgba(7, 9, 14, 0.85);
    backdrop-filter: blur(8px);
    z-index: 1500;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
}

.spinner {
    width: 40px;
    height: 40px;
    border: 3px solid rgba(16, 185, 129, 0.1);
    border-radius: 50%;
    border-top-color: var(--color-emerald);
    animation: spin 1s linear infinite;
    filter: drop-shadow(0 0 10px var(--color-emerald-glow));
}

.loader-text {
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
}

/* Mobile-only view switch between Property and Insights panels, and the
   drag handle / full-map toggle that wrap it. All inert above the mobile
   breakpoint -- see the bottom-sheet rules in the max-width:768px block. */
.mobile-view-tabs {
    display: none;
}

.sheet-handle {
    display: none;
}

/* Right Panel: Insights and Charts */
.insights-container {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.insights-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
    /* Stretch (the default) so row-mates match height even when one card
       has more trend detail (e.g. Crimes vs Permits) — a jagged mix of
       short/tall bordered boxes in the same row reads as broken layout;
       one card having quiet bottom padding reads as an even grid. */
    align-items: stretch;
}

.insight-card {
    padding: var(--space-4);
    /* Grid items default to min-width: auto, which refuses to shrink below
       its content's intrinsic width — a row that can't compress enough
       (e.g. a wide label + tag next to a nowrap value) then pushes the
       whole card past its grid track and off the edge of the viewport
       instead of wrapping inside it. Force it to respect the track. */
    min-width: 0;
}

.card-label {
    font-size: 12px;
    line-height: 1.3;
    /* Reserve space for 2 lines so the value below always starts at the
       same height whether this card's label wraps (e.g. "Population
       Estimate") or fits on one line (e.g. "Property Value") — otherwise
       row-mates' values drift out of alignment depending on label length. */
    min-height: calc(1.3em * 2);
    color: var(--text-secondary);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    margin-bottom: 4px;
}

/* Combined insight cards (e.g. "Code Enforcement & Incident Activity")
   group several related stats as rows instead of separate boxes, so a
   thin row's content never leaves a tall empty gap next to a busier
   row-mate the way same-sized standalone cards used to. */
.stat-panel {
    display: flex;
    flex-direction: column;
}

.stat-panel .card-label {
    /* This label has no cross-card sibling to align against — reclaim
       the 2-line reservation .card-label normally reserves. */
    min-height: 0;
}

.stat-rows {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1;
    gap: var(--space-3);
    margin-top: var(--space-2);
}

.stat-row {
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-color);
}

.stat-row:first-child {
    padding-top: 0;
    border-top: none;
}

.stat-row-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
}

/* Small categorical dot preceding a stat label — ties the row to its data
   domain (see the --cat-* tokens in theme.css) without shouting. Aligned
   optically to the text's midline rather than the baseline. */
.cat-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: var(--space-1);
    vertical-align: 9%;
}

.cat-dot.cat-violent { background-color: var(--cat-crime-violent); }
.cat-dot.cat-property { background-color: var(--cat-crime-property); }
.cat-dot.cat-crime { background-color: var(--cat-crime-mixed); }
.cat-dot.cat-code { background-color: var(--cat-code-violation); }
.cat-dot.cat-permit { background-color: var(--cat-permit); }
.cat-dot.cat-civic { background-color: var(--cat-civic); }

.stat-row-label {
    font-size: 13px;
    color: var(--text-secondary);
    font-weight: 500;
    /* Flex items also default to min-width: auto — without this, the
       label refuses to wrap and instead forces .stat-row-top (and the
       card around it) wider than the grid column. */
    min-width: 0;
}

/* Names the row's data-source type (resident report, agency action, etc.)
   inline instead of a full block tag, since several rows now share one
   card — keeps the resident/agency/police distinction without repeating
   a full-width label per metric. */
.row-tag {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    margin-left: var(--space-1);
}

.stat-row-value {
    font-size: 18px;
    font-weight: 700;
    flex-shrink: 0;
    white-space: nowrap;
}

.stat-row-value.rose { color: var(--color-rose); }
.stat-row-value.amber { color: var(--color-amber); }
.stat-row-value.pink { color: var(--color-pink); }
.stat-row-value.blue { color: var(--color-blue); }
.stat-row-value.teal { color: var(--color-teal); }
.stat-row-value.indigo { color: var(--color-indigo); }

/* ---- Standardized metric cards (Economic Profile / Code Enforcement &
   Incidents / 311 Service Requests) ------------------------------------
   These three share one fixed shape -- header, 2 metric blocks, 1 bottom
   badge -- specifically so they render at identical height in a row,
   instead of each growing to fit its own content like the rest of
   .insight-card. Kept as its own component (not a .stat-panel variant)
   so this height/density contract can't drift if .stat-panel changes for
   Safety Overview or some future card that doesn't want it. */
.metric-cards-grid {
    display: grid;
    /* .panel-right is a fixed 400px sidebar on desktop (~344px of content
       after padding) -- nowhere near enough for 3 columns of text-2xl
       numbers side by side, so that's the default. It's a viewport-width
       *container* query, not a media query, specifically because this
       panel is narrowest exactly when the viewport is widest (desktop
       sidebar) and can get much wider on a phone/tablet where it becomes a
       near-full-width bottom sheet -- the inverse of the usual "mobile is
       narrow" assumption a max-width media query would encode. */
    grid-template-columns: 1fr;
    gap: var(--space-4);
    align-items: stretch;
    margin-top: var(--space-3);
}

@container (min-width: 620px) {
    .metric-cards-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

.metric-card {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: var(--space-4);
    border-radius: var(--radius-lg);
    min-width: 0;
}

.metric-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}

.metric-card-pill {
    font-size: 10px;
    font-weight: 700;
    font-family: var(--font-mono, monospace);
    color: var(--text-muted);
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    text-transform: none;
    letter-spacing: 0;
}

/* Distributes the 2 metric blocks evenly across the card's remaining
   vertical space (justify-content: space-around), so a card whose blocks
   have less trend text below them doesn't just leave a dense cluster at
   the top with dead air underneath -- every card's whitespace balances
   the same way regardless of how much each block's content fills. */
.metric-card-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    padding: var(--space-3) 0;
    gap: var(--space-2);
}

.metric-block-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
}

.metric-label {
    font-size: 12px;
    color: var(--text-secondary);
    min-width: 0;
}

.metric-value {
    font-size: 24px;
    font-weight: 800;
    color: var(--text-primary);
    flex-shrink: 0;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.metric-value.rose { color: var(--color-rose); }
.metric-value.amber { color: var(--color-amber); }
.metric-value.pink { color: var(--color-pink); }
.metric-value.blue { color: var(--color-blue); }
.metric-value.teal { color: var(--color-teal); }
.metric-value.indigo { color: var(--color-indigo); }

.metric-trend {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 2px;
}

/* Anchored to the card's bottom edge regardless of how much (or little)
   vertical space the header/body above take up, so every card in the row
   ends on the same footer line. */
.metric-card-footer {
    margin-top: auto;
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-color);
}

.metric-badge {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    background: var(--bg-card-hover);
    border: 1px solid var(--border-color);
    font-size: 12px;
    font-weight: 500;
    color: var(--text-secondary);
}

.metric-badge-label {
    display: flex;
    align-items: center;
    min-width: 0;
    color: var(--text-muted);
}

.metric-badge-label .cat-dot {
    margin-right: var(--space-1);
}

.metric-badge-value {
    flex-shrink: 0;
    font-weight: 700;
    color: var(--text-primary);
    text-align: right;
}

/* Category tint (not a good/bad grade) -- same violent/property hues as
   the crime-map legend and the neighborhood-overview split tag, see
   .trend-tag.cat-violent/.cat-property. */
.metric-badge-value.cat-violent { color: var(--cat-crime-violent); }
.metric-badge-value.cat-property { color: var(--cat-crime-property); }

.data-quality-note {
    font-size: 11px;
    color: var(--text-muted);
    font-style: italic;
    margin-top: var(--space-1);
    line-height: 1.4;
}

/* Puts a qualitative badge (e.g. "High backlog") on its own line rather
   than letting a text detail try to flow inline after it and wrap
   wherever it happens to run out of room. */
.card-trend-detail {
    margin-top: var(--space-1);
}

.card-value {
    font-size: 24px;
    font-weight: 700;
}

.card-value.emerald {
    color: var(--color-emerald);
}

.card-value.rose {
    color: var(--color-rose);
}

.card-value.amber {
    color: var(--color-amber);
}

.card-value.pink {
    color: var(--color-pink);
}

.card-value.blue {
    color: var(--color-blue);
}

.card-value.teal {
    color: var(--color-teal);
}

.card-value.indigo {
    color: var(--color-indigo);
}

/* Numbers everywhere align digit-for-digit: tabular figures keep stat
   columns, ranks, and currency values on a rigid vertical rhythm even as
   the values change. */
.card-value,
.stat-row-value,
.val-value,
.health-risk-value,
.owner-portfolio-num,
.hotcorners-count,
.hotcorners-rank,
.leaderboard-value,
.leaderboard-rank,
.trend-badge {
    font-variant-numeric: tabular-nums;
}

/* ---- Safety Overview card ------------------------------------------ */
/* The insights grid's hero: full-width summary of the two crime indices.
   Tier chip + one-line reading first; the index rows below; methodology
   behind the accordion at the bottom. */
.safety-card {
    grid-column: 1 / -1;
}

/* Column, not row: badge+trend get their own full-width line above the
   title instead of a narrow side column squeezed next to it. That side-
   column layout is what made a long label like "Well Above Range" wrap
   across 3 lines -- this way the badge always has the card's full width
   to lay out on one line. */
.safety-summary {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-2) 0 var(--space-3);
    border-bottom: 1px solid var(--border-color);
}

.safety-tier-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.safety-tier-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    font-family: var(--font-heading);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: 0.01em;
    white-space: nowrap;
    border: 1px solid transparent;
    transition: var(--transition-interactive);
}

.safety-tier-badge.tier-pending {
    background: rgba(107, 114, 128, 0.15);
    border-color: rgba(107, 114, 128, 0.3);
    color: var(--text-secondary);
}

/* Deliberately the SAME single hue for all four tiers, not a green-to-red
   scale -- this badge describes where a rate sits relative to the citywide
   range, not a grade of the neighborhood. Blue reads as neutral information
   here (same "informational," not "good/bad," accent already used for the
   panel header icon and chart accents elsewhere), which gives it visible
   contrast against the card without an alarm color ever attaching to
   "Well Above Range." */
.safety-tier-badge.tier-below,
.safety-tier-badge.tier-typical,
.safety-tier-badge.tier-above,
.safety-tier-badge.tier-well-above {
    background: var(--color-blue-glow);
    border-color: rgba(59, 130, 246, 0.35);
    color: var(--color-blue);
}

.safety-summary-info {
    min-width: 0;
}

.safety-summary-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    line-height: 1.3;
}

.safety-summary-sub {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 2px;
}

/* The safety card's two index rows sit side by side where width allows,
   sharing the same row anatomy as every other stat panel. */
.safety-card .stat-rows {
    flex-direction: row;
    gap: var(--space-4);
}

.safety-card .stat-row {
    flex: 1;
    min-width: 0;
    padding-top: 0;
    border-top: none;
}

.safety-card .stat-row + .stat-row {
    padding-left: var(--space-4);
    border-left: 1px solid var(--border-color);
}

/* ---- Progressive disclosure (accordion) ----------------------------- */
/* Generic accordion: a .disclosure-toggle button points at its
   .disclosure-body via data-disclosure. Height animates through the CSS
   grid 0fr -> 1fr technique — no JS measurement, no max-height guessing. */
.disclosure-toggle {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    margin-top: var(--space-3);
    padding: var(--space-2) 0;
    background: none;
    border: none;
    border-top: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-family: var(--font-body);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    cursor: pointer;
    text-align: left;
    transition: var(--transition-interactive);
}

.disclosure-toggle:hover {
    color: var(--text-primary);
}

.disclosure-chevron {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    transition: transform var(--dur-base) var(--ease-standard);
}

.disclosure-toggle.open .disclosure-chevron {
    transform: rotate(180deg);
}

.disclosure-body {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--dur-slow) var(--ease-standard);
}

.disclosure-body.open {
    grid-template-rows: 1fr;
}

.disclosure-content {
    overflow: hidden;
    min-height: 0;
}

.disclosure-text {
    font-size: 12px;
    line-height: 1.6;
    color: var(--text-secondary);
    padding-bottom: var(--space-2);
}

.disclosure-text:last-child {
    padding-bottom: 0;
}

.disclosure-text strong {
    color: var(--text-primary);
    font-weight: 600;
}

/* Health & Risk uses the shared accordion but its toggle doubles as the
   card header — no divider line above it, no uppercase re-style. */
.health-risk-section .disclosure-toggle {
    margin-top: 0;
    padding: 0;
    border-top: none;
    font-size: 13px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text-primary);
}

/* ---- Skeleton loading states ---------------------------------------- */
/* A loading value keeps its exact box (text turns transparent; the shimmer
   paints over the same rect), so nothing ever reflows when data lands:
   layout shift stays at zero by construction. */
@keyframes skeletonShimmer {
    from { background-position: 200% 50%; }
    to { background-position: -200% 50%; }
}

.is-loading {
    position: relative;
    color: transparent !important;
    background-image: linear-gradient(
        100deg,
        rgba(255, 255, 255, 0.05) 40%,
        rgba(255, 255, 255, 0.12) 50%,
        rgba(255, 255, 255, 0.05) 60%
    );
    background-size: 200% 100%;
    border-radius: var(--radius-sm);
    animation: skeletonShimmer 1.6s var(--ease-standard) infinite;
    min-width: 48px;
    user-select: none;
    pointer-events: none;
}

/* Trend lines under a loading value: hide the old badges/tags entirely
   (they're stale, and colored chips showing through a shimmer look broken)
   while preserving one line of height. */
.card-trend.is-loading > * {
    visibility: hidden;
}

.skeleton-line,
.skeleton-block {
    display: inline-block;
    height: 0.75em;
    border-radius: var(--radius-sm);
    background-image: linear-gradient(
        100deg,
        rgba(255, 255, 255, 0.05) 40%,
        rgba(255, 255, 255, 0.12) 50%,
        rgba(255, 255, 255, 0.05) 60%
    );
    background-size: 200% 100%;
    animation: skeletonShimmer 1.6s var(--ease-standard) infinite;
}

.skeleton-line {
    display: block;
    margin: var(--space-1) 0;
}

.skeleton-block {
    height: 100%;
}

.hotcorners-row.skeleton-row {
    cursor: default;
}

.hotcorners-row.skeleton-row .hotcorners-rank {
    color: transparent;
}

.hotcorners-row.skeleton-row .skeleton-count {
    width: 24px;
    height: 20px;
}

.timeline-marker.crime-item {
    background-color: var(--color-pink);
    box-shadow: 0 0 8px var(--color-pink-glow);
}

.timeline-tag.pink {
    color: var(--color-pink);
}

/* Map Markers Styling */
.marker-pin {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid #fff;
    box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
    transition: var(--transition-interactive);
}

.marker-pin.selected-pin {
    width: 16px;
    height: 16px;
    border: 2.5px solid #fff;
    box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}

/* Points of interest (schools, and any point-geometry parks) get a rounded
   square instead of a circle. Violations/permits/crimes are all circular
   dots differing only by color -- with 5+ marker colors on the map at
   once, color alone isn't a reliable way to tell "a stable place" apart
   from "a reported activity" at a glance. */
.marker-pin.poi-marker {
    border-radius: 4px;
}

/* Marker cluster badges (Leaflet.markercluster) for the Violations/Permits/
   Crimes layers. Leaflet.markercluster's own CSS files style its default
   grey/yellow/orange badges -- overridden here so clusters read as "this
   layer's color" instead of a size-only heatmap-like gradient. */
.custom-cluster-icon {
    background: transparent !important;
    border: none !important;
}

.cluster-badge {
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 2px solid #fff;
    color: #fff;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: 700;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
}

.card-trend {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: var(--space-2);
    line-height: 1.4;
}

.trend-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--space-2);
    border-radius: var(--radius-sm);
    font-size: 11px;
    font-weight: 600;
    margin-right: 2px;
}
.trend-badge.trend-good {
    background: rgba(16, 185, 129, 0.12);
    color: var(--color-emerald);
}
.trend-badge.trend-bad {
    background: rgba(239, 68, 68, 0.12);
    color: var(--color-rose);
}
.trend-badge.trend-neutral {
    background: rgba(107, 114, 128, 0.12);
    color: var(--text-secondary);
}
.trend-badge.trend-amber {
    background: rgba(245, 158, 11, 0.12);
    color: var(--color-amber);
}

/* Small caption above a .trend-tags row for tags whose meaning isn't
   already obvious from the stat-row label above them (e.g. a reason
   breakdown) -- without it, a row of bare pills has no stated subject. */
.trend-tags-label {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin-top: var(--space-2);
}

.trend-tags {
    display: flex;
    gap: var(--space-1);
    margin-top: var(--space-1);
    flex-wrap: wrap;
    align-items: center;
}
.trend-tags-label + .trend-tags {
    margin-top: var(--space-1); /* tighter directly under its own label than after a badge/text line */
}
/* 0.1 background with no border read as barely-there dark boxes against the
   already-dark, blurred glass card underneath -- closer to invisible than
   a colored chip. A stronger fill plus a matching-hue border gives them an
   actual visible edge instead of just a faint tint. */
.trend-tag {
    display: inline-block;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    font-size: 10px;
    font-weight: 500;
    background: rgba(107, 114, 128, 0.18);
    border: 1px solid rgba(107, 114, 128, 0.3);
    color: var(--text-secondary);
}
.trend-tag.trend-bad {
    background: rgba(239, 68, 68, 0.18);
    border-color: rgba(239, 68, 68, 0.35);
    color: var(--color-rose);
}
.trend-tag.trend-good {
    background: rgba(16, 185, 129, 0.18);
    border-color: rgba(16, 185, 129, 0.35);
    color: var(--color-emerald);
}
.trend-tag.trend-amber {
    background: rgba(245, 158, 11, 0.18);
    border-color: rgba(245, 158, 11, 0.35);
    color: var(--color-amber);
}
/* Category tint (not a good/bad grade) for tags describing a composition --
   e.g. which crime type makes up the majority of incidents. Reuses the same
   hues as the crime-map legend (--cat-crime-violent/--cat-crime-property) so
   "this tag is about the violent-crime category" reads consistently with the
   rest of the app, instead of red = bad / green = good escalation right next
   to the Safety Overview card's deliberately neutral tier badge. */
.trend-tag.cat-violent {
    background: color-mix(in srgb, var(--cat-crime-violent) 18%, transparent);
    border-color: color-mix(in srgb, var(--cat-crime-violent) 35%, transparent);
    color: var(--cat-crime-violent);
}
.trend-tag.cat-property {
    background: color-mix(in srgb, var(--cat-crime-property) 18%, transparent);
    border-color: color-mix(in srgb, var(--cat-crime-property) 35%, transparent);
    color: var(--cat-crime-property);
}
/* Same neutral-informational blue as the Safety Overview tier badge --
   used for citywide-position context (e.g. "above the citywide median")
   that describes a comparison, not a good/bad reading, so it stays one
   fixed hue regardless of which quarter the rate falls in. */
.trend-tag.trend-info {
    background: var(--color-blue-glow);
    border-color: rgba(59, 130, 246, 0.35);
    color: var(--color-blue);
}

.chart-container-card {
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.chart-title {
    font-size: 14px;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    border-bottom: 1px solid var(--border-color);
    padding-bottom: var(--space-2);
}

.chart-title svg {
    width: 16px;
    height: 16px;
    color: var(--color-blue);
}

.chart-wrapper {
    position: relative;
    height: 180px;
    width: 100%;
}

/* Extra height for charts with wrapped multi-line axis labels */
.chart-wrapper.wrapped-labels {
    height: 220px;
}

/* Crime Hot Corners */
.hotcorners-filter {
    margin-left: auto;
    background: var(--bg-card-hover);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-size: 11px;
    font-family: var(--font-body);
    border-radius: var(--radius-sm);
    padding: var(--space-1) var(--space-2);
}

.hotcorners-list {
    display: flex;
    flex-direction: column;
}

.hotcorners-item:last-child .hotcorners-row {
    border-bottom: none;
}

.hotcorners-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--border-color);
    cursor: pointer;
}
.hotcorners-row:hover {
    background: var(--bg-card-hover);
}

.hotcorners-chevron {
    flex: none;
    font-size: 11px;
    color: var(--text-muted);
    transition: var(--transition-interactive);
}
.hotcorners-row[aria-expanded="true"] .hotcorners-chevron {
    transform: rotate(-180deg);
}

.hotcorners-detail {
    max-height: 0;
    overflow: hidden;
}
.hotcorners-detail.open {
    max-height: 260px;
    overflow-y: auto;
    padding: var(--space-1) 0 var(--space-3) 36px; /* 36 = rank width 24 + row gap 12: detail text aligns under the address */
    border-bottom: 1px solid var(--border-color);
}
.hotcorners-item:last-child .hotcorners-detail.open {
    border-bottom: none;
}

.hotcorners-detail-row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-1) 0;
    font-size: 12px;
    color: var(--text-secondary);
}
.hotcorners-detail-row:first-child {
    padding-top: 2px;
}

.hotcorners-detail-more {
    font-size: 11px;
    color: var(--text-muted);
    padding-top: 4px;
    font-style: italic;
}

.hotcorners-rank {
    width: 24px;
    height: 24px;
    flex: none;
    border-radius: var(--radius-sm);
    background: var(--bg-card-hover);
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 11px;
    color: var(--text-secondary);
}

.hotcorners-info {
    flex: 1;
    min-width: 0;
}

.hotcorners-address {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hotcorners-type {
    font-size: 11px;
    color: var(--text-muted);
    margin-top: 2px;
}

.hotcorners-count {
    font-size: 16px;
    font-weight: 700;
    color: var(--cat-crime-mixed); /* re-tinted per active filter inline (see renderHotCorners) */
    flex: none;
}

/* Top Neighborhoods leaderboard (city-wide scope) */
.leaderboard-section + .leaderboard-section {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-color);
}

.leaderboard-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
    margin-bottom: var(--space-2);
}

.leaderboard-list {
    display: flex;
    flex-direction: column;
    list-style: none;
}

.leaderboard-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) 0;
}

.leaderboard-rank {
    width: 24px;
    height: 24px;
    flex: none;
    border-radius: var(--radius-sm);
    background: var(--bg-card-hover);
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 10px;
    color: var(--text-secondary);
}

.leaderboard-name {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-weight: 500;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.leaderboard-value {
    font-size: 13px;
    font-weight: 700;
    flex: none;
}

.hotcorners-empty {
    color: var(--text-muted);
    font-size: 12px;
    text-align: center;
    padding: 20px 0;
}

/* Neighborhood Culture (curated vibe/history copy) */
.profile-card .profile-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.profile-vibe {
    font-size: 13px;
    font-weight: 600;
    font-style: italic;
    color: var(--color-teal);
}

.profile-history {
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.profile-card.is-empty .profile-vibe,
.profile-card.is-error .profile-vibe {
    display: none;
}

.profile-card.is-empty .profile-history,
.profile-card.is-error .profile-history {
    color: var(--text-muted);
    font-style: italic;
}

/* Neighborhood Context card: Current Challenges + Current Interventions
   read together as one issue -> response pair, so they share one outer
   card. The card itself is hidden until loadNeighborhoodProfile() finds
   at least one challenge or intervention; each inner subsection is
   independently hidden if its own list is empty. */
.profile-context-card {
    display: none;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out), display var(--dur-slow) allow-discrete;
}

.profile-context-card.is-visible {
    display: flex;
    opacity: 1;
    transform: translateY(0);
}

/* A labeled sub-section within a merged profile card (Historical Facts
   inside Neighborhood Culture; Challenges/Interventions inside
   Neighborhood Context) -- a lighter-weight heading than a full
   .chart-title, with a top divider so it still reads as its own topic
   without repeating a full bordered card's chrome. Hidden until its
   list has content. */
.profile-subsection {
    display: none;
    margin-top: var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-color);
    opacity: 0;
    transform: translateY(4px);
    transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out), display var(--dur-slow) allow-discrete;
}

.profile-subsection.is-visible {
    display: block;
    opacity: 1;
    transform: translateY(0);
}

/* display:none -> block is an abrupt binary toggle; the transition above
   only animates the block->none direction by default. @starting-style
   supplies the "before" frame for the none->block direction too, so a
   newly-shown subsection fades + rises in instead of popping in instantly.
   Falls back to the old instant toggle on browsers without
   @starting-style/allow-discrete support -- no regression, just no
   animation. */
@starting-style {
    .profile-subsection.is-visible {
        opacity: 0;
        transform: translateY(4px);
    }
}

.profile-subsection-label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: var(--space-3);
}

.profile-subsection-label svg {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.profile-subsection-label.challenges svg {
    color: var(--color-amber);
}

.profile-subsection-label.interventions svg {
    color: var(--color-emerald);
}

.profile-subsection-label.historical svg {
    color: var(--color-slate);
}

.profile-challenges-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.profile-challenges-list li {
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-secondary);
    padding-left: 16px;
    position: relative;
}

.profile-challenges-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-amber);
}

.profile-interventions-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.profile-interventions-list li {
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-secondary);
    padding-left: 16px;
    position: relative;
}

.profile-interventions-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-emerald);
}

/* Region badge — small pill next to the "Neighborhood Culture" title,
   from neighborhood_stats.region (see migration 0015). Hidden by default;
   shown only once loadNeighborhoodProfile() confirms the neighborhood has
   a region on record (every real neighborhood does; UNKNOWN doesn't). */
.profile-region-badge {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 2px 8px;
    margin-left: auto;
}

.profile-historical-facts-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.profile-historical-facts-list li {
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-secondary);
    padding-left: 16px;
    position: relative;
}

.profile-historical-facts-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-slate);
}

/* Animations */
@keyframes markerPulse {
    0% {
        transform: scale(1);
        opacity: 0.8;
        filter: drop-shadow(0 0 0 rgba(107, 114, 128, 0.4));
    }
    50% {
        transform: scale(1.1);
        opacity: 1;
        filter: drop-shadow(0 0 12px rgba(16, 185, 129, 0.6));
    }
    100% {
        transform: scale(1);
        opacity: 0.8;
        filter: drop-shadow(0 0 0 rgba(107, 114, 128, 0.4));
    }
}

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

@keyframes slideIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Responsive Styling */
@media (max-width: 1200px) {
    .app-main {
        grid-template-columns: 360px 1fr;
        gap: var(--space-3);
        padding: var(--space-3);
    }

    .panel-right {
        display: none; /* Collapses the third panel on standard screens, or we can toggle it */
    }
}

@media (max-width: 768px) {
    /* The map fills the whole area below the header; the Property/Insights
       sheet floats above it and is animated with a single `transform`
       (cheap on the compositor) instead of resizing a grid track on every
       frame of a drag. */
    .app-main {
        display: block;
        position: relative;
        overflow: hidden;
        padding: 0; /* full-bleed: the desktop workspace gutters go away */
    }

    /* Collapse controls are a desktop-only feature; the mobile sheet
       already provides a full-screen map via the "peek" state. */
    .panel-collapse-toggle,
    .panel-collapsed-strip {
        display: none !important;
    }

    .panel-map {
        position: absolute;
        inset: 0;
        border: none;
        border-radius: 0;
    }

    .mobile-sheet {
        display: flex;
        flex-direction: column;
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        /* Leaves a sliver of map visible above the sheet even fully expanded. */
        height: calc(100% - 56px);
        background-color: var(--bg-panel);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
        border-top: 1px solid var(--border-color);
        border-radius: var(--radius-xl) var(--radius-xl) 0 0;
        box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.45);
        z-index: 1300;
        transform: translateY(0);
        transition: transform var(--dur-slow) var(--ease-sheet);
        will-change: transform;
    }

    /* No transition while a finger is actively dragging the handle -- the
       sheet should track the touch point 1:1, not ease toward it. Also drop
       the backdrop blur for the duration of the drag: live-sampling and
       re-blurring the map underneath on every frame the sheet moves is the
       single most expensive thing happening during the gesture, and it's
       what actually causes the dropped frames/stutter, even on fast
       hardware. A flat, near-opaque fill stands in for it until the sheet
       settles, then the real blur snaps back in as the sheet eases to its
       final position (below) -- attention is on the moving sheet, not the
       one-frame swap, and animating the blur itself would reintroduce the
       same cost it's fixing. */
    .mobile-sheet.dragging {
        transition: none;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background-color: rgba(11, 15, 23, 0.96);
    }

    .sheet-handle {
        display: flex;
        flex-direction: column;
        flex-shrink: 0;
        padding-top: 8px;
        cursor: grab;
        touch-action: none; /* the handle owns vertical drag gestures, not page scroll */
        -webkit-user-select: none;
        user-select: none;
        -webkit-touch-callout: none; /* no iOS text-selection magnifier when a drag starts on/near a label */
    }

    .sheet-handle:active {
        cursor: grabbing;
    }

    .sheet-grabber {
        display: block;
        width: 36px;
        height: 4px;
        border-radius: 2px;
        background: var(--border-hover);
        margin: 0 auto 8px;
    }

    .sheet-handle-row {
        display: flex;
        align-items: stretch;
        min-height: 44px; /* Property/Insights/full-map all live here -- the row itself
                              was measuring ~38px tall, under Apple's 44pt tap-target min */
        background-color: rgba(7, 9, 14, 0.6);
        border-top: 1px solid var(--border-color);
        border-bottom: 1px solid var(--border-color);
    }

    /* Segmented-chip tabs: soft pill highlight on the active view rather
       than an underline, matching the scope toggle's segmented-control
       language. These are the sheet's sticky "filter chips" — they live in
       the always-visible handle row, never scrolling away. */
    .mobile-view-tabs {
        display: flex;
        flex: 1;
        gap: var(--space-1);
        padding: var(--space-1) var(--space-2);
    }

    .mobile-view-tab {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: var(--space-1);
        padding: var(--space-2);
        background: none;
        border: none;
        border-radius: var(--radius-md);
        color: var(--text-secondary);
        font-family: var(--font-body);
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
        transition: var(--transition-interactive);
    }

    .mobile-view-tab svg {
        width: 14px;
        height: 14px;
    }

    .mobile-view-tab.active {
        color: var(--color-emerald);
        background-color: var(--color-emerald-glow);
    }

    .sheet-map-toggle {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        flex-shrink: 0;
        background: none;
        border: none;
        border-left: 1px solid var(--border-color);
        color: var(--text-secondary);
        cursor: pointer;
    }

    .sheet-map-toggle svg {
        width: 16px;
        height: 16px;
    }

    .sheet-map-toggle.active {
        color: var(--color-emerald);
    }

    /* Inside the sheet, the panels are plain scroll regions — the sheet
       itself supplies the glass chrome, so their desktop card treatment
       (border, radius, blur) comes off. */
    .panel-left,
    .panel-right {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        border: none;
        border-radius: 0;
        background: transparent;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        padding: 0 var(--space-4) var(--space-4);
    }

    /* Sticky in-panel headers: match the tighter mobile panel padding and
       drop the desktop's rounded top corners. */
    .search-section,
    .panel-header-sticky {
        margin: 0 calc(-1 * var(--space-4)) var(--space-4);
        padding: var(--space-3) var(--space-4);
        border-radius: 0;
    }

    .panel-right {
        display: none;
    }

    /* The Safety Overview's two index rows sit side by side on desktop;
       at phone width they stack back into standard divided rows. */
    .safety-card .stat-rows {
        flex-direction: column;
        gap: var(--space-3);
    }

    .safety-card .stat-row + .stat-row {
        padding-left: 0;
        border-left: none;
        padding-top: var(--space-3);
        border-top: 1px solid var(--border-color);
    }

    .app-main.mobile-view-insights .panel-left {
        display: none;
    }

    .app-main.mobile-view-insights .panel-right {
        display: flex;
    }

    .map-overlay-card {
        top: 16px;
        right: 16px;
        width: 180px;
    }

    /* Expanded, this list can run tall enough to reach into the area the
       bottom sheet occupies at its default height -- without this, the
       sheet (it sits above the map at z-index: 1300) visually cuts the
       list off mid-row instead of the map just showing through. Whatever
       the user just opened should stay on top, above the sheet at any of
       its snap positions. */
    .map-overlay-card:not(.collapsed) {
        z-index: 1400;
    }

    /* Collapsed by default on mobile so it doesn't cover the map. The card's
       own padding is zeroed and handed to the header button instead (below)
       -- otherwise that padding is visually part of the pill but sits
       outside the <button>, so taps near the edge of what looks like one
       tappable pill silently do nothing. */
    .map-overlay-card.collapsed {
        width: auto;
        padding: 0;
    }

    /* The button now owns the full pill: padding deep enough to clear
       Apple's 44pt minimum tap target, matching the visible shape exactly
       so there's no dead zone around the edge. */
    .map-overlay-card.collapsed .map-overlay-header {
        padding: var(--space-3) var(--space-4);
        margin-bottom: 0;
        min-height: 22px;
    }

    /* If a mobile user taps it open, 8+ toggle rows could still push the
       card past the visible map area -- scroll the list internally
       instead of growing the card past its container. */
    .map-overlay-card:not(.collapsed) .overlay-controls {
        max-height: 45vh;
        overflow-y: auto;
    }

    /* Compact, non-overflowing header */
    .app-header {
        height: auto;
        min-height: 56px;
        padding: var(--space-2) var(--space-4);
        gap: var(--space-2);
        flex-wrap: wrap;
    }

    .logo-subtitle {
        display: none;
    }

    .logo-area h1 {
        font-size: 18px;
    }

    .header-actions {
        gap: var(--space-2);
        min-width: 0;
        flex: 0 0 100%;
        justify-content: flex-end;
    }

    .picker-label {
        display: none;
    }

    .neighborhood-picker-container {
        flex: 1;
        min-width: 0;
        gap: 0;
    }

    .select-wrapper {
        width: 100%;
    }

    #neighborhood-select {
        width: 100%;
        min-width: 0;
        /* iOS Safari auto-zooms the page on focus if a form field's font-size
           is under 16px, and often doesn't zoom back out afterward. */
        font-size: 16px;
    }

    #search-input {
        font-size: 16px;
    }

    .one-pager-link span {
        display: none; /* icon-only to save space */
    }

    .one-pager-link {
        padding: var(--space-2) var(--space-3);
        flex: none;
    }

    /* Timeline tabs: drop icons and tighten so 3 tabs fit cleanly */
    .tab-btn {
        font-size: 12px;
        padding: var(--space-3) 2px;
        gap: 0;
    }

    .tab-btn svg {
        display: none;
    }

    .owner-portfolio-card {
        left: 12px;
        /* Map Visuals now sits top-right on mobile too -- leave room for it
           at its full expanded width, same idea as the desktop rule above. */
        max-width: calc(100% - 24px - 190px);
        padding-bottom: 4px; /* the drag handle below supplies its own tap-target padding */
    }

    .owner-portfolio-handle {
        display: flex;
        justify-content: center;
        padding: 8px 0 2px;
        margin-top: 4px;
        cursor: grab;
        touch-action: none; /* the handle owns vertical drag gestures, not page scroll */
        -webkit-user-select: none;
        user-select: none;
        -webkit-touch-callout: none;
    }

    .owner-portfolio-handle .sheet-grabber {
        margin: 0;
    }

    .owner-portfolio-handle:active {
        cursor: grabbing;
    }

    /* At a real phone width, 2 columns leave each insight card ~170px wide
       -- too narrow for a label + data-source tag + value without heavy
       wrapping. Stack them full-width instead. */
    .insights-grid {
        grid-template-columns: 1fr;
    }

    /* Pull-to-stretch header (see setupPullToStretch in app.js): --pull runs
       0..1 as the panel is pulled past its scrolled-to-top edge, and these
       grow instead of a hero image scaling up the way iOS does it. No
       transition while actively pulling -- it should track the finger, not
       ease toward it -- .stretch-settling adds one back for the release
       spring-back only. */
    .property-card-header,
    .panel-header-sticky {
        --pull: 0;
    }

    .property-card-header {
        padding-top: calc(var(--pull) * 16px);
        padding-bottom: calc(var(--pull) * 8px);
    }

    /* The sticky insights header keeps its own base padding under the
       stretch — the pull only ever adds to it. */
    .panel-header-sticky {
        padding-top: calc(var(--space-3) + var(--pull) * 16px);
        padding-bottom: calc(var(--space-3) + var(--pull) * 8px);
    }

    .property-card-header .address-title h2 {
        font-size: calc(20px + var(--pull) * 5px);
    }

    .panel-header-sticky .panel-title {
        font-size: calc(18px + var(--pull) * 5px);
    }

    .property-card-header.stretch-settling,
    .property-card-header.stretch-settling .address-title h2,
    .panel-header-sticky.stretch-settling,
    .panel-header-sticky.stretch-settling .panel-title {
        transition: padding 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), font-size 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
    }
}

/* ── Neighborhood Chat Assistant ────────────────────────────────────────
   Floating launcher + slide-up panel, above every other layer (map,
   mobile sheet, loader overlays all top out at 1500). Grounded answers
   come from server/src/groqChat.js via api/v1/chat.js; see
   buildChatContext()/sendChatMessage() in app.js. */
.chat-launcher {
    position: fixed;
    bottom: var(--space-5);
    right: var(--space-5);
    z-index: 2000;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-indigo);
    border: none;
    box-shadow: 0 4px 16px rgba(99, 102, 241, 0.45);
    cursor: pointer;
    transition: var(--transition-interactive);
}

.chat-launcher:hover {
    background: #7477f5;
    transform: scale(1.05);
}

.chat-launcher i {
    width: 24px;
    height: 24px;
    color: #fff;
}

.chat-launcher.open {
    display: none;
}

.chat-panel {
    position: fixed;
    bottom: var(--space-5);
    right: var(--space-5);
    z-index: 2000;
    width: 380px;
    max-width: calc(100vw - var(--space-5) * 2);
    height: 520px;
    max-height: calc(100vh - var(--space-5) * 2);
    display: flex;
    flex-direction: column;
    background-color: var(--bg-card);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-xl);
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
    opacity: 0;
    transform: translateY(16px) scale(0.98);
    pointer-events: none;
    transition: opacity var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-out);
}

.chat-panel.open {
    opacity: 1;
    transform: translateY(0) scale(1);
    pointer-events: auto;
}

.chat-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-4);
    border-bottom: 1px solid var(--border-color);
    flex-shrink: 0;
}

.chat-panel-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-heading);
    font-weight: 600;
    font-size: 15px;
    color: var(--text-primary);
}

.chat-panel-title i {
    width: 18px;
    height: 18px;
    color: var(--color-indigo);
}

.chat-panel-close {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-muted);
    display: flex;
    padding: var(--space-1);
    border-radius: var(--radius-sm);
    transition: var(--transition-interactive);
}

.chat-panel-close:hover {
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.08);
}

.chat-panel-close i {
    width: 18px;
    height: 18px;
}

.chat-panel-sub {
    font-size: 11px;
    color: var(--text-muted);
    padding: var(--space-2) var(--space-4) 0;
    flex-shrink: 0;
}

.chat-messages {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-3) var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.chat-message {
    max-width: 88%;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-lg);
    font-size: 13px;
    line-height: 1.5;
}

.chat-message p {
    margin: 0;
}

.chat-message p + p {
    margin-top: var(--space-2);
}

.chat-message.assistant {
    align-self: flex-start;
    background: rgba(99, 102, 241, 0.12);
    border: 1px solid rgba(99, 102, 241, 0.25);
    color: var(--text-primary);
    border-bottom-left-radius: var(--radius-sm);
}

.chat-message.user {
    align-self: flex-end;
    background: var(--bg-card-hover);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    border-bottom-right-radius: var(--radius-sm);
}

.chat-message.error {
    align-self: flex-start;
    background: var(--color-rose-glow);
    border: 1px solid rgba(239, 68, 68, 0.35);
    color: var(--color-rose);
}

.chat-message.pending {
    align-self: flex-start;
    display: flex;
    gap: 4px;
    padding: var(--space-3);
}

.chat-message.pending span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-indigo);
    opacity: 0.4;
    animation: chat-typing-bounce 1.2s infinite ease-in-out;
}

.chat-message.pending span:nth-child(2) { animation-delay: 0.15s; }
.chat-message.pending span:nth-child(3) { animation-delay: 0.3s; }

@keyframes chat-typing-bounce {
    0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }
    30% { opacity: 1; transform: translateY(-3px); }
}

.chat-input-row {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--border-color);
    flex-shrink: 0;
}

.chat-input-row input {
    flex: 1;
    min-width: 0;
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
    color: var(--text-primary);
    font-size: 13px;
    font-family: var(--font-body);
}

.chat-input-row input:focus {
    outline: none;
    border-color: var(--color-indigo);
}

.chat-input-row input::placeholder {
    color: var(--text-muted);
}

.chat-input-row button {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    border: none;
    background: var(--color-indigo);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: var(--transition-interactive);
}

.chat-input-row button:hover {
    background: #7477f5;
}

.chat-input-row button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.chat-input-row button i {
    width: 16px;
    height: 16px;
    color: #fff;
}

@media (max-width: 480px) {
    .chat-panel {
        bottom: var(--space-3);
        right: var(--space-3);
        left: var(--space-3);
        width: auto;
        max-width: none;
        height: min(520px, calc(100vh - var(--space-3) * 2));
    }

    .chat-launcher {
        bottom: var(--space-3);
        right: var(--space-3);
    }
}
