/* ═══════════════════════════════════════════════════════════════════════════
   Blue Belmont - Design Language & Token System
   ═══════════════════════════════════════════════════════════════════════════

   DESIGN INTENT
   ─────────────
   This platform is used by real estate agents, investors, brokers, and
   lenders to make decisions about property. The design must feel:

     TRUSTWORTHY - Like a financial terminal, not a social app. Data is
     primary. Decoration is minimal. Every pixel earns its place by
     making a number easier to read or an action clearer to take.

     ANALYTICAL - Dense information presented with clear hierarchy.
     Monospace numbers align in columns. Labels are uppercase micro-text.
     Charts use muted fills, not saturated gradients.

     MODERN - Refined type pairing (Playfair Display for headings, Source
     Sans 3 for body, DM Mono for data). Smooth 150-250ms transitions.
     Cards with subtle shadows. No skeuomorphism, no gradients on buttons.

     EXPENSIVE - Warm neutrals (cream, gold, slate) instead of cold grays.
     Generous whitespace. Restrained color - gold is an accent, not a
     theme. The product should feel like it costs what it costs.

   WHAT THIS IS NOT
   ────────────────
   Not a crypto dashboard (no neon, no dark-mode-by-default, no
   glowing borders). Not a consumer app (no illustrations, no
   mascots, no emoji-as-icons). Not a startup template (no bouncy
   animations, no confetti, no "delightful" micro-interactions).

   COLOR PHILOSOPHY
   ────────────────
   The palette is a real estate palette: the warm tones of limestone
   and hardwood, the authority of slate, the credibility of gold.

   • Gold (#b5942a) is the accent. Used for: active states, primary
     CTA backgrounds, focus rings, the lead-score "hot" tier, and
     links. Never used as a background fill on large surfaces.

   • Slate (#1c2331) is the anchor. Used for: headings, primary text,
     the top nav, dark section backgrounds, and the hero overlay.
     Warm-toned - not pure black, not cold gray.

   • Cream (#faf8f4) is the canvas. Used for: page backgrounds, card
     surfaces, input backgrounds. Slightly warm - not sterile white.

   • Status colors are institution-standard:
     Success (#2e7d32), Warning (#f57c00), Error (#c62828), Info (#1565c0).
     Low-saturation, high-legibility. Never used decoratively.

   TYPOGRAPHY RULES
   ────────────────
   1. Playfair Display (serif) is for DISPLAY ONLY: hero headings,
      stat-card values, section h2s, and the brand wordmark. Never
      used for body text, labels, or UI controls.

   2. Source Sans 3 (sans-serif) is the workhorse: body text, labels,
      buttons, inputs, table cells, meta text. Always.

   3. DM Mono (monospace) is for DATA: parcel IDs, monetary values in
      tables, assessed values, timestamps, code-like content. Not for
      labels or headings.

   4. Font-variant-numeric: tabular-nums on all monetary/numeric
      columns so digits align vertically across rows.

   SPACING RULES
   ─────────────
   1. Use the --space-* scale (0.25rem increments up to 1.5rem, then
      2rem, 2.5rem, 3rem). No ad-hoc rem values in padding/margin/gap.

   2. Card body padding: --space-5 (1.25rem).
      Card header/footer: --space-4 vertical, --space-5 horizontal.
      This is the universal card rhythm - modal, property section,
      stat card, and data card all use it.

   3. Section gaps: --space-6 (1.5rem) between cards in a grid.
      --space-4 (1rem) between items within a card.

   ANIMATION RULES
   ───────────────
   1. Use --anim-fast (150ms) for hover states and micro-interactions.
      Use --anim-normal (250ms) for reveals and transitions.
      Use --anim-slow (400ms) for expand/collapse.

   2. Use --easing (cubic-bezier 0.4,0,0.2,1) for all transitions.
      Only use --easing-spring for the sidebar active indicator.

   3. No animation may scale above 1.0 (no overshoot, no bounce).
      Entrance animations start at scale(0.92) or translateY(0.5rem),
      never scale(0.7) or translateY(2rem).

   4. All animations respect prefers-reduced-motion: reduce.

   COMPONENT RULES
   ───────────────
   1. Buttons: height-locked to --control-h scale. Never padding-driven.
      Primary = gold bg. Secondary = border only. Ghost = no border.

   2. Inputs: same height scale as buttons so they align in rows.
      Focus ring: --shadow-gold (gold outline). Error: red border.

   3. Cards: --radius-lg corner radius. --shadow-card base shadow.
      Hover: --shadow-card-hover + --gold-border + 1px translateY lift.
      Never more than 1px lift. Never scale on hover.

   4. Badges: height-locked (1.25rem standard, 1rem compact, 1.5rem large).
      Inline-flex centered. --radius-sm for status badges,
      --radius-pill for filter pills and chips.

   5. Tables: --text-base body, --text-xs uppercase headers.
      Right-align numbers. Monospace + tabular-nums on numeric cells.
      Truncate long text columns. Never wrap monetary values.

   BUILD-TIME ENFORCEMENT
   ──────────────────────
   Three scripts enforce this design language:

     node frontend/check-routes.js   - Every link resolves to a real page
     node frontend/check-schema.js   - Every data field exists in the backend
     node frontend/check-copy.js     - No generic AI marketing language

   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Design Tokens ── */
:root {
    --gold: #b5942a;
    --gold-light: #d4af37;
    --gold-dark: #8a7020;
    --gold-muted: rgba(181, 148, 42, 0.08);
    --gold-border: rgba(181, 148, 42, 0.2);

    --slate: #1c2331;
    --slate-light: #2a3447;
    --slate-hover: #354158;
    --slate-muted: rgba(28, 35, 49, 0.6);

    --cream: #faf8f4;
    --warm-white: #fffdf8;
    --white: #ffffff;

    --border: #d5cfc5;
    --border-light: #ebe6dd;
    --border-focus: var(--gold);

    --text: #2c2c2c;
    --text-secondary: #555049;
    /* Contrast-safe secondaries - both meet WCAG AA (4.5:1) on cream/white.
       Previous values (#6b655c, #8b8680) failed at ~3.8:1 and ~3.0:1. */
    --muted: #5d5651;
    --faint: #767069;

    --bg-hover: #f0ece4;
    --bg-subtle: #f7f5f0;
    --bg-active: rgba(181, 148, 42, 0.06);

    --success: #2e7d32;
    --success-light: #e8f5e9;
    --warning: #f57c00;
    --warning-light: #fff3e0;
    --error: #c62828;
    --error-light: #ffebee;
    --info: #1565c0;
    --info-light: #e3f2fd;

    --shadow-sm: 0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.06);
    --shadow-md: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.08);
    --shadow-lg: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.1);
    --shadow-xl: 0 1rem 3rem rgba(0, 0, 0, 0.12);

    --radius-xs: 0.125rem;
    --radius-sm: 0.25rem;
    --radius-md: 0.375rem;
    --radius-lg: 0.5rem;
    --radius-xl: 0.75rem;
    --radius-pill: 62.4375rem;

    --top-nav-h: 4rem;
    --sidebar-w: 15rem;
    --sidebar-collapsed-w: 3.75rem;
    --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);

    --font-display: 'Playfair Display', 'Playfair Display Fallback', Georgia, serif;
    --font-body: 'Source Sans 3', 'Source Sans 3 Fallback', system-ui, -apple-system, sans-serif;
    --font-mono: 'DM Mono', 'DM Mono Fallback', 'IBM Plex Mono', monospace;

    --scroll-thumb: #b5a99a;
    --scroll-track: transparent;

    /* Gradients */
    --gold-gradient: linear-gradient(135deg, #b5942a 0%, #d4af37 100%);
    --slate-gradient: linear-gradient(135deg, #1c2331 0%, #2a3447 100%);

    /* Surfaces */
    --surface-elevated: var(--warm-white);
    --surface-inset: #f3efe8;

    /* Animation */
    --anim-fast: 150ms;
    --anim-normal: 250ms;
    --anim-slow: 400ms;
    --easing: cubic-bezier(0.4, 0, 0.2, 1);
    --easing-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);

    /* Z-index scale */
    --z-dropdown: 100;
    --z-sticky: 200;
    --z-modal: 2000;
    --z-toast: 3000;
    --z-tooltip: 4000;

    /* ── Responsive breakpoint scale ──
       All @media queries across the design system use these tiers.
       Units are `em` (not rem/px) because media-query em is always
       relative to 16px regardless of :root font-size.

       Tier        em       px     What changes
       ──────────  ──────   ────   ──────────────────────────────────
       bp-xl       80em    1280    6→3 col stats, grid refinements
       bp-lg       64em    1024    Sidebar icon-only, split→drawer,
                                   auth sidebar collapses
       bp-md       48em     768    Major layout shift, nav collapses,
                                   metrics 5→2, quick-actions 3→2
       bp-sm       40em     640    Modals fullscreen, grids→1 col,
                                   metrics→1, quick-actions→1
       bp-xs       30em     480    Final compact, card/tab shrink
       bp-2xs      22.5em   360    Micro phone floor, root font ↓

       Two intermediate steps for the grid utility cascade only:
          67.5em  grid-4/5 → 3 cols
          55em    grid-3/4/5 → 2 cols

       Every other file (auth, components, dashboard, property, style)
       must use only the six official tiers or the two intermediates. */

    /* ── Spacing scale (8pt grid) ──
       Use these instead of ad-hoc rem values so vertical and horizontal
       rhythm stay coherent across every page.                          */
    --space-0: 0;
    --space-1: 0.25rem;   /*  0.25rem */
    --space-2: 0.5rem;    /*  0.5rem */
    --space-3: 0.75rem;   /* 0.75rem */
    --space-4: 1rem;      /* 1rem */
    --space-5: 1.25rem;   /* 1.25rem */
    --space-6: 1.5rem;    /* 1.5rem */
    --space-8: 2rem;      /* 2rem */
    --space-10: 2.5rem;   /* 2.5rem */
    --space-12: 3rem;     /* 3rem */
    --space-16: 4rem;     /* 4rem */
    --space-20: 5rem;     /* 5rem */
    --space-24: 6rem;     /* 6rem */

    /* ── Type scale ──
       Type goes from microcopy (xs) up to large display headings (5xl).
       Body text defaults to --text-sm = 0.9375rem (0.9375rem) for dense UIs. */
    --text-2xs:  0.5625rem;  /* 0.5625rem - micro labels, activity tags */
    --text-xs:   0.6875rem;  /* 0.6875rem - labels, hints, table meta */
    --text-sm:   0.8125rem;  /* 0.8125rem - small ui */
    --text-base: 0.9375rem;  /* 0.9375rem - body, table cells */
    --text-md:   1rem;       /* 1rem - emphasis body */
    --text-lg:   1.125rem;   /* 1.125rem - section titles */
    --text-xl:   1.375rem;   /* 1.375rem - card headings */
    --text-2xl:  1.75rem;    /* 1.75rem - page title */
    --text-3xl:  2.25rem;    /* 2.25rem - hero secondary */
    --text-4xl:  3rem;       /* 3rem - hero */
    --text-5xl:  3.75rem;    /* 3.75rem - landing hero */

    /* Line heights */
    --leading-tight: 1.2;
    --leading-snug:  1.35;
    --leading-normal: 1.5;
    --leading-relaxed: 1.7;

    /* Font weights */
    --weight-regular: 400;
    --weight-medium:  500;
    --weight-semibold: 600;
    --weight-bold:    700;

    /* ── Refined elevation ──
       Premium shadows use the gold/slate palette so they don't grey-wash
       the cream background.                                             */
    --shadow-card:    0 0.0625rem 0.125rem rgba(28, 35, 49, 0.04), 0 0.125rem 0.5rem rgba(28, 35, 49, 0.05);
    --shadow-card-hover: 0 0.25rem 0.75rem rgba(28, 35, 49, 0.08), 0 0.0625rem 0.1875rem rgba(28, 35, 49, 0.04);
    --shadow-popover: 0 0.75rem 2rem rgba(28, 35, 49, 0.12), 0 0.125rem 0.375rem rgba(28, 35, 49, 0.06);
    --shadow-modal:   0 1.5rem 4rem rgba(28, 35, 49, 0.18), 0 0.25rem 0.75rem rgba(28, 35, 49, 0.08);
    --shadow-gold:    0 0 0 0.1875rem rgba(181, 148, 42, 0.18);
    --shadow-inset:   inset 0 0.0625rem 0.125rem rgba(28, 35, 49, 0.06);
    --shadow-gold-hover: 0 0.25rem 0.75rem rgba(181, 148, 42, 0.25);
    --shadow-error:   0 0 0 0.1875rem rgba(198, 40, 40, 0.18);
    --shadow-elevated: 0 0.5rem 1.25rem rgba(28, 35, 49, 0.08);

    /* ── Surface tones ── */
    --surface-card: var(--warm-white);
    --surface-canvas: var(--cream);
    --surface-raised: #ffffff;
    --surface-overlay: rgba(28, 35, 49, 0.55);
    --surface-overlay-blur: 0.375rem;

    /* ── Control heights ──
       Single source of truth for buttons, inputs, selects, and any
       interactive control. Sharing these makes a button + input + select
       in the same row line up perfectly without extra wrapper hacks.
       Aligned to a 0.25rem sub-grid. */
    --control-h-xs:  1.5rem;    /* 1.5rem - table-row inline action */
    --control-h-sm:  1.875rem;  /* 1.875rem - toolbar dense */
    --control-h:     2.25rem;   /* 2.25rem - default button / select / input */
    --control-h-lg:  2.75rem;   /* 2.75rem - touch target / hero CTA */

    /* Padding-x scale that pairs with the control heights so button text
       has a deliberate hug rather than ad-hoc whitespace. */
    --control-px-xs: 0.5rem;
    --control-px-sm: 0.75rem;
    --control-px:    1rem;
    --control-px-lg: 1.25rem;
}

/* ── Motion System ──────────────────────────────────────────────────────
   Three tiers, one easing, GPU-only properties.

   TIER       TOKEN            DURATION  USE CASE
   ─────────  ───────────────  ────────  ──────────────────────────────
   Micro      --anim-fast      150ms     Hover states, color changes,
                                         icon swaps, chip interactions
   Standard   --anim-normal    250ms     Card reveals, tab transitions,
                                         dropdown opens, toasts, modals
   Macro      --anim-slow      400ms     Page entrance, staggered
                                         cascades, section reveals

   EASING     --easing         cubic-bezier(0.4, 0, 0.2, 1)
   All interactive motion uses this single curve. The only exception
   is --easing-spring on the sidebar active indicator.

   STAGGER    50ms increments via .stagger-1 through .stagger-12.
              Max cascade length: 600ms.

   ENTRANCE VOCABULARY
   ───────────────────
   fadeInUp       THE entrance. Pages, cards, tabs, sections, rows.
   fadeInDown     Dropdowns, popovers opening downward.
   scaleIn        Sidebar markers, small indicators.
   popIn          Pills, badges, notification dots.
   slideInRight   Slide-in panels, toast notifications.

   EXIT VOCABULARY
   ───────────────
   fadeOutDown    Modal/drawer close, panel dismiss.
   popOut         Pill/badge removal.
   toast-out      Toast dismissal (slide right).

   LOADING VOCABULARY
   ──────────────────
   shimmer        Skeleton placeholder sweep (1.5s linear infinite).
   shimmerSweep   Gauge loading shimmer (finite gradient sweep).
   spin           Spinner rotation (0.6s linear infinite).

   DATA VOCABULARY
   ───────────────
   gaugeSweep     SVG gauge fill from 0 to target offset.
   drawNumber     Stat counter settle (snap into place).
   underlineGrow  Tab/link underline draw-in.
   activeNavSlide Sidebar gold marker entrance.

   RULES
   ─────
   1. Only animate transform and opacity. Never width/height/top/left.
   2. No animation may scale above 1.0.
   3. Entrance animations start at scale(0.92) or translateY(0.5rem).
   4. All motion respects prefers-reduced-motion: reduce.
   ──────────────────────────────────────────────────────────────────── */

/* ── Entrances ── */
@keyframes fadeInUp {
    from { opacity: 0; transform: translateY(0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInDown {
    from { opacity: 0; transform: translateY(-0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes slideInRight {
    from { opacity: 0; transform: translateX(1rem); }
    to   { opacity: 1; transform: translateX(0); }
}
@keyframes scaleIn {
    from { opacity: 0; transform: scale(0.95); }
    to   { opacity: 1; transform: scale(1); }
}
@keyframes popIn {
    from { opacity: 0; transform: scale(0.92); }
    to   { opacity: 1; transform: scale(1); }
}

/* ── Exits ── */
@keyframes fadeOutDown {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(0.5rem); }
}
@keyframes popOut {
    from { opacity: 1; transform: scale(1); }
    to   { opacity: 0; transform: scale(0.95); }
}

/* ── Data visualization ── */
@keyframes gaugeSweep {
    from { stroke-dashoffset: var(--gauge-circumference, 999); }
    to   { stroke-dashoffset: var(--gauge-target-offset, 0); }
}
@keyframes drawNumber {
    from { transform: translateY(0.1875rem); opacity: 0.65; }
    to   { transform: translateY(0); opacity: 1; }
}

/* ── Navigation ── */
@keyframes activeNavSlide {
    from { transform: scaleY(0.4); opacity: 0; }
    to   { transform: scaleY(1); opacity: 1; }
}
@keyframes underlineGrow {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

/* ── Loading ── */
@keyframes shimmerSweep {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(100%); }
}

/* ── Fallback font metric overrides ──
   Stops the layout shift that happens when Playfair Display + Source
   Sans 3 finish loading and Chrome swaps them in. We define an
   adjusted local fallback for each loaded face whose ascent / descent /
   line-gap match the loaded font's metrics so the swap is visually
   imperceptible. This is the cleanest CLS fix for web fonts -
   significantly better than relying on font-display:swap alone. */
@font-face {
    font-family: 'Source Sans 3 Fallback';
    src: local('Helvetica Neue'), local('Helvetica'), local('Arial');
    size-adjust: 100.6%;
    ascent-override: 92%;
    descent-override: 25%;
    line-gap-override: 0%;
}
@font-face {
    font-family: 'Playfair Display Fallback';
    src: local('Georgia'), local('Times New Roman'), local('Times');
    size-adjust: 105%;
    ascent-override: 95%;
    descent-override: 28%;
    line-gap-override: 0%;
}
@font-face {
    font-family: 'DM Mono Fallback';
    src: local('Menlo'), local('Consolas'), local('Courier New'), local('monospace');
    size-adjust: 95%;
    ascent-override: 88%;
    descent-override: 24%;
    line-gap-override: 0%;
}

/* ── Reset & Base ── */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html {
    font-size: 1rem;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Reserve space for the scrollbar so the viewport width stays constant
       whether the page is scrollable or not. Prevents the ~6px layout shift
       that happens when content grows/shrinks past the viewport height. */
    scrollbar-gutter: stable;
}

/* Skip-to-content link - hidden off-screen, visible on focus for
   keyboard users who need to bypass the top nav and sidebar. */
.skip-link {
    position: absolute;
    top: -100%;
    left: var(--space-4);
    z-index: 10000;
    background: var(--gold);
    color: var(--slate);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-md);
    font-weight: var(--weight-bold);
    font-size: var(--text-sm);
    text-decoration: none;
    box-shadow: var(--shadow-md);
}
.skip-link:focus {
    top: var(--space-2);
}

/* Screen-reader-only utility - visually hidden, announced by assistive tech.
   Used for table captions, status text, and other content that provides
   context without needing visual space. */
.sr-only {
    position: absolute;
    width: 0.0625rem;
    height: 0.0625rem;
    padding: 0;
    margin: -0.0625rem;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Font-display strategy:
   - Source Sans 3 (body): display=swap - renders system font immediately,
     swaps when loaded. Metric fallback minimizes shift.
   - Playfair Display (headings): display=optional - uses Georgia if not
     loaded by first paint. No visible swap, no CLS. Set in the Google
     Fonts URL (?display=optional), not overridden here.
   - DM Mono (data): display=optional - same as Playfair. */

body {
    font-family: 'Source Sans 3', 'Source Sans 3 Fallback', system-ui, -apple-system, sans-serif;
    color: var(--text);
    background: var(--cream);
    line-height: var(--leading-normal);
}
body.fullscreen-app {
    overflow: hidden;
    height: 100vh;
}
a { color: var(--gold); text-decoration: none; transition: color var(--transition); }
a:hover { color: var(--gold-dark); }

/* Custom scrollbars */
::-webkit-scrollbar { width: 0.375rem; height: 0.375rem; }
::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: var(--radius-xs); }
::-webkit-scrollbar-thumb:hover { background: var(--muted); }
::-webkit-scrollbar-track { background: var(--scroll-track); }

/* ═══════════════════════════════════════════════════════════════════════════
   TOP NAVIGATION
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════
   TOP NAV - enterprise SaaS standard (Linear / Stripe / CoStar pattern)
   Flat slate surface, single-line wordmark, no decorative gradients.
   Height locked at 3.5rem. Spacing follows an 8-point grid.
   ═══════════════════════════════════════════════════════════════════ */
.top-nav {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: var(--top-nav-h);
    background: var(--slate);
    border-bottom: 0.0625rem solid rgba(255, 255, 255, 0.06);
    /* Flex layout - picked over grid because the bell needs to be
       PERFECTLY centered between the search input's right edge and the
       user button's left edge, at every viewport width. With `margin:
       auto` on both sides of .nav-actions, the bell consumes the free
       space symmetrically and lands exactly at the midpoint between its
       neighbors. The previous grid layout centered the bell against
       the search COLUMN's right edge, but .nav-search has max-width:
       34rem so on wide screens the visible input ended before the
       column did - and the bell drifted right. */
    display: flex;
    align-items: center;
    padding: 0 clamp(0.875rem, 1.5vw, 1.5rem);
    /* Nav-priority tier - sits above modals (2000), toasts (3000), cookie
       banner (9000), and ABOVE the sidebar (9500) so the search-bar
       dropdown can overlap the sidebar without being clipped. The whole
       top-nav establishes a stacking context, so its child .nav-search
       dropdown paints over the sidebar regardless of DOM order. Only the
       accessibility skip link (10000) is allowed to render above this. */
    z-index: 9520;
    /* No flex gap - auto margins on .nav-actions handle the spacing
       around the bell. Brand-to-search spacing comes from .nav-search's
       own margin-left. */
    min-width: 0;
}
/* Each child is non-shrinking except the search bar, which can shrink
   below its 34rem basis on narrow screens. */
.top-nav .mobile-menu-btn { flex: 0 0 auto; }
.top-nav .top-nav-brand   { flex: 0 0 auto; min-width: 0; overflow: hidden; }
.top-nav .nav-search      { flex: 0 1 34rem; min-width: 0; }
/* Wide-screen default: bell sits next to the user button with a
   small breathing margin. `margin-left: auto` consumes ALL the free
   space between .nav-search and the bell, so on wide viewports the
   bell isn't isolated in the middle of an empty band - it stays in
   the right cluster, the standard top-bar pattern. */
.top-nav .nav-actions     {
    flex: 0 0 auto;
    margin-left: auto;
    margin-right: clamp(0.5rem, 1vw, 1rem);
}
.top-nav .nav-user        { flex: 0 0 auto; }
/* Bell auto-centering ONLY when:
     1. The search bar is visible (i.e. the viewport is wider than the
        ≤48em mobile breakpoint where .nav-search is `display:none`); AND
     2. The screen is narrow enough that the gap between .nav-search and
        .nav-user is tight (≤64em - roughly where .nav-search's 34rem
        flex-basis stops being fully honored).
   Outside that band the bell stays in the right cluster (margin-left:
   auto + a small fixed margin-right). On mobile, search is hidden so
   centering between the missing search and the user button would just
   isolate the bell - keep it next to the user instead. */
@media (min-width: 48.01em) and (max-width: 64em) {
    .top-nav .nav-actions {
        margin-right: auto;
    }
}

.top-nav-brand {
    /* Brand cell stretches to the full top-nav height so the logo can
       flex-center both axes inside it - no asymmetric padding pushing
       the image to one edge. */
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.5rem;
    height: 100%;
    padding: 0 0.5rem;
    color: #fff;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: opacity var(--anim-fast) var(--easing);
    justify-self: start;
    align-self: stretch;
}
.top-nav-brand:hover { opacity: 0.85; }
.top-nav-brand-logo {
    display: block;
    margin: auto 0;
    height: clamp(1.75rem, 1rem + 1.4vw, 2.375rem);
    width: auto;
    max-width: min(15rem, 38vw);
    object-fit: contain;
}
.top-nav-brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.625rem;
    height: 1.625rem;
    border-radius: 0.3125rem;
    background: var(--gold);
    color: var(--slate);
    box-shadow: none;
}
.top-nav-brand-mark svg { width: 1rem; height: 1rem; stroke-width: 2; }
.top-nav-brand-text {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    line-height: 1;
}
.top-nav-brand-name {
    font-family: var(--font-display);
    font-weight: var(--weight-bold);
    font-size: 0.9375rem;
    color: #fff;
    letter-spacing: -0.005em;
}
/* The regional tagline is hidden on the app shell - enterprise apps don't
   carry marketing copy into the product chrome. It's kept in the DOM for
   semantic/SEO value but is visually suppressed so the top bar stays quiet. */
.top-nav-brand-region {
    display: none;
}

/* Global Search - hugs the left side of the middle grid track so it sits
   closer to the brand instead of dead-center under the nav. The width caps
   at 34rem so the input stays a comfortable reading length on ultrawide
   monitors, and a small margin-left keeps it from touching the logo.
   Everything is relative (rem / %) so narrow viewports shrink cleanly. */
.nav-search {
    justify-self: start;
    width: 100%;
    max-width: 34rem;
    margin-left: clamp(0.5rem, 3vw, 2.5rem);
    position: relative;
    /* Hoist the search column's stacking context above every page
       layer so its absolutely-positioned results dropdown can't be
       trapped behind a sibling that establishes its own context. */
    z-index: 12000;
}
.nav-search-input {
    width: 100%;
    height: 2.125rem;
    background: rgba(255, 255, 255, 0.06);
    border: 0.0625rem solid rgba(255, 255, 255, 0.08);
    border-radius: 0.375rem;
    padding: 0 0.875rem 0 2.25rem;
    color: #fff;
    font-family: var(--font-body);
    font-size: 0.8125rem;
    font-weight: var(--weight-regular);
    transition: background 120ms ease, border-color 120ms ease;
    outline: none;
    line-height: 2.125rem;
}
.nav-search-input::placeholder { color: rgba(255, 255, 255, 0.4); }
.nav-search-input:hover { background: rgba(255, 255, 255, 0.08); }
.nav-search-input:focus {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.2);
    box-shadow: none;
}
.nav-search-icon {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    color: rgba(255, 255, 255, 0.45);
    pointer-events: none;
    display: inline-flex;
    align-items: center;
}
.nav-search-input { padding-right: 3rem; }
.nav-search-kbd {
    position: absolute;
    right: 0.625rem;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none;
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: var(--weight-semibold);
    color: rgba(255, 255, 255, 0.55);
    background: rgba(255, 255, 255, 0.07);
    border: 0.0625rem solid rgba(255, 255, 255, 0.12);
    border-radius: var(--radius-sm);
    padding: 0.125rem 0.375rem;
    letter-spacing: 0.04em;
}
.nav-search-input:focus + .nav-search-kbd { opacity: 0; }
.nav-search-results {
    display: none;
    position: absolute;
    top: calc(100% + 0.375rem);
    left: 0; right: 0;
    /* Make the dropdown wide enough to overlap the sidebar on every
       viewport. The input itself stays a reasonable width, but the
       results panel widens to a fixed minimum (clamped to the viewport)
       so users see full result rows that visually float over the
       sidebar rail. */
    min-width: min(34rem, calc(100vw - 2rem));
    background: var(--warm-white);
    border: 0.0625rem solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-popover);
    /* Cap at 24rem on a tall screen, but always leave 1rem of viewport
       gutter so the dropdown never overruns the bottom edge. */
    max-height: min(24rem, calc(100vh - var(--top-nav-h) - 1rem));
    max-height: min(24rem, calc(100dvh - var(--top-nav-h) - 1rem));
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* Sits above EVERY non-toast layer in the app - including the
       sidebar (9500) and the top-nav stacking context (9520). The
       parent .top-nav stacking context already lifts this above the
       sidebar; the explicit z-index here is the in-context tiebreaker
       against any sibling that establishes its own context. The toast
       container at 10000 still wins so a confirmation can render over
       the dropdown, but nothing else may obscure live search results. */
    z-index: 12000;
}
.nav-search-results.active { display: block; animation: fadeInDown var(--anim-fast) var(--easing); }
.nav-search-result {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.625rem 0.875rem;
    cursor: pointer;
    transition: background var(--transition);
    border-bottom: 0.0625rem solid var(--border-light);
    font-size: var(--text-sm);
}
.nav-search-result:last-child { border-bottom: none; }
.nav-search-result:hover,
.nav-search-result.highlighted { background: var(--bg-hover); }
.nav-search-result-type {
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--gold);
    background: var(--gold-muted);
    padding: 0.125rem 0.375rem;
    border-radius: var(--radius-sm);
    flex-shrink: 0;
}
.nav-search-result-text { color: var(--text); font-weight: var(--weight-medium); }
.nav-search-result-sub { color: var(--muted); font-size: 0.75rem; margin-left: auto; flex-shrink: 0; }

/* Nav Actions */
.nav-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
    flex-shrink: 0;
}
.nav-action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.125rem;
    height: 2.125rem;
    border-radius: 0.375rem;
    border: none;
    background: transparent;
    color: rgba(255, 255, 255, 0.65);
    cursor: pointer;
    transition: color 120ms ease, background 120ms ease;
    font-size: 1rem;
    position: relative;
    flex-shrink: 0;
}
.nav-action-btn:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
.nav-action-btn .badge-dot {
    position: absolute;
    top: 0.375rem;
    right: 0.375rem;
    width: 0.4375rem;
    height: 0.4375rem;
    background: var(--gold-light);
    border-radius: 50%;
    border: 0.0938rem solid var(--slate);
}

/* User Menu */
.nav-user {
    position: relative;
    flex-shrink: 0;
}
.nav-user-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    height: 2.125rem;
    padding: 0 0.75rem 0 0.375rem;
    border-radius: 0.375rem;
    border: 0.0625rem solid var(--gold-border, rgba(212, 175, 55, 0.35));
    background: rgba(212, 175, 55, 0.1);
    color: var(--gold-light);
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
    font-family: var(--font-body);
    font-size: 0.8125rem;
    font-weight: var(--weight-semibold);
    letter-spacing: 0.01em;
    flex-shrink: 0;
}
.nav-user-btn:hover { background: rgba(212, 175, 55, 0.18); border-color: var(--gold); color: #fff; }
.nav-user-btn:focus-visible { outline: none; box-shadow: var(--shadow-gold); }
.nav-user-chevron { color: var(--gold-light); }
.nav-user-avatar {
    width: 1.625rem;
    height: 1.625rem;
    border-radius: 50%;
    background: var(--gold);
    color: var(--slate);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: var(--weight-bold);
    font-size: 0.75rem;
    flex-shrink: 0;
    letter-spacing: 0;
    text-transform: uppercase;
}
.nav-user-name {
    /* Reserve space up front so the swap from the boot placeholder
       ("Loading...") to the real user name doesn't shift the rest of
       the top nav. min-width handles short names like "Jane D" without
       leaving giant gaps. */
    min-width: 5rem;
    max-width: 8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Stable visual baseline regardless of the loaded name's length. */
    text-align: left;
}
.nav-user-chevron {
    opacity: 0.55;
    transition: transform var(--anim-fast) var(--easing);
    color: currentColor;
}
.nav-user.open .nav-user-chevron { transform: rotate(180deg); }
.nav-user-dropdown-item .ui-icon,
.nav-user-dropdown-item svg {
    margin-right: 0.5rem;
    opacity: 0.7;
}

.nav-user-dropdown {
    display: none;
    position: absolute;
    top: calc(100% + 0.375rem);
    right: 0;
    min-width: 14rem;
    max-width: min(18rem, calc(100vw - 1.5rem));
    max-height: calc(100vh - var(--top-nav-h) - 1rem);
    max-height: calc(100dvh - var(--top-nav-h) - 1rem);
    background: var(--warm-white);
    border: 0.0625rem solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-popover);
    /* Nav-priority panel tier - always above modals, toasts, cookie
       banner, and every other floating UI. Sits one step above the
       top-nav (9500) so the open menu paints over the nav bar itself. */
    z-index: 9510;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.nav-user.open .nav-user-dropdown { display: block; animation: fadeInDown var(--anim-fast) var(--easing); }
.nav-user-dropdown-header {
    padding: 0.75rem;
    border-bottom: 0.0625rem solid var(--border-light);
}
.nav-user-dropdown-name { font-weight: var(--weight-semibold); font-size: var(--text-sm); color: var(--text); }
.nav-user-dropdown-email { font-size: var(--text-xs); color: var(--muted); }
.nav-user-dropdown-plan {
    display: inline-block;
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.0625rem 0.375rem;
    border-radius: var(--radius-sm);
    margin-top: 0.25rem;
}
/* Plan badge variants. There is no perpetual free plan: every account starts
   on a 7-day trial, then either subscribes or lapses to "expired". */
.plan-trial      { background: var(--gold-muted); color: var(--gold-dark); border: 0.0625rem solid var(--gold-border); }
.plan-pro        { background: var(--gold-muted); color: var(--gold-dark); border: 0.0625rem solid var(--gold-border); }
.plan-enterprise { background: var(--info-light); color: var(--info); }
.plan-single_county { background: var(--info-light); color: var(--info); }
.plan-five_counties { background: var(--gold-muted); color: var(--gold-dark); border: 0.0625rem solid var(--gold-border); }
.plan-all_counties  { background: var(--gold); color: var(--slate); }
.plan-expired    { background: var(--error-light); color: var(--error); border: 0.0625rem solid rgba(198, 40, 40, 0.18); }
.plan-free       { background: var(--error-light); color: var(--error); border: 0.0625rem solid rgba(198, 40, 40, 0.18); } /* legacy alias */
.nav-user-dropdown-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5625rem 0.75rem;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background var(--transition);
    border: none;
    background: none;
    width: 100%;
    text-align: left;
    font-family: var(--font-body);
}
.nav-user-dropdown-item:hover { background: var(--bg-hover); }
.nav-user-dropdown-item.danger { color: var(--error); }
.nav-user-dropdown-divider { height: 0.0625rem; background: var(--border-light); margin: 0.25rem 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   SIDEBAR NAVIGATION - Three-state responsive behavior
   ═══════════════════════════════════════════════════════════════════════════

   Drives off body classes set by _initMobileMenu() in app.html:
     body.sb-drawer  - phone (<48em): hidden; hamburger opens as overlay
     body.sb-rail    - tablet (48-80em): icon-only rail, always visible
     body.sb-full    - desktop (>=80em): full labelled sidebar, always visible

   The --sidebar-gap variable is what .main-content uses for margin-left so
   content never sits under the rail. When the user manually "maximizes"
   the sidebar from rail on a medium screen, the sidebar grows to full
   width but --sidebar-gap stays at rail width - sidebar floats over
   content (the user asked for this trade-off explicitly).
   ═══════════════════════════════════════════════════════════════════════════ */

body.sb-drawer { --sidebar-gap: 0; }
body.sb-rail   { --sidebar-gap: var(--sidebar-collapsed-w); }
body.sb-full   { --sidebar-gap: var(--sidebar-w); }

/* Route-level override - views that need the full canvas (explorer /
   parcel map) toggle body.sb-hide-on-route on mount so the sidebar
   slides out and the content gap collapses to zero. Unmount removes
   the class and the normal mode resumes.
   CRITICAL: while the sidebar is hidden, the hamburger must come back
   so the user can pop the sidebar open as a drawer and navigate away
   from the map. Without this override the hamburger stays display:none
   from sb-full/sb-rail and the user is stranded on /explorer. */
body.sb-hide-on-route { --sidebar-gap: 0 !important; }
body.sb-hide-on-route .sidebar-nav { transform: translateX(-100%) !important; }
body.sb-hide-on-route .mobile-nav-overlay { display: none !important; }
body.sb-hide-on-route .mobile-menu-btn { display: inline-flex !important; }
body.sb-hide-on-route .sidebar-nav.mobile-open {
    transform: translateX(0) !important;
    width: min(18rem, 85vw);
}
body.sb-hide-on-route .mobile-nav-overlay:not(.hidden) {
    display: block !important;
}

/* Medium viewports: even if the user "maximizes" to full, keep the content
   gap at rail width so the sidebar covers the data it overlaps rather than
   squeezing content into an unreadable sliver. */
@media (max-width: 80em) {
    body.sb-full { --sidebar-gap: var(--sidebar-collapsed-w); }
}

/* Narrow viewports: if stale body classes hang around from a resize, fall
   back to drawer so the sidebar is a drawer (never a pinned rail) below 48em. */
@media (max-width: 48em) {
    body.sb-rail, body.sb-full { --sidebar-gap: 0; }
}

.sidebar-nav {
    position: fixed;
    top: var(--top-nav-h);
    left: 0;
    bottom: 0;
    width: var(--sidebar-w);
    max-width: 90vw;
    background: var(--white);
    border-right: 0.0625rem solid var(--border);
    display: flex;
    flex-direction: column;
    /* Nav-priority tier - when the hamburger is pressed the sidebar
       must overlap EVERYTHING on the page at every screen size: filter
       drawer (1170), notification panel, modals (2000), toasts (3000),
       cookie banner (9000). Only the accessibility skip link (10000)
       renders above. Matches `.top-nav` (9500) so the two surfaces form
       one cohesive chrome layer. */
    z-index: 9500;
    transform: translateX(-100%);
    transition: transform var(--anim-normal) var(--easing),
                width var(--anim-normal) var(--easing);
    overflow: hidden;
    box-shadow: 0 0.25rem 1.5rem rgba(28, 35, 49, 0.18);
}

/* Drawer (mobile) - off-screen, slides in on hamburger click */
.sidebar-nav.mobile-open { transform: translateX(0); }

/* Rail (tablet default) - pinned narrow, icon-only */
body.sb-rail .sidebar-nav {
    transform: translateX(0);
    width: var(--sidebar-collapsed-w);
    box-shadow: 0 0.0625rem 0.25rem rgba(28, 35, 49, 0.06);
}

/* Full (desktop default) - pinned wide, full labels */
body.sb-full .sidebar-nav {
    transform: translateX(0);
    width: var(--sidebar-w);
    box-shadow: 0 0.0625rem 0.25rem rgba(28, 35, 49, 0.08);
}

/* Small-screen safety: don't leak the pinned-open state into drawer
   mode if the classes haven't synced yet after a resize. */
@media (max-width: 48em) {
    body.sb-rail .sidebar-nav,
    body.sb-full .sidebar-nav {
        transform: translateX(-100%);
        width: min(18rem, 85vw);
    }
    body.sb-rail .sidebar-nav.mobile-open,
    body.sb-full .sidebar-nav.mobile-open {
        transform: translateX(0);
    }
}

/* Rail appearance - hide labels/section/footer, center icons */
body.sb-rail .sidebar-nav-label,
body.sb-rail .sidebar-nav-section,
body.sb-rail .sidebar-nav-divider,
body.sb-rail .sidebar-nav-footer,
body.sb-rail .sidebar-nav-badge {
    display: none !important;
}
body.sb-rail .sidebar-nav-item {
    justify-content: center;
    padding: 0.625rem 0;
    position: relative;
}
body.sb-rail .sidebar-nav-item .sidebar-nav-icon {
    margin: 0;
}
/* Tooltip-on-hover: since labels are hidden, expose the label via a
   floating pill so users can identify each icon by pointing at it.
   Guards:
     - [data-label]  requires JS to have populated the attribute - no
                      empty pills before hydration
     - :not(:empty)  double-safeguard; CSS `attr()` returning "" still
                      lets the pseudo-element paint its box, so we pair
                      with a non-empty check via the combinator below
     - @media (hover) + (pointer: fine) restricts to real mice, so
                      touch taps don't fire a sticky hover state and
                      flash a tooltip at the user as they navigate */
@media (hover: hover) and (pointer: fine) {
    body.sb-rail .sidebar-nav-item[data-label]:not([data-label=""]):hover::after {
        content: attr(data-label);
        position: absolute;
        left: calc(100% + 0.5rem);
        top: 50%;
        transform: translateY(-50%);
        background: var(--slate);
        color: #fff;
        padding: 0.25rem 0.5rem;
        border-radius: var(--radius-sm);
        font-size: var(--text-xs);
        font-weight: var(--weight-semibold);
        white-space: nowrap;
        pointer-events: none;
        box-shadow: var(--shadow-md);
        z-index: 1165;
    }
}

.sidebar-nav-items {
    flex: 1;
    padding: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    overflow-y: auto;
}

.sidebar-nav-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0.5625rem var(--space-3);
    border-radius: var(--radius-md);
    color: var(--muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    cursor: pointer;
    transition: background var(--anim-fast) var(--easing), color var(--anim-fast) var(--easing), transform var(--anim-fast) var(--easing);
    text-decoration: none;
    white-space: nowrap;
    border: none;
    background: none;
    width: 100%;
    text-align: left;
    font-family: var(--font-body);
    position: relative;
}
.sidebar-nav-item:hover {
    background: var(--bg-hover);
    color: var(--text);
}
.sidebar-nav-item.active {
    background: var(--bg-active);
    color: var(--gold-dark);
    font-weight: var(--weight-semibold);
}
.sidebar-nav-item.active::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.375rem;
    bottom: 0.375rem;
    width: 0.1875rem;
    background: var(--gold);
    border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
    animation: scaleIn var(--anim-normal) var(--easing);
}
.sidebar-nav-item.active .sidebar-nav-icon { color: var(--gold); }

.sidebar-nav-icon {
    width: 1.375rem;
    height: 1.375rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: currentColor;
    opacity: 0.62;
    transition: opacity var(--anim-fast) var(--easing),
                color var(--anim-fast) var(--easing),
                transform var(--anim-fast) var(--easing);
}
.sidebar-nav-icon svg { width: 100%; height: 100%; }
.sidebar-nav-item.active .sidebar-nav-icon { opacity: 1; color: var(--gold); }
.sidebar-nav-item:hover .sidebar-nav-icon { opacity: 1; }

.sidebar-nav-label {
    flex: 1;
    overflow: hidden;
    white-space: nowrap;
    transition: opacity var(--anim-normal, 0.25s) ease;
}

.sidebar-nav-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 1rem;
    min-height: 1rem;
    padding: 0 0.375rem;
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: 0.625rem;
    font-weight: var(--weight-semibold);
    line-height: 1;
    vertical-align: middle;
    background: var(--bg-subtle);
    color: var(--muted);
    flex-shrink: 0;
}
.sidebar-nav-item.active .sidebar-nav-badge {
    background: var(--gold-muted);
    color: var(--gold-dark);
}

.sidebar-nav-divider {
    height: 0.0625rem;
    background: var(--border-light);
    margin: 0.375rem 0.5rem;
}

.sidebar-nav-section {
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--faint);
    padding: 0.75rem 0.75rem 0.25rem;
}

/* Sidebar footer */
.sidebar-nav-footer {
    padding: 0.75rem;
    border-top: 0.0625rem solid var(--border-light);
}
.sidebar-nav-footer-item {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--faint);
    padding: 0.25rem 0.5rem;
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
    text-decoration: none;
    letter-spacing: 0;
    line-height: 1.4;
    transition: color var(--transition);
}
.sidebar-nav-footer-item:hover,
.sidebar-nav-footer-item:focus-visible { color: var(--gold); outline: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   MAIN CONTENT AREA
   ═══════════════════════════════════════════════════════════════════════════ */

.app-layout {
    display: flex;
    height: 100vh;
    /* Notch-safe offset. The fixed `.top-nav` grows by env(safe-area-inset-top)
       on devices with a display cutout (iPhone 13+, Android notched phones).
       Padding-top here has to include that same inset or the content below
       the nav - the "Lead Lists" row on /search, the saved-lists header,
       the sidebar pinned items - gets eaten by the notch at its top edge. */
    padding-top: calc(var(--top-nav-h) + env(safe-area-inset-top, 0rem));
}

.main-content {
    /* Content is pushed right by --sidebar-gap, which is set from the
       body's sidebar-mode class (sb-drawer=0, sb-rail=rail-w, sb-full=
       full-w - see SIDEBAR NAVIGATION block above). */
    margin-left: var(--sidebar-gap, 0);
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    /* Mirror `.app-layout`'s notch-safe padding so .main-content's scroll
       area starts exactly at the bottom edge of the fixed nav. Without
       the env() term, the top `safe-top` pixels sit behind the nav and
       clip whatever section header landed at y=0 (Lead Lists on /search
       was the visible victim). */
    height: calc(100vh - var(--top-nav-h) - env(safe-area-inset-top, 0rem));
    background: var(--cream);
    transition: margin-left var(--anim-normal) var(--easing);
}

.main-content.full-width { margin-left: var(--sidebar-gap, 0); }

/* Page containers */
.page { padding: var(--space-6) var(--space-8) var(--space-12); max-width: 87.5rem; margin: 0 auto; }
.page-wide { padding: var(--space-6) var(--space-8) var(--space-12); }
.page-full { padding: 0; height: 100%; }

/* Breadcrumb trail - used on every page to show hierarchy.
   Generated by UI.Breadcrumb() or inline HTML on static pages.
   Uses Schema.org BreadcrumbList markup for SEO. */
.breadcrumb {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--faint);
    margin-bottom: var(--space-4);
    flex-wrap: wrap;
    line-height: var(--leading-normal);
}
.breadcrumb a {
    color: var(--muted);
    text-decoration: none;
    transition: color var(--anim-fast) var(--easing);
    cursor: pointer;
}
.breadcrumb a:hover { color: var(--gold-dark); }
.breadcrumb a:focus-visible { outline: none; box-shadow: var(--shadow-gold); border-radius: var(--radius-xs); }
.breadcrumb-sep {
    color: var(--faint);
    opacity: 0.5;
    font-size: 0.625rem;
    user-select: none;
}
.breadcrumb-current {
    color: var(--text-secondary);
    font-weight: var(--weight-semibold);
}

/* Page header */
.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: var(--space-6);
    gap: var(--space-4);
    flex-wrap: wrap;
}
.page-title {
    font-family: var(--font-display);
    font-size: 1.5rem;
    font-weight: var(--weight-bold);
    color: var(--slate);
    letter-spacing: -0.01em;
    line-height: var(--leading-tight);
}
.page-subtitle {
    font-size: 0.875rem;
    color: var(--muted);
    margin-top: var(--space-1);
    line-height: var(--leading-normal);
}
.page-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-shrink: 0;
    /* Wrap so a four-button toolbar (Export / Print / Save / Search) on
       a narrow tablet doesn't push past the page-header's right edge. */
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BUTTONS
   ═══════════════════════════════════════════════════════════════════════════ */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    /* Height-locked instead of padding-derived so every button in a row
       aligns to the same baseline regardless of glyph or font weight. */
    height: var(--control-h);
    min-height: var(--control-h);
    padding: 0 var(--control-px);
    border-radius: var(--radius-md);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    line-height: 1;
    cursor: pointer;
    transition: background var(--anim-fast) var(--easing),
                border-color var(--anim-fast) var(--easing),
                color var(--anim-fast) var(--easing),
                box-shadow var(--anim-fast) var(--easing),
                transform 80ms var(--easing);
    border: 0.0625rem solid transparent;
    white-space: nowrap;
    text-decoration: none;
    /* Stop iOS Safari from adding tap-highlight artifacts. */
    -webkit-tap-highlight-color: transparent;
}
.btn:focus-visible {
    outline: none;
    box-shadow: var(--shadow-gold);
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-primary {
    background: var(--gold);
    color: var(--slate);
    border-color: var(--gold);
}
.btn-primary:hover:not(:disabled) {
    background: var(--gold-light);
    border-color: var(--gold-light);
}
.btn-primary:active:not(:disabled) { box-shadow: var(--shadow-inset); }

.btn-secondary {
    background: var(--warm-white);
    color: var(--text);
    border-color: var(--border);
}
.btn-secondary:hover:not(:disabled) {
    border-color: var(--gold);
    color: var(--gold-dark);
    background: var(--white);
}
.btn-secondary:active:not(:disabled) { box-shadow: var(--shadow-inset); }

.btn-ghost {
    background: transparent;
    color: var(--muted);
    border-color: transparent;
}
.btn-ghost:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); }
.btn-ghost:active:not(:disabled) { background: var(--border-light); }

.btn-danger {
    background: var(--error);
    color: #fff;
    border-color: var(--error);
}
.btn-danger:hover:not(:disabled) { background: var(--error-dark, #b71c1c); }
.btn-danger:active:not(:disabled) { box-shadow: var(--shadow-inset); }

.btn-sm {
    height: var(--control-h-sm);
    min-height: var(--control-h-sm);
    padding: 0 var(--control-px-sm);
    font-size: var(--text-xs);
    gap: 0.3125rem;
}
.btn-lg {
    height: var(--control-h-lg);
    min-height: var(--control-h-lg);
    padding: 0 var(--control-px-lg);
    font-size: var(--text-base);
}
.btn-xs {
    height: var(--control-h-xs);
    min-height: var(--control-h-xs);
    padding: 0 var(--control-px-xs);
    font-size: var(--text-xs);
    gap: 0.25rem;
}

.btn-icon {
    /* Square aspect - width matches the control height so an icon button
       slots into a row of text buttons without breaking the rhythm. */
    width: var(--control-h);
    height: var(--control-h);
    min-height: var(--control-h);
    padding: 0;
    border-radius: var(--radius-md);
}
.btn-icon.btn-sm {
    width: var(--control-h-sm);
    height: var(--control-h-sm);
    min-height: var(--control-h-sm);
}
.btn-icon.btn-xs {
    width: var(--control-h-xs);
    height: var(--control-h-xs);
    min-height: var(--control-h-xs);
}
.btn-icon.btn-lg {
    width: var(--control-h-lg);
    height: var(--control-h-lg);
    min-height: var(--control-h-lg);
}

/* Button group - multiple buttons that should read as a single segmented
   control. The wrapper gives each child square inner edges and forces a
   consistent gap so the row never has stray hairlines. */
.btn-group {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.btn-group .btn { flex-shrink: 0; }
.btn-group--tight { gap: 0.25rem; }

/* Full-width button - takes 100% of parent. Used in forms, cards, and
   stacked button layouts. Replaces inline style="width:100%". */
.btn-block {
    display: flex;
    width: 100%;
    justify-content: center;
}

/* Close/dismiss button - for modal close, panel dismiss, drawer close.
   Replaces inline-styled close buttons with no class. */
.btn-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: none;
    background: none;
    color: var(--muted);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    transition: color var(--transition), background var(--transition);
}
.btn-close:hover { color: var(--text); background: var(--bg-hover); }
.btn-close:focus-visible { box-shadow: var(--shadow-gold); }

/* ═══════════════════════════════════════════════════════════════════════════
   CARDS
   ═══════════════════════════════════════════════════════════════════════════ */

.card {
    background: var(--warm-white);
    border: 0.0625rem solid var(--border-light);
    border-radius: var(--radius-lg);
    transition: border-color var(--anim-fast) var(--easing),
                box-shadow var(--anim-fast) var(--easing);
    /* Cards in a grid should fill the row height so adjacent siblings
       end at the same y. Without this a 3-line card next to a 5-line
       card creates an uneven bottom edge. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.card:hover { border-color: var(--gold-border); box-shadow: var(--shadow-card-hover); }
.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Header / body / footer share the same horizontal padding (--space-5)
       so labels and content stay vertically aligned through the card.
       Vertical padding tightens slightly on the header so the card title
       doesn't float over the body. */
    padding: var(--space-4) var(--space-5);
    border-bottom: 0.0625rem solid var(--border-light);
    gap: var(--space-3);
    /* Lock a minimum height so an empty header still occupies the same
       vertical real estate as one with a button - keeps a row of cards
       in a grid lined up across the top. */
    min-height: 3rem;
    flex-shrink: 0;
}
.card-title {
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: var(--weight-bold);
    color: var(--slate);
    line-height: var(--leading-tight);
    letter-spacing: -0.005em;
    /* Allow the title to truncate if it overflows instead of pushing
       buttons out of the header on narrow screens. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.card-body {
    padding: var(--space-5);
    /* Body grows to fill the card so the footer always sticks to the
       bottom - required for equal-height card rows. */
    flex: 1 1 auto;
    min-width: 0;
}
.card-body-compact { padding: var(--space-3) var(--space-5); }
.card-footer {
    padding: var(--space-3) var(--space-5);
    border-top: 0.0625rem solid var(--border-light);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    flex-shrink: 0;
}
/* Right-only footer (the common case for "Save" / "Submit" buttons). */
.card-footer--right { justify-content: flex-end; }

/* Stat cards */
.stat-card {
    background: var(--warm-white);
    border: 0.0625rem solid var(--border-light);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    box-shadow: var(--shadow-card);
    transition: border-color var(--transition), box-shadow var(--transition);
}
.stat-card:hover { border-color: var(--gold-border); box-shadow: var(--shadow-card-hover); }
.stat-card-label {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    margin-bottom: 0.375rem;
}
.stat-card-value {
    font-family: var(--font-mono);
    font-size: 1.625rem;
    font-weight: var(--weight-medium);
    color: var(--slate);
    line-height: var(--leading-tight);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.stat-card-trend {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    margin-top: 0.375rem;
}
.stat-card-trend.up { color: var(--success); }
.stat-card-trend.down { color: var(--error); }
.stat-card-trend.neutral { color: var(--muted); }
.stat-card-sub {
    font-size: 0.75rem;
    color: var(--faint);
    margin-top: 0.25rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   GRID LAYOUTS
   ═══════════════════════════════════════════════════════════════════════════ */

.grid {
    display: grid;
    gap: var(--space-4);
    /* Equal-height children - required so a card row never has staggered
       bottoms. */
    grid-auto-rows: 1fr;
}
.grid > * { min-width: 0; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid-auto { grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); }

/* ── Analytics KPI grid - auto-fit tiles that never truncate ──
   The old fixed `grid-5` forced 5 equal columns on desktop, which
   clipped "$3,690,686,200" with ellipsis inside each tile. Using
   auto-fit + a minmax(clamped to tile width) means each tile widens
   to hold its content, wraps to the next row when short on space,
   and collapses to 1 column on phones - no media queries needed. */
.analytics-kpi-grid {
    display: grid;
    /* Tiles flex between 10rem and 1fr. auto-fit packs as many as fit
       on a row and the rest wrap - no fixed 5/4/3 breakpoints, the
       layout decides itself based on viewport + content width. */
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    gap: 0.625rem;
}
.analytics-kpi-grid--compact {
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: 0.5rem;
}
.analytics-kpi-grid .stat-card {
    /* min-width:0 lets grid items shrink past their content's intrinsic
       width - the value below caps itself with a clamp+nowrap so the
       tile never runs off-screen. Padding in rem = zoom-clean. */
    min-width: 0;
    padding: 0.875rem 1rem;
}
.analytics-kpi-grid .stat-card-value {
    /* Proportional font (not monospace) so long totals like
       "$3,690,686,200" are ~30% narrower and fit even the tightest
       tile. Font-size scales with viewport via clamp(), and tabular-
       nums keeps digit columns aligned. */
    font-family: var(--font-body, 'Source Sans 3', sans-serif);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.015em;
    font-size: clamp(1rem, 1.4vw + 0.4rem, 1.5rem);
    line-height: 1.15;
    /* Keep numbers on one line - the clamp() above guarantees they
       fit. Hidden+ellipsis is a last-ditch safety net; at any sensible
       viewport it shouldn't engage. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.analytics-kpi-grid .stat-card-value--range {
    font-size: clamp(0.75rem, 0.9vw + 0.4rem, 0.9375rem);
    letter-spacing: 0;
    white-space: normal;      /* a range "$X – $Y" is OK to wrap */
    overflow: visible;
    text-overflow: clip;
}
.analytics-kpi-grid .stat-card-label {
    white-space: normal;
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    line-height: 1.25;
}
.analytics-kpi-grid .stat-card-sub {
    font-size: 0.75rem;
    white-space: normal;
}

/* ── County Reports card rhythm ──
   Every top-level card on the analytics page gets the same breathing
   room. Previously the headings sat flush against the card above,
   making the page read as a single dense wall of tiles. */
#analytics-content > .card,
#analytics-content > .analytics-kpi-grid {
    margin-bottom: 1.25rem;
}
#analytics-content > .card + .card { margin-top: 0; }

/* ── Loading quip banner (analytics skeleton) ──
   Real-estate-themed one-liners that rotate every 4.5s while the
   county data is loading. Framed in a gold-trimmed strip so the user
   can tell it's intentional branding, not an error message. */
.analytics-loading-quip {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 1rem;
    color: #4a4842;
    background: linear-gradient(90deg, #fdfbf6 0%, #fffdf2 100%);
    border: 0.0625rem solid #e6e1d7;
    border-left: 3pt solid var(--gold, #b5942a);
    padding: 0.75rem 1rem;
    border-radius: 0.375rem;
    margin-bottom: 1rem;
    min-height: 2.5rem;
    display: flex;
    align-items: center;
    line-height: 1.3;
    animation: quipFade 500ms ease;
}
@keyframes quipFade {
    from { opacity: 0; transform: translateY(-0.125rem); }
    to   { opacity: 1; transform: translateY(0); }
}
@media (max-width: 32em) {
    .analytics-loading-quip {
        font-size: 0.9375rem;
        padding: 0.625rem 0.875rem;
    }
}

/* ════════════════════════════════════════════════════════════════════════
   MOUNTAIN LOADER - monochrome line-art animation
   ════════════════════════════════════════════════════════════════════════
   A tiny slate figure walks across a faint slate ridge whose vertices
   match the Blue Belmont favicon path exactly. NO COLOR DECORATION -
   only slate strokes, white snow, and grayscale opacity steps. Every
   visible motion is a robust, lifelike animation: limb articulation,
   body bounce, foot shadow, breath, snow particles, fall + ski poses,
   victory celebration. The decorative scenery (sun, sky, lake, trees,
   clouds, birds, fireworks) was removed at user request - what's left
   is the climber and his journey, period. */
.bb-mountain-loader {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.625rem;
    padding: 1.5rem 1.25rem 1.25rem;
    margin-bottom: 1rem;
    /* Matches .dash-metric-card / .skeleton-card-block so the loader
       reads as part of the same dashboard surface, not a separate
       white panel floating above. */
    background: var(--surface-card);
    border: 0.0625rem solid var(--border-light);
    border-radius: var(--radius-lg, 0.5rem);
    box-shadow: var(--shadow-card);
    text-align: center;
    overflow: hidden;
}
/* The stage wraps the SVG so HTML popups can position absolutely
   within the same coordinate space as the painted scene. The stage's
   width tracks the SVG (which fills 100 % up to its max-width), and
   pointer events stay on the loader proper - the popups are decorative,
   so they're explicitly aria-hidden + pointer-events: none. */
.bb-mountain-stage {
    position: relative;
    width: 100%;
    max-width: 28rem;
    aspect-ratio: 320 / 200;
}
.bb-mountain-svg {
    display: block;
    width: 100%;
    height: 100%;
    /* The climber's head sits a few units above the ridge in user
       coordinates; without `overflow: visible` the SVG would clip its
       top edge on first render. */
    overflow: visible;
}
.bb-ridge {
    fill: none;
    stroke-linejoin: round;
    stroke-linecap: round;
}
.bb-ridge-shadow {
    /* Soft slate underglow so the ridge has weight on a cream card. */
    stroke: rgba(28, 35, 49, 0.07);
    stroke-width: 7;
    transform: translateY(0.0625rem);
}
.bb-ridge-base {
    /* Faint slate ghost - the path the climber hasn't walked yet. */
    stroke: rgba(28, 35, 49, 0.18);
    stroke-width: 3;
}
.bb-ridge-trail {
    /* Slate trail painted behind the climber as they progress.
       pathLength=100 on the path normalizes the dasharray math so this
       7 s draw stays in lockstep with the 7 s animateMotion on the
       climber regardless of the ridge's actual geometric length. */
    stroke: var(--slate, #1c2331);
    stroke-width: 3;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: bb-ridge-trail 7s linear infinite;
}
@keyframes bb-ridge-trail {
    from { stroke-dashoffset: 100; }
    to   { stroke-dashoffset: 0; }
}
/* ── Climber anatomy - slate stick figure, gold backpack ──
   The climber is intentionally larger than a generic spinner glyph
   (~20 user units tall on a 200-tall canvas, which renders ~6–10 mm
   on a typical screen) so the user can actually read the gait. The
   stroke widths are tuned so the limb-thin arms read as lighter than
   the leg lines without disappearing on a low-DPI display. */
.bb-climber-head {
    fill: var(--slate, #1c2331);
    /* Soft outer glow so the head pops off the cream card. */
    filter: drop-shadow(0 0.0625rem 0.125rem rgba(28, 35, 49, 0.18));
}
.bb-climber-brim {
    /* The thin line through the head reads as a hiker's hat brim and
       gives the silhouette unmistakable "trekker" identity at small
       sizes where individual features would otherwise mush together. */
    stroke: rgba(255, 255, 255, 0.95);
    stroke-width: 1.4;
    stroke-linecap: round;
}
.bb-climber-body {
    stroke: var(--slate, #1c2331);
    stroke-width: 2;
    stroke-linecap: round;
    fill: none;
}
.bb-climber-limb {
    stroke: var(--slate, #1c2331);
    stroke-width: 1.85;
    stroke-linecap: round;
    fill: none;
}
.bb-climber-limb-thin {
    stroke: var(--slate, #1c2331);
    stroke-width: 1.55;
    stroke-linecap: round;
    fill: none;
}
.bb-climber-pack {
    fill: var(--slate, #1c2331);
    stroke: rgba(255, 255, 255, 0.4);
    stroke-width: 0.4;
}
.bb-climber-strap {
    stroke: rgba(255, 255, 255, 0.6);
    stroke-width: 0.6;
    stroke-linecap: round;
}

/* Body bounce - synced with the SMIL stride cadence (0.6s per stride).
   Two rises per stride (peaks at 25% and 75%) so the body lifts each
   time a foot plants. Amplitude 0.9 user units = ~1.5–2 screen px on
   typical displays - visible but not theatrical. The bounce is
   asymmetric (only above baseline) to give the gait an "uplift"
   feeling instead of a wobble. */
.bb-climber-step {
    animation: bb-climber-bounce 0.6s ease-in-out infinite;
}
@keyframes bb-climber-bounce {
    0%, 50%, 100% { transform: translateY(0); }
    25%, 75%      { transform: translateY(-0.9px); }
}

/* Trekking pole - a slim graphite line continuing from each hand to
   the path. Lighter weight than the limbs so the pole reads as a
   secondary detail, not a third leg. */
.bb-climber-pole {
    stroke: rgba(28, 35, 49, 0.55);
    stroke-width: 0.65;
    stroke-linecap: round;
    fill: none;
}

/* Breath puff - two staggered white circles that drift up and away
   from the climber's mouth on every inhale/exhale. Two puffs at slight
   offsets create the parallax illusion of breath dispersing. */
.bb-breath {
    fill: rgba(255, 255, 255, 0.85);
    opacity: 0;
}
.bb-breath-1 {
    animation: bb-breath-puff 1.7s ease-out infinite;
}
.bb-breath-2 {
    animation: bb-breath-puff 1.7s ease-out infinite;
    animation-delay: 0.4s;
}
@keyframes bb-breath-puff {
    0%   { opacity: 0; transform: translate(0, 0) scale(0.5); }
    25%  { opacity: 0.85; }
    100% { opacity: 0; transform: translate(4px, -4px) scale(1.6); }
}

/* ── Ski sequence ──
   When the climber transitions to the skiing pose, two slate skis
   appear under the feet, the body leans forward, and white snow
   sprays out behind. */
.bb-ski {
    stroke: var(--slate, #1c2331);
    stroke-width: 1.4;
    stroke-linecap: round;
    fill: none;
}
.bb-ski-shadow {
    stroke: rgba(28, 35, 49, 0.22);
    stroke-width: 1.2;
    stroke-linecap: round;
    fill: none;
}
.bb-spray {
    fill: rgba(255, 255, 255, 0.92);
    animation: bb-spray-puff 0.6s ease-out infinite;
}
.bb-spray-1 { animation-delay: 0s; }
.bb-spray-2 { animation-delay: 0.18s; }
.bb-spray-3 { animation-delay: 0.36s; }
@keyframes bb-spray-puff {
    0%   { opacity: 0;   transform: translate(2px, 0)  scale(0.5); }
    20%  { opacity: 0.9; }
    100% { opacity: 0;   transform: translate(-6px, -1px) scale(1.7); }
}

/* ── Tumble snow puff - kicked up at the moment of the fall ──
   Three small white puffs that explode outward when the climber
   stumbles. The 0.42 s tumble window is short, but these puffs sell
   the impact moment. */
.bb-snow-puff {
    fill: rgba(255, 255, 255, 0.95);
    stroke-width: 0;
    animation: bb-snow-puff-explode 0.42s ease-out infinite;
}
.bb-snow-puff-a { animation-delay: 0s; }
.bb-snow-puff-b { animation-delay: 0.05s; }
.bb-snow-puff-c { animation-delay: 0.10s; }
@keyframes bb-snow-puff-explode {
    0%   { opacity: 0;   transform: scale(0.4); }
    30%  { opacity: 1; }
    100% { opacity: 0;   transform: scale(2.2); }
}

/* ── Falling snow ──
   Twelve flakes drift continuously top-to-bottom across the canvas.
   Each has its own duration and negative delay so they enter at
   different y-positions, producing a steady, natural snowfall instead
   of a synchronized curtain. The flakes use translateY in user units
   (1 px = 1 user unit inside an SVG viewBox), so the values are
   relative to the 200-tall canvas - a flake travels from y=−10 to
   y=215, fully clearing the panel. */
.bb-snow {
    pointer-events: none;
}
.bb-flake {
    fill: rgba(255, 255, 255, 0.85);
    stroke: rgba(28, 35, 49, 0.18);
    stroke-width: 0.18;
    animation: bb-snow-fall var(--bb-flake-dur, 6s) linear infinite;
    animation-delay: var(--bb-flake-delay, 0s);
}
@keyframes bb-snow-fall {
    0%   { transform: translateY(-15px) translateX(0);    opacity: 0;   }
    8%   { opacity: 1; }
    50%  { transform: translateY(95px)  translateX(2px); }
    92%  { opacity: 0.85; }
    100% { transform: translateY(215px) translateX(-3px); opacity: 0;   }
}
.bb-flake-1  { --bb-flake-dur: 6.2s;  --bb-flake-delay: 0s;    }
.bb-flake-2  { --bb-flake-dur: 7.5s;  --bb-flake-delay: -0.8s; }
.bb-flake-3  { --bb-flake-dur: 5.8s;  --bb-flake-delay: -2.2s; }
.bb-flake-4  { --bb-flake-dur: 8.0s;  --bb-flake-delay: -1.5s; }
.bb-flake-5  { --bb-flake-dur: 6.6s;  --bb-flake-delay: -3.0s; }
.bb-flake-6  { --bb-flake-dur: 7.2s;  --bb-flake-delay: -0.4s; }
.bb-flake-7  { --bb-flake-dur: 5.4s;  --bb-flake-delay: -2.6s; }
.bb-flake-8  { --bb-flake-dur: 7.8s;  --bb-flake-delay: -1.0s; }
.bb-flake-9  { --bb-flake-dur: 6.0s;  --bb-flake-delay: -3.4s; }
.bb-flake-10 { --bb-flake-dur: 8.5s;  --bb-flake-delay: -0.2s; }
.bb-flake-11 { --bb-flake-dur: 5.6s;  --bb-flake-delay: -1.8s; }
.bb-flake-12 { --bb-flake-dur: 7.0s;  --bb-flake-delay: -2.4s; }

/* ── Snow caps ──
   White triangular drifts hugging each peak - purely natural snow on
   the mountains. Slate rim ties them to the ridge stroke. */
.bb-snowcap {
    fill: rgba(255, 255, 255, 0.96);
    stroke: rgba(28, 35, 49, 0.45);
    stroke-width: 0.5;
    stroke-linejoin: round;
}

/* ── Climber shadow ──
   Stationary ellipse on the ground beneath the walker that pulses
   with the stride: smaller + darker when the foot plants (mid-stride),
   larger + lighter when one foot is in the air (extreme stride). The
   pulse cadence (0.6 s) matches the SMIL leg rotation and CSS bounce,
   so all three motion layers feel mechanically tied together - what
   real walking looks like. Lives outside bb-climber-step so it stays
   on the ground while the body bounces above. */
.bb-climber-shadow {
    fill: rgba(28, 35, 49, 0.22);
    transform-box: fill-box;
    transform-origin: center;
    animation: bb-climber-shadow 0.6s ease-in-out infinite;
}
@keyframes bb-climber-shadow {
    0%, 50%, 100% { transform: scaleX(1)   scaleY(1);   opacity: 0.22; }
    25%, 75%      { transform: scaleX(0.8) scaleY(0.7); opacity: 0.32; }
}

/* ── Finish flag ──
   Slate triangular pennant on a thin pole at the right base of the
   path. The flag flutters gently via skew animation. */
.bb-finish-flag > path {
    fill: var(--slate, #1c2331);
    stroke-width: 0;
    transform-origin: 295px 161px;
    animation: bb-flag-flutter 1.4s ease-in-out infinite;
}
@keyframes bb-flag-flutter {
    0%, 100% { transform: skewX(0deg)   scaleX(1);    }
    50%      { transform: skewX(-7deg)  scaleX(0.93); }
}

/* ── Confetti ──
   Twelve slate chips that burst outward from the finish line on a 1.2 s
   loop. The parent .bb-confetti's SMIL opacity gates them so they're
   only visible during the last 5 % of the climber's loop - exactly when
   he crosses the finish. Each chip uses CSS variables for its target
   (x, y, rotation) end-point and animation-delay so the keyframe is
   shared but the trajectories are unique. */
.bb-confetti-piece {
    fill: var(--slate, #1c2331);
    transform-origin: center;
    animation: bb-confetti-fly 1.2s ease-out infinite;
}
.bb-confetti-1  { --bb-conf-x:  -22px; --bb-conf-y: -28px; --bb-conf-r: -540deg; animation-delay: 0s;    }
.bb-confetti-2  { --bb-conf-x:   18px; --bb-conf-y: -24px; --bb-conf-r:  420deg; animation-delay: 0.05s; }
.bb-confetti-3  { --bb-conf-x:  -14px; --bb-conf-y: -32px; --bb-conf-r: -360deg; animation-delay: 0.10s; }
.bb-confetti-4  { --bb-conf-x:   26px; --bb-conf-y: -18px; --bb-conf-r:  600deg; animation-delay: 0.02s; }
.bb-confetti-5  { --bb-conf-x:   -8px; --bb-conf-y: -36px; --bb-conf-r: -480deg; animation-delay: 0.18s; }
.bb-confetti-6  { --bb-conf-x:   12px; --bb-conf-y: -30px; --bb-conf-r:  540deg; animation-delay: 0.08s; }
.bb-confetti-7  { --bb-conf-x:  -26px; --bb-conf-y: -20px; --bb-conf-r: -420deg; animation-delay: 0.22s; }
.bb-confetti-8  { --bb-conf-x:    6px; --bb-conf-y: -34px; --bb-conf-r:  480deg; animation-delay: 0.13s; }
.bb-confetti-9  { --bb-conf-x:  -20px; --bb-conf-y: -24px; --bb-conf-r: -600deg; animation-delay: 0.27s; }
.bb-confetti-10 { --bb-conf-x:   22px; --bb-conf-y: -26px; --bb-conf-r:  360deg; animation-delay: 0.04s; }
.bb-confetti-11 { --bb-conf-x:    0px; --bb-conf-y: -38px; --bb-conf-r: -540deg; animation-delay: 0.16s; }
.bb-confetti-12 { --bb-conf-x:   30px; --bb-conf-y: -22px; --bb-conf-r:  420deg; animation-delay: 0.20s; }
@keyframes bb-confetti-fly {
    0%   { transform: translate(0, 0)            rotate(0deg);            opacity: 1; }
    60%  {                                                                opacity: 1; }
    100% { transform: translate(var(--bb-conf-x), var(--bb-conf-y)) rotate(var(--bb-conf-r)); opacity: 0; }
}

/* ── Victory pose ──
   Static at (295, 175). Body is the standard climber silhouette but
   with arms in a V and a gold medal hanging from a red ribbon over
   the chest. The figure does a soft "celebrate" bounce + scale on
   the brief window it's visible. */
.bb-victory {
    transform-box: fill-box;
    transform-origin: center;
    animation: bb-victory-cheer 0.7s ease-in-out infinite;
}
.bb-victory-figure { /* anchor for inner cheer transforms */ }
.bb-medal {
    fill: var(--slate, #1c2331);
    stroke: rgba(255, 255, 255, 0.7);
    stroke-width: 0.35;
}
.bb-medal-ribbon {
    stroke: rgba(28, 35, 49, 0.7);
    stroke-width: 0.7;
    stroke-linecap: round;
}
.bb-medal-star {
    font-size: 0.1625rem;
    font-weight: 700;
    fill: rgba(255, 255, 255, 0.92);
    paint-order: stroke;
}
@keyframes bb-victory-cheer {
    0%, 100% { transform: translate(0, 0)    scale(1); }
    50%      { transform: translate(0, -1px) scale(1.04); }
}

/* ── Sparkles ──
   Seven tiny slate dots ringing the celebrating climber's head. Each
   pulses on a different cycle for a "shimmer" effect. */
.bb-sparkle {
    fill: var(--slate, #1c2331);
    animation: bb-sparkle-pulse 1s ease-in-out infinite;
}
.bb-sparkle-1 { animation-delay: 0s;    }
.bb-sparkle-2 { animation-delay: 0.13s; }
.bb-sparkle-3 { animation-delay: 0.26s; }
.bb-sparkle-4 { animation-delay: 0.39s; }
.bb-sparkle-5 { animation-delay: 0.52s; }
.bb-sparkle-6 { animation-delay: 0.65s; }
.bb-sparkle-7 { animation-delay: 0.78s; }
@keyframes bb-sparkle-pulse {
    0%, 100% { opacity: 0.3; transform: scale(0.6); }
    50%      { opacity: 1;   transform: scale(1.4); }
}

/* ── Speech-bubble popups ──
   Four floating quip cards positioned at the corners of the stage.
   Each fades in/out on a 7 s loop with staggered delays so at least
   one is always on screen. The little tail arrow is a square with
   a 45° rotation, half-tucked behind the bubble.

   The popups are decorative + redundant with the main quip strip
   below the SVG, so they're aria-hidden - screen readers get the
   primary status text without an avalanche of repeated content. */
.bb-popup-cloud {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: visible;
}
.bb-popup {
    position: absolute;
    background: rgba(255, 255, 255, 0.97);
    border: 0.0625rem solid rgba(28, 35, 49, 0.45);
    border-radius: 0.5rem;
    padding: 0.375rem 0.625rem;
    font-family: 'DM Mono', 'Source Code Pro', ui-monospace, Menlo, monospace;
    font-size: 0.6875rem;
    line-height: 1.3;
    color: var(--slate, #1c2331);
    max-width: 11rem;
    min-width: 5rem;
    box-shadow: 0 0.125rem 0.5rem rgba(28, 35, 49, 0.12);
    opacity: 0;
    transform: translateY(0.5rem) scale(0.94);
    animation: bb-popup-bubble 7s ease-in-out infinite;
}
.bb-popup-text {
    /* No truncation - the full joke must always be visible. The bubble
       expands its width up to its max-width and then wraps to as many
       lines as needed. */
    display: block;
    overflow: visible;
    word-break: normal;
    overflow-wrap: anywhere;
}
/* Single popup, centered above the scene. The user explicitly asked
   for ONE bubble at a time - was four corners which felt cluttered. */
.bb-popup-1 {
    left: 50%;
    top: 4%;
    transform-origin: center;
    /* Translate -50% on X so the "centered" anchor is the bubble's
       midpoint, not its left edge. Combined with the keyframe's
       translateY+scale, the bubble fades in from below center, holds,
       and fades out upward. */
    --bb-popup-x: -50%;
    animation-delay: 0s;
    /* Wide enough that 90 % of jokes fit on two lines; long ones
       wrap to three or four without ever truncating. */
    max-width: 22rem;
    width: max-content;
}
@keyframes bb-popup-bubble {
    0%, 100% { opacity: 0; transform: translate(var(--bb-popup-x, 0), 0.5rem)  scale(0.92); }
    6%       { opacity: 1; transform: translate(var(--bb-popup-x, 0), 0)        scale(1);    }
    72%      { opacity: 1; transform: translate(var(--bb-popup-x, 0), 0)        scale(1);    }
    80%      { opacity: 0; transform: translate(var(--bb-popup-x, 0), -0.4rem)  scale(0.95); }
}
@media (max-width: 32em) {
    .bb-popup { font-size: 0.625rem; padding: 0.3rem 0.5rem; max-width: 11rem; }
}
.bb-mountain-quip {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 1rem;
    color: #4a4842;
    line-height: 1.35;
    letter-spacing: -0.005em;
    transition: opacity 220ms ease;
    max-width: 28rem;
    min-height: 1.4em;
}
@media (max-width: 32em) {
    .bb-mountain-loader {
        padding: 1.125rem 0.875rem 1rem;
        gap: 0.5rem;
    }
    .bb-mountain-svg { max-width: 100%; }
    .bb-mountain-quip { font-size: 0.9375rem; }
}
/* (Per-skeleton-card climber animation removed at user request - was
   distracting on a page with many small data parcels. The big
   MountainLoader at the top of the loading state is now the only
   loading-animation surface, and a behavior randomizer there cycles
   the climber through 10 different scenes.) */

/* ════════════════════════════════════════════════════════════════════════
   MOUNTAIN LOADER BEHAVIOR SCENES
   ════════════════════════════════════════════════════════════════════════
   Each behavior reuses the climber-figure styles (.bb-climber-head,
   .bb-climber-body, etc.) so its silhouette matches the brand.
   This block adds styles for the BEHAVIOR-SPECIFIC props (stars,
   pond, fish, fire, tent, book, camera, mug, snowman, etc.) plus
   the small ambient animations they each use. All slate/white only. */

/* ── Stargaze ── */
.bb-stars { pointer-events: none; }
.bb-star {
    fill: rgba(28, 35, 49, 0.7);
    animation: bb-star-twinkle 2.4s ease-in-out infinite;
}
.bb-star-1 { animation-delay: 0s;    }
.bb-star-2 { animation-delay: 0.3s;  }
.bb-star-3 { animation-delay: 0.6s;  }
.bb-star-4 { animation-delay: 0.9s;  }
.bb-star-5 { animation-delay: 1.2s;  }
.bb-star-6 { animation-delay: 0.15s; }
.bb-star-7 { animation-delay: 0.45s; }
.bb-star-8 { animation-delay: 0.75s; }
.bb-star-9 { animation-delay: 1.05s; }
.bb-star-10 { animation-delay: 1.35s; }
.bb-star-11 { animation-delay: 0.55s; }
.bb-star-12 { animation-delay: 0.85s; }
@keyframes bb-star-twinkle {
    0%, 100% { opacity: 0.3; transform: scale(0.7); }
    50%      { opacity: 1;   transform: scale(1.3); }
}
.bb-shooting-star { opacity: 0; animation: bb-shooting-star 8s linear infinite; }
@keyframes bb-shooting-star {
    0%, 90%, 100% { opacity: 0; }
    91%, 99%      { opacity: 1; }
}

/* ── Fishing ── */
.bb-pond > ellipse {
    fill: rgba(28, 35, 49, 0.18);
    stroke: rgba(28, 35, 49, 0.4);
    stroke-width: 0.4;
}
.bb-pond-shimmer {
    stroke: rgba(255, 255, 255, 0.7);
    stroke-width: 0.35;
    stroke-linecap: round;
    animation: bb-pond-shimmer 3s ease-in-out infinite;
}
.bb-pond-shimmer-2 { animation-delay: 1.5s; animation-duration: 4s; }
@keyframes bb-pond-shimmer {
    0%, 100% { opacity: 0.2; }
    50%      { opacity: 0.95; }
}
.bb-fishing-rod {
    stroke: var(--slate, #1c2331);
    stroke-width: 0.5;
    stroke-linecap: round;
}
.bb-fishing-line {
    stroke: rgba(28, 35, 49, 0.6);
    stroke-width: 0.3;
    stroke-linecap: round;
    animation: bb-fishing-line 2s ease-in-out infinite;
    transform-origin: 40px -19px;
    transform-box: fill-box;
}
@keyframes bb-fishing-line {
    0%, 100% { transform: rotate(0deg); }
    50%      { transform: rotate(2deg); }
}
.bb-fishing-bob {
    fill: var(--slate, #1c2331);
    animation: bb-fishing-bob 2s ease-in-out infinite;
}
@keyframes bb-fishing-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(0.6px); }
}
.bb-fish {
    fill: rgba(28, 35, 49, 0.75);
    opacity: 0;
    animation: bb-fish-jump 5s ease-in-out infinite;
}
@keyframes bb-fish-jump {
    0%, 70%, 100%  { opacity: 0; transform: translate(0, 0); }
    78%            { opacity: 1; transform: translate(-4px, -4px); }
    82%            { opacity: 1; transform: translate(-2px, -6px); }
    90%            { opacity: 0; transform: translate(2px, -3px); }
}

/* ── Sisyphus rock ── */
.bb-sisyphus-rock > circle {
    fill: rgba(28, 35, 49, 0.85);
    stroke: var(--slate, #1c2331);
    stroke-width: 0.4;
}

/* ── Camping ── */
.bb-tent {
    fill: rgba(28, 35, 49, 0.85);
    stroke: var(--slate, #1c2331);
    stroke-width: 0.5;
}
.bb-tent-pole, .bb-tent-flap {
    stroke: rgba(255, 255, 255, 0.5);
    stroke-width: 0.4;
    fill: none;
}
.bb-tent-flap { fill: rgba(255, 255, 255, 0.15); }
.bb-log {
    stroke: rgba(28, 35, 49, 0.7);
    stroke-width: 0.7;
    stroke-linecap: round;
}
.bb-flame {
    fill: rgba(28, 35, 49, 0.85);
    transform-origin: center bottom;
    transform-box: fill-box;
    animation: bb-flame-flicker 0.4s ease-in-out infinite alternate;
}
.bb-flame-2 {
    fill: rgba(255, 255, 255, 0.85);
    animation-duration: 0.32s;
    animation-delay: 0.1s;
}
@keyframes bb-flame-flicker {
    0%   { transform: scaleY(1)    scaleX(1);    }
    100% { transform: scaleY(1.2) scaleX(0.9); }
}
.bb-smoke-puff {
    fill: rgba(28, 35, 49, 0.18);
    animation: bb-smoke-rise 3s linear infinite;
}
.bb-smoke-1 { animation-delay: 0s;   }
.bb-smoke-2 { animation-delay: -1s;  }
.bb-smoke-3 { animation-delay: -2s;  }
@keyframes bb-smoke-rise {
    0%   { opacity: 0;   transform: translateY(0)    scale(0.6); }
    20%  { opacity: 0.5; }
    100% { opacity: 0;   transform: translateY(-12px) scale(1.6); }
}

/* ── Reading ── */
.bb-book-cover {
    fill: rgba(28, 35, 49, 0.85);
    stroke: var(--slate, #1c2331);
    stroke-width: 0.3;
}
.bb-book-spine {
    stroke: rgba(255, 255, 255, 0.6);
    stroke-width: 0.3;
}
.bb-book-page {
    fill: rgba(255, 255, 255, 0.92);
    stroke: rgba(28, 35, 49, 0.4);
    stroke-width: 0.2;
    transform-origin: 0 0;
    transform-box: fill-box;
    animation: bb-book-page-turn 4s ease-in-out infinite;
}
@keyframes bb-book-page-turn {
    0%, 70%, 100% { transform: scaleX(1); }
    80%, 90%      { transform: scaleX(-0.3); }
}

/* ── Photography ── */
.bb-camera {
    fill: rgba(28, 35, 49, 0.9);
    stroke: var(--slate, #1c2331);
    stroke-width: 0.3;
}
.bb-camera-lens {
    fill: rgba(255, 255, 255, 0.85);
    stroke: var(--slate, #1c2331);
    stroke-width: 0.3;
}
.bb-camera-flash {
    fill: rgba(255, 255, 255, 1);
    opacity: 0;
    animation: bb-camera-flash 3s ease-out infinite;
}
@keyframes bb-camera-flash {
    0%, 90%, 100% { opacity: 0;   transform: scale(0.6); }
    93%           { opacity: 1;   transform: scale(1.4); }
}

/* ── Cocoa ── */
.bb-mug {
    fill: rgba(28, 35, 49, 0.85);
    stroke: var(--slate, #1c2331);
    stroke-width: 0.3;
}
.bb-mug-handle {
    fill: none;
    stroke: var(--slate, #1c2331);
    stroke-width: 0.4;
}
.bb-steam-puff {
    fill: none;
    stroke: rgba(28, 35, 49, 0.5);
    stroke-width: 0.4;
    stroke-linecap: round;
    opacity: 0;
    animation: bb-steam-rise 2.4s ease-out infinite;
}
.bb-steam-1 { animation-delay: 0s;   }
.bb-steam-2 { animation-delay: -0.8s; }
.bb-steam-3 { animation-delay: -1.6s; }
@keyframes bb-steam-rise {
    0%   { opacity: 0;   transform: translateY(2px)  scale(0.8); }
    25%  { opacity: 0.6; }
    100% { opacity: 0;   transform: translateY(-3px) scale(1.2); }
}

/* ── Yoga ── */
.bb-breath-wave {
    fill: none;
    stroke: rgba(28, 35, 49, 0.5);
    stroke-width: 0.4;
    opacity: 0;
    animation: bb-breath-wave 4s ease-out infinite;
    transform-origin: center;
    transform-box: fill-box;
}
.bb-breath-wave-1 { animation-delay: 0s;     }
.bb-breath-wave-2 { animation-delay: -1.3s;  }
.bb-breath-wave-3 { animation-delay: -2.6s;  }
@keyframes bb-breath-wave {
    0%   { opacity: 0;   transform: scale(0.4); }
    25%  { opacity: 0.5; }
    100% { opacity: 0;   transform: scale(1.6); }
}

/* ── Snowman ── */
.bb-snowman-base, .bb-snowman-mid, .bb-snowman-head {
    fill: rgba(255, 255, 255, 0.96);
    stroke: var(--slate, #1c2331);
    stroke-width: 0.4;
}
.bb-snowman-eye  { fill: var(--slate, #1c2331); }
.bb-snowman-nose { fill: rgba(28, 35, 49, 0.65); }
.bb-snowman-arm  { stroke: rgba(28, 35, 49, 0.7); stroke-width: 0.5; stroke-linecap: round; }
.bb-snowman-hat  { fill: var(--slate, #1c2331); }
.bb-snowman-hat-brim {
    stroke: var(--slate, #1c2331);
    stroke-width: 0.5;
    stroke-linecap: round;
}
.bb-snowman-arm-pat line {
    stroke: var(--slate, #1c2331);
    stroke-width: 0.5;
    stroke-linecap: round;
}

@media (prefers-reduced-motion: reduce) {
    .bb-ridge-trail,
    .bb-climber-step,
    .bb-climber-shadow,
    .bb-flake,
    .bb-breath-1, .bb-breath-2,
    .bb-spray, .bb-snow-puff,
    .bb-finish-flag > path,
    .bb-confetti-piece,
    .bb-victory,
    .bb-sparkle,
    .bb-popup,
    .bb-skeleton-bars,
    .bb-box-mountain,
    .bb-box-ridge-trail,
    .bb-box-climber {
        animation: none !important;
    }
    /* Reduced-motion fallback: keep skeleton bars at full opacity, hide
       the mountain entirely so the loading screen stays calm. */
    .bb-box-mountain { opacity: 0; }
    .bb-skeleton-bars { opacity: 1; }
    /* Native SVG animateMotion / animateTransform DO honor the
       prefers-reduced-motion query in modern Chrome / Firefox / Safari,
       but Safari's older engines and some embedded WebViews don't -
       so we belt-and-brace it: hide every SMIL animation tag inside
       the climber, freezing the figure at its initial pose. */
    .bb-climber animateMotion,
    .bb-climber animateTransform,
    .bb-climber animate,
    .bb-victory animate,
    .bb-confetti animate {
        display: none !important;
    }
    /* Reset trail draw to fully painted; hide tumble + ski + victory
       poses + confetti + popups so only the static walker remains. */
    .bb-ridge-trail { stroke-dashoffset: 0; }
    .bb-pose-tumble, .bb-pose-ski, .bb-victory,
    .bb-confetti { opacity: 0; }
    .bb-flake, .bb-popup { opacity: 0; }
}

/* ── Census demographics - themed sections inside the card ──
   Each section (Income / Housing / People / Work) renders its own
   mini-heading above its sub-grid so users can scan by topic rather
   than reading one long flat tile strip. */
.demo-section { margin: 0 0 1.25rem; }
.demo-section:last-of-type { margin-bottom: 0.5rem; }
.demo-section-title {
    font-family: var(--font-body);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gold-dark, #8f7420);
    margin: 0 0 0.5rem;
    padding-bottom: 0.25rem;
    border-bottom: 0.0625rem solid var(--border-light, #ebe6dd);
}
.demo-source {
    font-size: 0.6875rem;
    color: var(--muted, #706b63);
    margin-top: 0.75rem;
    padding-top: 0.5rem;
    border-top: 0.0625rem solid var(--border-light, #ebe6dd);
    font-style: italic;
}

/* Responsive grid steps - each large grid drops one column at a time as
   the viewport narrows so cards always fill the row. The previous
   stylesheet jumped straight from 4-cols to 2 (and 5 to 3) at 75rem and
   then to 1 col at 48rem, leaving wasted space at the tablet breakpoint. */
/* ── Asymmetric grids ── Break the uniform-card-grid monotony by
   offering layouts with visual hierarchy built into the grid itself.
   Use these instead of equal-column grids when one item is more
   important than the others. */

/* Featured-first: hero item full-width, remaining items in auto-fill grid */
.grid-featured {
    display: grid;
    gap: var(--space-6);
    grid-template-columns: 1fr;
}
.grid-featured > :first-child {
    display: flex;
    gap: var(--space-6);
    align-items: stretch;
}
.grid-featured-body {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: var(--space-4);
}

/* Content + sidebar: wide primary region with a narrower support column */
.grid-sidebar {
    display: grid;
    grid-template-columns: 1fr 18rem;
    gap: var(--space-6);
    align-items: start;
}
@media (max-width: 48em) {
    .grid-sidebar { grid-template-columns: 1fr; }
}

/* Span utility: make a child span 2 columns in any grid */
.grid-span-2 { grid-column: span 2; }
@media (max-width: 40em) {
    .grid-span-2 { grid-column: span 1; }
}

/* Flow grid: auto-height rows so cards size to their content instead of
   stretching to match the tallest sibling. Better for mixed content. */
.grid-flow { grid-auto-rows: auto; }

/* 2-1 stagger: 2 items top row, 1 centered below for triangle rhythm */
.grid-stagger {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-6);
}
.grid-stagger > :nth-child(3) {
    grid-column: 1 / -1;
    max-width: 60%;
    justify-self: center;
}
@media (max-width: 40em) {
    .grid-stagger { grid-template-columns: 1fr; }
    .grid-stagger > :nth-child(3) { max-width: 100%; }
}

@media (max-width: 80em) {
    .grid-5 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 67.5em) {
    .grid-4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .grid-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 55em) {
    .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 40em) {
    .grid-2,
    .grid-3,
    .grid-4,
    .grid-5 { grid-template-columns: minmax(0, 1fr); }
    .grid { gap: var(--space-3); }
}
@media (max-width: 48em) {
    .page { padding: var(--space-4); }
}

/* Hamburger / drawer toggle - visible only in drawer mode (phones).
   In rail/full modes the sidebar is pinned and the hamburger is redundant,
   so _initMobileMenu() hides it via body.sb-rail / body.sb-full (below).
   Lives inside the dark top-nav, so the bars are white for contrast. */
body.sb-rail .mobile-menu-btn,
body.sb-full .mobile-menu-btn { display: none !important; }

.mobile-menu-btn {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    align-items: stretch;
    gap: 0.3125rem;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0.625rem 0.5rem;
    background: rgba(255, 255, 255, 0.06);
    border: 0.0625rem solid rgba(255, 255, 255, 0.12);
    border-radius: var(--radius-md);
    cursor: pointer;
    flex-shrink: 0;
    color: #fff;
    transition: background-color var(--anim-fast) var(--easing),
                border-color var(--anim-fast) var(--easing),
                box-shadow var(--anim-fast) var(--easing);
    -webkit-tap-highlight-color: transparent;
}
.mobile-menu-btn:hover { background: rgba(255, 255, 255, 0.14); border-color: rgba(255, 255, 255, 0.22); }
.mobile-menu-btn:focus-visible {
    outline: none;
    background: rgba(255, 255, 255, 0.14);
    box-shadow: 0 0 0 0.1875rem var(--gold-muted), 0 0 0 0.0625rem var(--gold);
    border-color: var(--gold);
}
.mobile-menu-btn span {
    display: block;
    width: 100%;
    height: 0.1875rem;
    background: currentColor;
    border-radius: 62.4375rem;
    transition: transform var(--anim-fast) var(--easing), opacity var(--anim-fast) var(--easing);
}
.mobile-menu-btn.active {
    background: rgba(212, 175, 55, 0.2);
    border-color: rgba(212, 175, 55, 0.55);
    color: var(--gold-light);
}
.mobile-menu-btn.active span:nth-child(1) { transform: translateY(0.5rem) rotate(45deg); }
.mobile-menu-btn.active span:nth-child(2) { opacity: 0; }
.mobile-menu-btn.active span:nth-child(3) { transform: translateY(-0.5rem) rotate(-45deg); }

/* Backdrop that dims main content while the sidebar drawer is open on
   narrow screens. No blur - users find the blur disorienting. On large
   screens the sidebar behaves like a dock, so the overlay is suppressed
   entirely and the content stays fully legible and interactive. */
.mobile-nav-overlay {
    position: fixed;
    inset: 0;
    top: var(--top-nav-h);
    background: rgba(17, 24, 39, 0.25);
    /* Slotted just below the nav-priority tier (9500) so the sidebar
       paints over it, but above every other overlay (modals, toasts,
       cookie banner) so they get dimmed behind the drawer. */
    z-index: 9490;
    transition: opacity var(--anim-normal) var(--easing);
}
.mobile-nav-overlay.hidden { display: none; }
/* Rail / full modes keep the content interactive - no overlay at all.
   The overlay is only for drawer mode where the sidebar slides over
   content. */
body.sb-rail .mobile-nav-overlay,
body.sb-full .mobile-nav-overlay { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   TABS
   ═══════════════════════════════════════════════════════════════════════════ */

.tabs {
    display: flex;
    gap: 0;
    border-bottom: 0.0625rem solid var(--border);
    margin-bottom: var(--space-6);
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    /* Hide the scrollbar on touch devices so the tab strip doesn't get a
       cosmetic ridge under it. */
    scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
    /* Locked height matches the default control rhythm so a tab strip
       above a toolbar of buttons reads as part of the same metric grid. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: var(--control-h-lg);
    padding: 0 var(--control-px);
    font-size: var(--text-sm);
    /* Always semibold so the active state doesn't shift the layout when
       the user clicks a tab - only color and border-bottom change. */
    font-weight: var(--weight-semibold);
    color: var(--muted);
    cursor: pointer;
    border-bottom: 0.125rem solid transparent;
    /* Compensate for the bottom border so the active and inactive labels
       sit on the same baseline (otherwise the active tab appears 0.125rem high). */
    margin-bottom: -0.0625rem;
    transition: color var(--anim-fast) var(--easing),
                border-color var(--anim-fast) var(--easing);
    white-space: nowrap;
    background: none;
    border-top: none;
    border-left: none;
    border-right: none;
    font-family: var(--font-body);
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
}
.tab:hover { color: var(--text); }
.tab:focus-visible {
    outline: none;
    color: var(--text);
    box-shadow: inset 0 -0.125rem 0 var(--gold-border);
}
.tab.active,
.tab[aria-selected="true"] {
    color: var(--gold-dark);
    border-bottom-color: var(--gold);
}
.tab-panel { display: none; }
.tab-panel.active,
.tab-panel:not([hidden]) { display: block; }

/* ═══════════════════════════════════════════════════════════════════════════
   BADGES & TAGS
   ═══════════════════════════════════════════════════════════════════════════ */

.badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    /* Locked height + line-height:1 so a badge inside a table cell or
       button row vertically centers on the text baseline without
       padding-induced jitter between variants. */
    height: 1.25rem;
    min-height: 1.25rem;
    padding: 0 0.5rem;
    border-radius: var(--radius-sm);
    font-size: 0.625rem;
    font-weight: var(--weight-bold);
    line-height: 1;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    vertical-align: middle;
    /* Overflow protection - long enum labels stay inside their row. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}
.badge-gold { background: var(--gold-muted); color: var(--gold-dark); border: 0.0625rem solid var(--gold-border); }
.badge-success { background: var(--success-light); color: var(--success); border: 0.0625rem solid rgba(46, 125, 50, 0.18); }
.badge-warning { background: var(--warning-light); color: var(--warning); border: 0.0625rem solid rgba(245, 124, 0, 0.18); }
.badge-error { background: var(--error-light); color: var(--error); border: 0.0625rem solid rgba(198, 40, 40, 0.18); }
.badge-info { background: var(--info-light); color: var(--info); border: 0.0625rem solid rgba(21, 101, 192, 0.18); }
.badge-neutral { background: var(--bg-subtle); color: var(--muted); border: 0.0625rem solid var(--border-light); }
.badge-slate { background: var(--slate); color: var(--cream); border: 0.0625rem solid var(--slate-light); }
.badge-outline { background: transparent; color: var(--text-secondary); border: 0.0625rem solid var(--border); }

/* Alert hints - contextual messages inside cards */
/* ── Inline alert banners ──
   Contextual notifications that live within a page section, not floating.
   Use .alert-hint for compact hints below content, or .alert-banner for
   prominent page-level notifications with optional dismiss and action. */
.alert-hint {
    margin-top: 0.75rem;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    line-height: var(--leading-normal);
}
.alert-hint-success { background: rgba(46, 125, 50, 0.06); color: var(--success); }
.alert-hint-error { background: rgba(198, 40, 40, 0.06); color: var(--error); }
.alert-hint-warning { background: rgba(245, 124, 0, 0.06); color: var(--warning); }
.alert-hint-info { background: rgba(21, 101, 192, 0.06); color: var(--info); }

/* Full-width alert banner with icon, message, action, and dismiss */
.alert-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    line-height: var(--leading-snug);
    border: 0.0625rem solid var(--border-light);
    background: var(--surface-card);
    margin-bottom: var(--space-4);
}
.alert-banner-icon { flex-shrink: 0; display: flex; margin-top: 0.0625rem; }
.alert-banner-body { flex: 1; min-width: 0; }
.alert-banner-body strong { display: block; color: var(--text); margin-bottom: var(--space-1); }
.alert-banner-action {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--gold-dark);
    font-weight: var(--weight-bold);
    font-size: var(--text-xs);
    cursor: pointer;
    padding: 0;
    white-space: nowrap;
    align-self: center;
}
.alert-banner-action:hover { color: var(--gold); }
.alert-banner-dismiss {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--faint);
    font-size: var(--text-lg);
    cursor: pointer;
    padding: 0;
    line-height: 1;
}
.alert-banner-dismiss:hover { color: var(--text); }

.alert-banner-success { border-color: rgba(46, 125, 50, 0.2); background: rgba(46, 125, 50, 0.03); }
.alert-banner-success .alert-banner-icon { color: var(--success); }
.alert-banner-error { border-color: rgba(198, 40, 40, 0.2); background: rgba(198, 40, 40, 0.03); }
.alert-banner-error .alert-banner-icon { color: var(--error); }
.alert-banner-warning { border-color: rgba(245, 124, 0, 0.2); background: rgba(245, 124, 0, 0.03); }
.alert-banner-warning .alert-banner-icon { color: var(--warning); }
.alert-banner-info { border-color: rgba(21, 101, 192, 0.2); background: rgba(21, 101, 192, 0.03); }
.alert-banner-info .alert-banner-icon { color: var(--info); }
.badge-lg {
    height: 1.5rem;
    min-height: 1.5rem;
    font-size: var(--text-xs);
    padding: 0 0.625rem;
}
.badge-sm {
    height: 1rem;
    min-height: 1rem;
    font-size: var(--text-2xs);
    padding: 0 0.375rem;
}
.badge-xs {
    height: 0.875rem;
    min-height: 0.875rem;
    font-size: 0.5625rem;
    padding: 0 0.3125rem;
    letter-spacing: 0.02em;
}

/* Solid variants - white text on filled background. Used for the lead
   score column in search results and anywhere a badge needs maximum
   contrast against a colored fill.  Inherits the locked-height model
   from .badge so they never distort table rows. */
.badge-solid-gold    { background: var(--gold);    color: var(--slate); border-color: transparent; }
.badge-solid-success { background: var(--success); color: #fff; border-color: transparent; }
.badge-solid-warning { background: var(--warning); color: #fff; border-color: transparent; }
.badge-solid-muted   { background: var(--muted);   color: #fff; border-color: transparent; }

/* Lead score badge - used in search results and similar-parcels tables.
   Slightly wider than a generic badge so 2-digit scores don't feel cramped,
   and cursor:help signals the hover tooltip with factor details. */
.badge-score {
    min-width: 1.75rem;
    cursor: help;
}

.tag {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    /* Locked height so tags inside table cells or dense result rows never
       push the row taller than siblings.  Matches .badge model. */
    height: 1.25rem;
    min-height: 1.25rem;
    padding: 0 0.5rem;
    border-radius: var(--radius-pill);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    line-height: 1;
    background: var(--bg-subtle);
    color: var(--text-secondary);
    border: 0.0625rem solid var(--border-light);
    vertical-align: middle;
    /* Same protection as badges - long tags stay inside their parent. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Tag rows wrap onto multiple lines instead of forcing horizontal scroll
   so a parcel with many tags reads as a vertical stack. */
.tag-list,
.tag-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    min-width: 0;
}
.tag-remove {
    cursor: pointer;
    opacity: 0.5;
    font-size: var(--text-sm);
    line-height: 1;
}
.tag-remove:hover { opacity: 1; }

/* Focus rings for interactive inline elements that aren't native buttons.
   Uses :focus-visible so mouse clicks don't show the ring, only keyboard
   Tab navigation. Matches the gold focus glow used on .form-input:focus. */
.tag:focus-visible,
.multiselect-chip:focus-visible,
.filter-pill:focus-visible,
.smart-list-chip:focus-visible,
.insights-category-chip:focus-visible {
    outline: none;
    box-shadow: var(--shadow-gold);
}

/* ═══════════════════════════════════════════════════════════════════════════
   FORMS
   ═══════════════════════════════════════════════════════════════════════════ */

/* Form fields - premium treatment.
   Labels are sentence-case (not screaming uppercase) and match the table
   header microcopy weight. Inputs use a subtle inset shadow for depth,
   a 0.1875rem gold focus ring, and a custom select chevron so they don't fall
   back to the OS default appearance. */
.form-group { margin-bottom: var(--space-4); }
.form-label {
    display: block;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
    margin-bottom: var(--space-2);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.form-label .form-label-required {
    color: var(--error);
    margin-left: var(--space-1);
}
.form-hint {
    font-size: var(--text-xs);
    color: var(--faint);
    margin-top: var(--space-1);
    line-height: var(--leading-snug);
}
.form-error {
    font-size: var(--text-xs);
    color: var(--error);
    margin-top: var(--space-1);
    font-weight: var(--weight-medium);
}

.form-input, .form-select, .form-textarea, select.form-input {
    width: 100%;
    /* Lock the height to the same scale as buttons so inputs and buttons
       in the same row align without manual tweaks. Textareas opt out below. */
    height: var(--control-h);
    min-height: var(--control-h);
    padding: 0 var(--control-px);
    border: 0.0625rem solid var(--border);
    border-radius: var(--radius-md);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    /* Match button line-height (1) so inputs and buttons on the same
       row share an identical text baseline. The locked height + flexbox
       centering handles the vertical alignment - line-height just
       needs to not fight it. */
    line-height: 1;
    color: var(--text);
    background: #ffffff;
    /* Pin the control surface to light so OS dark mode can't flip the
       native <select> chrome to a dark fill on mobile. */
    color-scheme: light;
    box-shadow: var(--shadow-inset);
    transition: border-color var(--anim-fast) var(--easing),
                box-shadow var(--anim-fast) var(--easing),
                background var(--anim-fast) var(--easing);
    outline: none;
}
.form-input:hover:not(:focus):not(:disabled),
.form-select:hover:not(:focus):not(:disabled),
.form-textarea:hover:not(:focus):not(:disabled) {
    border-color: var(--text-secondary);
}
.form-input:focus, .form-select:focus, .form-textarea:focus {
    border-color: var(--gold);
    box-shadow: var(--shadow-gold), var(--shadow-inset);
    background: var(--surface-raised);
}
.form-input::placeholder, .form-textarea::placeholder {
    color: var(--faint);
    font-weight: var(--weight-regular);
}
.form-input:disabled, .form-select:disabled, .form-textarea:disabled {
    background: var(--bg-subtle);
    color: var(--muted);
    cursor: not-allowed;
    box-shadow: none;
}
.form-input.error, .form-select.error, .form-textarea.error {
    border-color: var(--error);
}
.form-input.error:focus, .form-select.error:focus, .form-textarea.error:focus {
    box-shadow: var(--shadow-error), var(--shadow-inset);
}
/* Inline field error text - appears below the input when validation fails.
   Created by UI.showFieldError(), removed by UI.clearFieldError(). */
.field-error {
    font-size: var(--text-xs);
    color: var(--error);
    margin-top: var(--space-1);
    line-height: var(--leading-snug);
}

/* Size variants - same scale as the button heights so a `.form-input-sm`
   sits cleanly next to a `.btn-sm` and a `.form-input-lg` next to a `.btn-lg`. */
.form-input-sm, .form-select-sm {
    height: var(--control-h-sm);
    min-height: var(--control-h-sm);
    padding: 0 var(--control-px-sm);
    font-size: var(--text-xs);
}
.form-input-lg, .form-select-lg {
    height: var(--control-h-lg);
    min-height: var(--control-h-lg);
    padding: 0 var(--control-px-lg);
    font-size: var(--text-base);
}

/* Custom chevron for selects - kills the OS default arrow that breaks
   the premium feel on Safari/Firefox. */
.form-select {
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b655c' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right var(--space-3) center;
    background-size: 0.75rem;
    padding-right: var(--space-8);
}
.form-textarea {
    /* Textareas are the one control that can't be height-locked - they
       grow to fit content. Override the .form-input rule and give them
       sensible vertical padding. */
    height: auto;
    min-height: 5rem;
    padding: var(--space-3) var(--space-4);
    resize: vertical;
    line-height: var(--leading-normal);
}

/* Input with leading icon (e.g. search field) */
.form-input-icon-wrap {
    position: relative;
    display: block;
}
.form-input-icon-wrap .form-input {
    padding-left: calc(var(--control-px) + 1.25rem);
}
.form-input-icon-wrap .form-icon {
    position: absolute;
    left: var(--control-px);
    top: 50%;
    transform: translateY(-50%);
    color: var(--faint);
    pointer-events: none;
    font-size: var(--text-base);
    /* Decorative - don't let the icon eat clicks meant for the input. */
    z-index: 1;
}

.form-row {
    display: flex;
    gap: var(--space-4);
    align-items: flex-end;
}
.form-row .form-group { flex: 1; min-width: 0; margin-bottom: 0; }
/* On phones the row stacks vertically; restore the bottom margin so the
   stacked groups don't touch each other. */
@media (max-width: 40em) {
    .form-row { flex-direction: column; align-items: stretch; gap: var(--space-3); }
}

/* Checkbox & radio - the label hugs the input and lines them up by their
   shared center, not by baseline. min-height matches the small control
   rhythm so a stack of form-checks has the same vertical density as a
   stack of small inputs. */
.form-check {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    cursor: pointer;
    font-size: var(--text-sm);
    color: var(--text-secondary);
    min-height: var(--control-h-sm);
    line-height: 1.3;
    -webkit-tap-highlight-color: transparent;
}
.form-check input[type="checkbox"],
.form-check input[type="radio"] {
    accent-color: var(--gold);
    width: 1rem;
    height: 1rem;
    cursor: pointer;
    margin: 0;
    flex-shrink: 0;
}
.form-check:hover { color: var(--text); }
.form-check input:focus-visible {
    outline: 0.125rem solid var(--gold);
    outline-offset: 0.125rem;
}

/* Range slider */
.form-range {
    width: 100%;
    accent-color: var(--gold);
    cursor: pointer;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MODALS
   ═══════════════════════════════════════════════════════════════════════════ */

/* Modals - premium overlay pattern.
   Backdrop uses a slate tint + 0.5rem blur so the underlying page reads as
   "out of focus" without going black. The modal itself scales-in with a
   spring easing, has a generous header that holds the title and an
   optional subtitle, and on mobile fills the screen edge-to-edge so it
   doesn't feel like a cramped popup on a phone.                       */
.modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: var(--surface-overlay);
    -webkit-backdrop-filter: blur(var(--surface-overlay-blur));
    backdrop-filter: blur(var(--surface-overlay-blur));
    z-index: var(--z-modal);
    align-items: center;
    justify-content: center;
    padding: var(--space-6);
    animation: overlay-in var(--anim-normal) var(--easing);
}
.modal-overlay.active { display: flex; }

@keyframes overlay-in {
    from { opacity: 0; -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0); }
    to { opacity: 1; -webkit-backdrop-filter: blur(var(--surface-overlay-blur)); backdrop-filter: blur(var(--surface-overlay-blur)); }
}

.modal {
    background: var(--surface-card);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-modal);
    width: 100%;
    max-width: 32rem;
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    animation: modal-in var(--anim-normal) var(--easing);
    border: 0.0625rem solid var(--border-light);
    overflow: hidden;
}
.modal-xs { max-width: 22rem; }
.modal-sm { max-width: 26rem; }
.modal-md { max-width: 32rem; }
.modal-lg { max-width: 48rem; }
.modal-xl { max-width: 64rem; }
.modal-full { max-width: calc(100vw - 4rem); max-height: calc(100vh - 4rem); }

@keyframes modal-in {
    from { opacity: 0; transform: translateY(1rem) scale(0.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes modal-out {
    from { opacity: 1; transform: translateY(0) scale(1); }
    to   { opacity: 0; transform: translateY(0.5rem) scale(0.97); }
}
@keyframes overlay-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}
/* Exit: JS adds .is-closing, then removes .active after animation ends */
.modal-overlay.is-closing {
    animation: overlay-out var(--anim-fast) var(--easing) forwards;
}
.modal-overlay.is-closing .modal {
    animation: modal-out var(--anim-fast) var(--easing) forwards;
}

.modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    padding: var(--space-4) var(--space-5);
    border-bottom: 0.0625rem solid var(--border-light);
    gap: var(--space-4);
    background: var(--surface-card);
}
.modal-title-wrap {
    flex: 1;
    min-width: 0;
}
.modal-title {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    color: var(--slate);
    line-height: var(--leading-tight);
    letter-spacing: -0.01em;
    /* Long titles (e.g. an unbroken parcel id used as a label) used to
       blow out the modal width. ``overflow-wrap: anywhere`` lets the
       browser break inside a word as a last resort, while min-width:0
       keeps the title playing nicely with the flex header layout. */
    overflow-wrap: anywhere;
    word-break: break-word;
    min-width: 0;
}
.modal-subtitle {
    font-size: var(--text-sm);
    color: var(--muted);
    margin-top: var(--space-1);
    line-height: var(--leading-snug);
    overflow-wrap: anywhere;
    min-width: 0;
}
.modal-close {
    width: 2.25rem;
    height: 2.25rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: var(--bg-subtle);
    color: var(--muted);
    cursor: pointer;
    border-radius: var(--radius-md);
    font-size: var(--text-lg);
    line-height: 1;
    transition: background var(--anim-fast) var(--easing),
                color var(--anim-fast) var(--easing);
    flex-shrink: 0;
}
.modal-close:hover {
    background: var(--bg-hover);
    color: var(--text);
}

.modal-body {
    padding: var(--space-5);
    overflow-y: auto;
    overscroll-behavior: contain;
    flex: 1;
    color: var(--text);
    font-size: var(--text-base);
    line-height: var(--leading-normal);
}
.modal-footer {
    padding: var(--space-3) var(--space-5);
    border-top: 0.0625rem solid var(--border-light);
    display: flex;
    /* The default footer puts ALL actions on the right (Cancel | Save).
       Use ``.modal-footer--spread`` for the secondary-action-on-left
       pattern (e.g. "Delete" on the left, "Cancel | Save" on the right). */
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-3);
    background: var(--surface-card);
    flex-shrink: 0;
}
.modal-footer--spread { justify-content: space-between; }
.modal-footer .btn { min-width: 5rem; }
/* Wrap on phones so a 3-button footer doesn't get clipped. */
@media (max-width: 30em) {
    .modal-footer { flex-wrap: wrap; }
    .modal-footer .btn { flex: 1 1 auto; min-width: 0; }
}

/* Mobile fullscreen */
@media (max-width: 40em) {
    .modal-overlay { padding: 0; }
    .modal,
    .modal-xs, .modal-sm, .modal-md, .modal-lg, .modal-xl, .modal-full {
        max-width: 100%;
        max-height: 100vh;
        height: 100vh;
        border-radius: 0;
        border: none;
    }
    .modal-header { padding: var(--space-4); }
    .modal-body { padding: var(--space-4); }
    .modal-footer { padding: var(--space-3) var(--space-4); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   TOASTS / NOTIFICATIONS
   ═══════════════════════════════════════════════════════════════════════════ */

.toast-container {
    position: fixed;
    bottom: 1.5rem;
    right: 1.5rem;
    z-index: 3000;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    pointer-events: none;
}
/* Shift toasts above the fixed action bar on property pages so
   they don't overlap the action buttons. */
body:has(.prop-action-bar:not(.hidden)) .toast-container {
    bottom: 5.5rem;
}
.toast {
    background: var(--warm-white);
    border: 0.0625rem solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    padding: 0.625rem 0.75rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 16rem;
    max-width: min(26rem, calc(100vw - 2rem));
    pointer-events: auto;
    animation: toast-in var(--anim-normal) var(--easing);
    font-size: var(--text-sm);
    overflow-wrap: anywhere;
    word-break: break-word;
    line-height: var(--leading-snug);
}
.toast.leaving { animation: toast-out 140ms var(--easing) forwards; }

/* Type variants - left border + opaque tinted surface + icon color.
   The tint is layered over the solid warm-white so the toast never
   goes transparent when it floats over a dark app shell. */
.toast-success { border-left: 0.1875rem solid var(--success); background: linear-gradient(rgba(46, 125, 50, 0.05), rgba(46, 125, 50, 0.05)), var(--warm-white); }
.toast-success .toast-icon { color: var(--success); }
.toast-error   { border-left: 0.1875rem solid var(--error);   background: linear-gradient(rgba(198, 40, 40, 0.05), rgba(198, 40, 40, 0.05)), var(--warm-white); }
.toast-error .toast-icon   { color: var(--error); }
.toast-warning { border-left: 0.1875rem solid var(--warning); background: linear-gradient(rgba(245, 124, 0, 0.05), rgba(245, 124, 0, 0.05)), var(--warm-white); }
.toast-warning .toast-icon { color: var(--warning); }
.toast-info    { border-left: 0.1875rem solid var(--info);    background: linear-gradient(rgba(21, 101, 192, 0.05), rgba(21, 101, 192, 0.05)), var(--warm-white); }
.toast-info .toast-icon    { color: var(--info); }

/* Icon: type-specific SVG */
.toast-icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
}
/* Message text */
.toast-text {
    flex: 1;
    min-width: 0;
    color: var(--text);
}
/* Action button: "Retry", "Undo", "View" etc. */
.toast-action {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--gold-dark);
    font-weight: var(--weight-bold);
    font-size: var(--text-xs);
    cursor: pointer;
    padding: 0;
    white-space: nowrap;
}
.toast-action:hover { color: var(--gold); }
/* Dismiss X */
.toast-dismiss {
    flex-shrink: 0;
    background: none;
    border: none;
    color: var(--faint);
    font-size: var(--text-lg);
    line-height: 1;
    cursor: pointer;
    padding: 0 0 0 0.25rem;
    margin-left: auto;
}
.toast-dismiss:hover { color: var(--text); }

/* Countdown progress bar - renders as a hairline along the bottom edge
   of the toast that shrinks from full width to zero over the toast's
   auto-dismiss duration. Gives the user an honest visual signal of
   how much time is left before the toast clears itself. */
.toast { position: relative; }
.toast-progress {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 0.125rem;
    width: 100%;
    border-bottom-left-radius: var(--radius-md);
    border-bottom-right-radius: var(--radius-md);
    background: var(--muted);
    transform-origin: left center;
    animation: toast-progress linear forwards;
    pointer-events: none;
    opacity: 0.55;
}
.toast-success .toast-progress { background: var(--success); }
.toast-error   .toast-progress { background: var(--error); }
.toast-warning .toast-progress { background: var(--warning); }
.toast-info    .toast-progress { background: var(--info); }
@keyframes toast-progress {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

@keyframes toast-in {
    from { opacity: 0; transform: translateX(1rem); }
    to { opacity: 1; transform: translateX(0); }
}
@keyframes toast-out {
    to { opacity: 0; transform: translateX(1rem); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   EMPTY STATES - premium pattern shared across every page that can be empty.
   Works for: zero search results, empty saved lists, no recent activity,
   permission errors, network failures, "select an item" placeholders.
   ═══════════════════════════════════════════════════════════════════════════ */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--space-12) var(--space-6);
    color: var(--muted);
    animation: fadeInUp var(--anim-normal) var(--easing) both;
}
.empty-state.is-compact { padding: var(--space-8) var(--space-4); }

/* Soft tinted circle behind the icon - gives empty states a deliberate
   "this is intentional" look instead of a stripped-back placeholder. */
.empty-state-icon {
    width: 4.5rem;
    height: 4.5rem;
    border-radius: var(--radius-pill);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--gold-muted);
    border: 0.0625rem solid var(--gold-border);
    color: var(--gold-dark);
    font-size: var(--text-2xl);
    margin-bottom: var(--space-5);
    box-shadow: var(--shadow-card);
}
.empty-state-icon svg {
    width: 1.75rem;
    height: 1.75rem;
}
.empty-state.is-error .empty-state-icon {
    background: var(--error-light);
    border-color: rgba(198, 40, 40, 0.2);
    color: var(--error);
}
.empty-state.is-success .empty-state-icon {
    background: var(--success-light);
    border-color: rgba(46, 125, 50, 0.2);
    color: var(--success);
}
.empty-state.is-info .empty-state-icon {
    background: var(--info-light);
    border-color: rgba(21, 101, 192, 0.18);
    color: var(--info);
}

.empty-state-title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
    color: var(--text);
    margin-bottom: var(--space-2);
    line-height: var(--leading-tight);
    letter-spacing: -0.01em;
}
.empty-state-text {
    font-size: var(--text-base);
    max-width: 28rem;
    margin: 0 auto;
    line-height: var(--leading-normal);
    color: var(--muted);
}
.empty-state-actions {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-6);
    flex-wrap: wrap;
    justify-content: center;
}
.empty-state-hint {
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 0.0625rem solid var(--border-light);
    font-size: var(--text-xs);
    color: var(--faint);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    width: 100%;
    max-width: 28rem;
}

/* Real-estate quip rendered on error empty states. Sits just under the
   actual error text so the user gets real info first (the "what went
   wrong") and the levity second (the quip), making the screen feel
   human instead of broken. Gold accent bar on the left so it reads as
   intentional, not a glitch. */
.empty-state-quip {
    margin-top: var(--space-4);
    padding: 0.625rem 0.875rem;
    border-left: 0.1875rem solid var(--gold, #b5942a);
    background: var(--gold-muted, #fdf7e3);
    color: var(--slate, #1c2331);
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-style: italic;
    line-height: 1.45;
    max-width: 32rem;
    border-radius: 0.25rem;
    transition: opacity 180ms ease;
}

/* Log-activity modal list picker */
.act-list-picker {
    padding: 0.625rem;
    background: var(--bg-subtle, #f7f5f0);
    border: 0.0625rem solid var(--border-light, #ebe6dd);
    border-radius: 0.375rem;
}
.act-list-grid {
    display: grid;
    gap: 0.25rem;
    max-height: 11rem;
    overflow-y: auto;
}
.act-list-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.5rem;
    border-radius: 0.25rem;
    cursor: pointer;
    transition: background 140ms ease;
    font-size: 0.8125rem;
}
.act-list-option:hover { background: rgba(181, 148, 42, 0.08); }
.act-list-option input[type="radio"] { accent-color: var(--gold, #b5942a); }
.act-list-option:has(input:checked) {
    background: var(--gold-muted, #fdf7e3);
    outline: 0.0625rem solid var(--gold, #b5942a);
}
.act-list-option .color-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    flex-shrink: 0;
}
.act-list-option .act-list-name { flex: 1; color: var(--slate, #1c2331); font-weight: 600; }
.act-list-option .act-list-count { font-size: 0.6875rem; color: var(--muted, #6b6660); }

/* Log-activity checkbox-group variant - used when the property already
   lives in one or more lists. Every containing list renders as a checked
   row; unchecking drops that list from the activity fan-out. */
.act-list-group {
    display: grid;
    gap: 0.25rem;
    max-height: 11rem;
    overflow-y: auto;
    margin-top: 0.375rem;
}
.act-list-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4375rem 0.625rem;
    border: 0.0625rem solid var(--border-light, #ebe6dd);
    border-radius: 0.375rem;
    background: #fff;
    cursor: pointer;
    transition: border-color 140ms ease, background 140ms ease;
    font-size: 0.8125rem;
}
.act-list-row:hover { border-color: var(--gold, #b5942a); background: rgba(181, 148, 42, 0.04); }
.act-list-row:has(.act-list-check:checked) {
    background: rgba(181, 148, 42, 0.08);
    border-color: var(--gold, #b5942a);
}
.act-list-check { accent-color: var(--gold, #b5942a); flex-shrink: 0; }
.act-list-row .act-list-name { flex: 1; color: var(--slate, #1c2331); font-weight: 600; }
.act-list-row .act-list-count { font-size: 0.6875rem; color: var(--muted, #6b6660); }
.act-list-hint {
    font-size: 0.75rem;
    color: var(--muted, #6b6660);
    margin-top: 0.375rem;
    line-height: 1.45;
}

/* List activity feed (Saved Properties → selected list → Activity block). */
.list-activity-section {
    margin-top: var(--space-6, 1.5rem);
    padding: var(--space-4, 1rem);
    background: var(--surface-card, #fff);
    border: 0.0625rem solid var(--border-light, #ebe6dd);
    border-radius: var(--radius-md, 0.5rem);
}
.list-activity-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.75rem;
}
.list-activity-title {
    font-weight: 700;
    font-size: 0.9375rem;
    color: var(--slate, #1c2331);
}
.list-activity-sub {
    font-size: 0.75rem;
    color: var(--muted, #8b8680);
    margin-top: 0.125rem;
}
.list-activity-feed {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}
.list-activity-item {
    display: flex;
    gap: 0.625rem;
    padding: 0.5rem 0.625rem;
    background: var(--bg-subtle, #f7f5f0);
    border-radius: 0.375rem;
    border-left: 0.1875rem solid var(--gold, #b5942a);
}
.list-activity-icon {
    flex-shrink: 0;
    color: var(--gold-dark, #8a6d1f);
    padding-top: 0.125rem;
}
.list-activity-main { flex: 1; min-width: 0; }
.list-activity-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.375rem;
    font-size: 0.8125rem;
}
.list-activity-type { font-weight: 700; color: var(--slate, #1c2331); }
.list-activity-sep { color: var(--faint, #c4bfb5); }
.list-activity-link { color: var(--gold-dark, #8a6d1f); text-decoration: none; font-weight: 600; }
.list-activity-link:hover { text-decoration: underline; }
.list-activity-time { margin-left: auto; font-size: 0.6875rem; color: var(--muted, #8b8680); }
.list-activity-body-text {
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    color: var(--text, #2c2c2c);
    line-height: 1.45;
    white-space: pre-wrap;
}
.list-activity-outcome {
    margin-top: 0.375rem;
    display: inline-block;
    font-size: 0.625rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Color picker for the "Create List" dialog - swatches sized for
   fingers on touch + keyboard focus, with a proper checked state
   ring that reads the same whether or not the color is dark. */
.color-swatch-grid {
    display: grid;
    grid-template-columns: repeat(8, minmax(0, 1fr));
    gap: 0.5rem;
    max-width: 22rem;
}
@media (max-width: 30em) {
    .color-swatch-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: none; }
}
.color-swatch {
    appearance: none;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0.25rem;
    border: 0.0625rem solid transparent;
    background: transparent;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.color-swatch:hover { transform: scale(1.08); }
.color-swatch:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.1875rem rgba(181, 148, 42, 0.35);
}
.color-swatch-fill {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.12);
}
.color-swatch.is-selected {
    border-color: var(--slate, #1c2331);
    box-shadow: 0 0 0 0.0625rem var(--surface-card, #fff) inset;
}
.color-swatch.is-selected .color-swatch-fill {
    box-shadow:
        inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.15),
        inset 0 0 0 0.1875rem var(--surface-card, #fff);
}

/* Save-results-to-list modal (Property Search → Save button). */
.save-to-list-modal { display: flex; flex-direction: column; gap: 0.875rem; }
.save-to-list-summary {
    margin: 0;
    padding: 0.5rem 0.75rem;
    background: var(--gold-muted, #fdf7e3);
    border-left: 0.1875rem solid var(--gold, #b5942a);
    border-radius: 0.25rem;
    font-size: 0.875rem;
    color: var(--slate, #1c2331);
}
.save-to-list-tabs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
    border: 0.0625rem solid var(--border-light, #ebe6dd);
    border-radius: 0.375rem;
    overflow: hidden;
}
.save-to-list-tab {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0.5rem 0.75rem;
    font-family: var(--font-body);
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--muted, #8b8680);
    cursor: pointer;
    transition: background 140ms ease, color 140ms ease;
}
.save-to-list-tab + .save-to-list-tab { border-left: 0.0625rem solid var(--border-light, #ebe6dd); }
.save-to-list-tab:hover:not([disabled]) { background: var(--bg-subtle, #f7f5f0); }
.save-to-list-tab.is-active {
    background: var(--slate, #1c2331);
    color: var(--gold, #b5942a);
}
.save-to-list-tab[disabled] { opacity: 0.5; cursor: not-allowed; }

.save-to-list-panel { padding-top: 0.125rem; }
.save-to-list-options { display: flex; flex-direction: column; gap: 0.25rem; max-height: 14rem; overflow-y: auto; }
.save-to-list-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.625rem;
    border-radius: 0.375rem;
    cursor: pointer;
    transition: background 140ms ease;
    font-size: 0.875rem;
    background: var(--bg-subtle, #f7f5f0);
}
.save-to-list-option:hover { background: rgba(181, 148, 42, 0.08); }
.save-to-list-option input[type="radio"] { accent-color: var(--gold, #b5942a); }
.save-to-list-option:has(input:checked) {
    background: var(--gold-muted, #fdf7e3);
    outline: 0.0625rem solid var(--gold, #b5942a);
}
.save-to-list-option .color-dot {
    width: 0.625rem;
    height: 0.625rem;
    border-radius: 50%;
    flex-shrink: 0;
}
.save-to-list-name { flex: 1; color: var(--slate, #1c2331); font-weight: 600; }
.save-to-list-count { font-size: 0.6875rem; color: var(--muted, #8b8680); }

/* Aside variant: horizontal left-aligned layout for inline contexts -
   inside a sidebar, within a card, or a narrow column. Feels editorial
   instead of the centered-icon-text template every SaaS uses. */
.empty-state--aside {
    flex-direction: row;
    text-align: left;
    align-items: flex-start;
    padding: var(--space-6) var(--space-5);
    gap: var(--space-5);
}
.empty-state--aside .empty-state-icon {
    width: 3rem;
    height: 3rem;
    font-size: var(--text-lg);
    margin-bottom: 0;
    flex-shrink: 0;
}
.empty-state--aside .empty-state-icon svg {
    width: 1.25rem;
    height: 1.25rem;
}
.empty-state--aside .empty-state-text {
    margin: 0;
    max-width: none;
}
.empty-state--aside .empty-state-actions {
    justify-content: flex-start;
}
@media (max-width: 30em) {
    .empty-state--aside { flex-direction: column; text-align: center; align-items: center; }
    .empty-state--aside .empty-state-actions { justify-content: center; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   VIDEO PLAYER - Facade pattern component (UI.VideoPlayer)
   ═══════════════════════════════════════════════════════════════════════════

   Shows a lightweight poster + play button on load. The real iframe or
   <video> element is injected only when the user clicks play.
   Performance cost until interaction: one image + one SVG.
   Used in help articles, onboarding, and any SPA-embedded video. */

.vid-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--slate);
    border: 0.0625rem solid var(--border-light);
    box-shadow: var(--shadow-card);
}
.vid-facade {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    background: var(--slate);
    cursor: pointer;
    z-index: 2;
}
.vid-facade .vid-poster {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.vid-facade.is-placeholder { cursor: default; }
.vid-facade.is-loading .vid-play-icon {
    opacity: 0.5;
    animation: spin 0.6s linear infinite;
}
.vid-play-btn {
    position: relative;
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    color: var(--warm-white);
}
.vid-play-btn:disabled { cursor: default; }
.vid-play-icon {
    width: 3.5rem;
    height: 3.5rem;
    border-radius: 50%;
    background: rgba(181, 148, 42, 0.15);
    border: 0.125rem solid rgba(181, 148, 42, 0.3);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform var(--anim-fast) var(--easing),
                background var(--anim-fast) var(--easing);
}
.vid-play-icon svg { color: var(--gold-light); }
.vid-play-btn:hover:not(:disabled) .vid-play-icon {
    transform: scale(1.06);
    background: rgba(181, 148, 42, 0.25);
}
.vid-play-btn:focus-visible .vid-play-icon {
    box-shadow: var(--shadow-gold);
}
.vid-play-label {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    color: rgba(255, 255, 255, 0.4);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.vid-embed {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: none;
    display: block;
}
.vid-transcript-link {
    position: absolute;
    bottom: var(--space-3);
    right: var(--space-3);
    z-index: 4;
    font-size: var(--text-2xs);
    font-weight: var(--weight-semibold);
    color: rgba(255, 255, 255, 0.3);
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    background: rgba(0, 0, 0, 0.4);
    transition: color var(--anim-fast) var(--easing);
}
.vid-transcript-link:hover { color: rgba(255, 255, 255, 0.7); }

/* ═══════════════════════════════════════════════════════════════════════════
   PRODUCT SCREENSHOTS - responsive containers for real captures
   ═══════════════════════════════════════════════════════════════════════════

   Drop a real screenshot <img> into .screen-frame and it gets proper
   aspect ratio, border treatment, shadow, loading state, and responsive
   sizing. Pairs with the text fallback in .screen-fallback for when
   no screenshot file exists yet.

   Capture standards (enforced by check-media.js):
     Format:    WebP preferred, PNG acceptable
     Width:     1920px source (renders at 960px on standard displays)
     Viewport:  1440×900 browser window
     Data:      Real database content, not demo values
     Directory: /img/screens/{view-name}.webp                          */

.screen-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--slate);
    border: 0.0625rem solid var(--border-light);
    box-shadow: var(--shadow-card);
}
.screen-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: top left;
    display: block;
}
/* Loading state: skeleton shimmer until image loads */
.screen-frame img[loading="lazy"] {
    background: linear-gradient(
        90deg,
        var(--bg-subtle) 25%,
        var(--border-light) 37%,
        var(--bg-subtle) 63%
    );
    background-size: 200% 100%;
    animation: shimmer 1.5s linear infinite;
}
/* Text-based fallback - shown when no screenshot file exists.
   Hidden when a sibling <img> is present. */
.screen-fallback {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    padding: var(--space-6);
    color: rgba(255, 255, 255, 0.85);
}
.screen-frame:has(img) .screen-fallback { display: none; }

/* Dark frame variant for landing page context */
.screen-frame--dark {
    background: linear-gradient(135deg, var(--slate) 0%, #232b3a 100%);
    box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.3);
    border-color: rgba(255, 255, 255, 0.06);
}
/* Inset frame variant for in-app documentation */
.screen-frame--inset {
    box-shadow: var(--shadow-inset);
    border-color: var(--border);
}

/* ═══════════════════════════════════════════════════════════════════════════
   LOADING
   ═══════════════════════════════════════════════════════════════════════════ */

.loading {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3rem 1rem;
    /* Wrap so a long quip on a narrow screen drops below the spinner
       instead of forcing a horizontal scroll. */
    flex-wrap: wrap;
    gap: 0.75rem;
    text-align: center;
}
.loading-spinner {
    width: 1.5rem;
    height: 1.5rem;
    border: 0.125rem solid var(--border);
    border-top-color: var(--gold);
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
    flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }

.loading-text {
    font-size: var(--text-sm);
    color: var(--muted);
    /* gap on the parent handles spacing - drop the legacy left margin
       so the wrap-to-newline state isn't double-spaced. */
    transition: opacity 180ms ease;
    /* On narrow screens, let the message break naturally instead of
       overflowing the container. */
    max-width: 100%;
}
@media (prefers-reduced-motion: reduce) {
    .loading-spinner { animation: none !important; }
}

/* Skeleton loaders - used for tables, cards, and detail headers while
   real data fetches. Tri-stop gradient: base cream → slightly darker
   border tone → a tinted slate highlight, then back. The deeper peak
   makes the shimmer unmistakably visible on every viewport (the
   previous `--bg-subtle` → `--border-light` pair was a ~5% lightness
   shift that read as static on cellular displays). The radius matches
   whatever the skeleton is masking. */
.skeleton {
    background: linear-gradient(
        90deg,
        var(--bg-subtle) 0%,
        var(--border-light) 35%,
        rgba(28, 35, 49, 0.10) 50%,
        var(--border-light) 65%,
        var(--bg-subtle) 100%
    );
    background-size: 220% 100%;
    animation: shimmer 1.4s linear infinite;
    border-radius: var(--radius-sm);
    color: transparent;
    user-select: none;
    pointer-events: none;
    /* Minimum visual mass - without this, a 0.625rem-tall skeleton-line
       inside a cell that has its own padding can collapse to a
       hard-to-spot sliver. */
    min-height: 0.6rem;
}
@media (prefers-reduced-motion: reduce) {
    /* Replace the moving gradient with a soft 2-stop opacity pulse so
       the loading state still reads, just without horizontal motion. */
    .skeleton {
        background: var(--border-light);
        animation: skeleton-pulse 1.8s ease-in-out infinite;
    }
    @keyframes skeleton-pulse {
        0%, 100% { opacity: 0.7; }
        50%      { opacity: 1; }
    }
}
@keyframes shimmer {
    0% { background-position: 220% 0; }
    100% { background-position: -220% 0; }
}

/* Skeleton block primitives used by SkeletonCard / SkeletonTable */
.skeleton-line {
    height: 0.875rem;
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-2);
}
.skeleton-line.is-tall { height: 1.125rem; }
.skeleton-line.is-short { width: 60%; }
.skeleton-line.is-medium { width: 80%; }
.skeleton-pill {
    height: 1.5rem;
    width: 4rem;
    border-radius: var(--radius-pill);
    display: inline-block;
}
.skeleton-circle {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
}
.skeleton-stat {
    height: 2.5rem;
    width: 70%;
    border-radius: var(--radius-md);
    margin-bottom: var(--space-2);
}

/* Pre-built layouts so views don't have to compose primitives by hand. */
.skeleton-card-block {
    padding: var(--space-5);
    border: 0.0625rem solid var(--border-light);
    border-radius: var(--radius-lg);
    background: var(--surface-card);
    box-shadow: var(--shadow-card);
}
.skeleton-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-bottom: 0.0625rem solid var(--border-light);
}
.skeleton-row:last-child { border-bottom: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   UTILITY CLASSES
   ═══════════════════════════════════════════════════════════════════════════ */

.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-xs { gap: var(--space-1); }
.gap-sm { gap: var(--space-2); }
.gap-md { gap: var(--space-4); }
.gap-lg { gap: var(--space-6); }

.text-mono { font-family: var(--font-mono); }
.text-display { font-family: var(--font-display); }
.text-sm { font-size: 0.75rem; }
.text-xs { font-size: var(--text-xs); }
.text-muted { color: var(--muted); }
.text-faint { color: var(--faint); }
.text-gold { color: var(--gold); }
.text-success { color: var(--success); }
.text-warning { color: var(--warning); }
.text-error { color: var(--error); }
.text-right { text-align: right; }
.text-center { text-align: center; }

.font-bold { font-weight: var(--weight-bold); }
.font-semibold { font-weight: var(--weight-semibold); }
.font-medium { font-weight: var(--weight-medium); }

/* ── Spacing utilities ──
   Mapped 1:1 to the spacing scale tokens. Use these instead of
   inline style="margin-top:..." so spacing stays on the grid. */
.mt-xs { margin-top: var(--space-1); }
.mt-sm { margin-top: var(--space-2); }
.mt-md { margin-top: var(--space-4); }
.mt-lg { margin-top: var(--space-6); }
.mt-xl { margin-top: var(--space-8); }
.mb-xs { margin-bottom: var(--space-1); }
.mb-sm { margin-bottom: var(--space-2); }
.mb-md { margin-bottom: var(--space-4); }
.mb-lg { margin-bottom: var(--space-6); }
.mb-xl { margin-bottom: var(--space-8); }

.hidden { display: none !important; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nowrap { white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE
   ═══════════════════════════════════════════════════════════════════════════ */

/* No icon-only responsive mode - sidebar is an overlay drawer on every
   device now, so it keeps its full-width labels when open. */

/* ── Tablet tier: 48em–64em (768–1024px) ──
   Fills the gap between mobile (<48em) and desktop (≥64em). Prevents
   the "cramped tablet" look where the search bar is still visible but
   every track is squeezed. Shrinks the brand + softens the search so
   the top-nav breathes. */
@media (min-width: 48em) and (max-width: 64em) {
    /* Padding + gap stay fluid at this tier so the layout breathes
       evenly between phone and desktop. clamp() ensures the bell never
       crowds the search bar at any specific viewport in the range. */
    .top-nav { padding: 0 clamp(0.75rem, 1.4vw, 1rem); gap: clamp(0.875rem, 1.4vw, 1.25rem); }
    .top-nav-brand-logo { max-width: min(11rem, 32vw); }
    .nav-search-input { font-size: var(--text-sm); }
    .nav-search-kbd { display: none; }
    /* Kbd hint is hidden at this tier - drop the 3rem right reservation
       so the input doesn't appear half-empty AND the bell button next to
       it isn't jammed against the input's right edge. */
    .nav-search-input { padding-right: 0.875rem; }
    .nav-user-name { max-width: 7rem; overflow: hidden; text-overflow: ellipsis; }
    /* Keep the page padding readable but not bloated at this tier. */
    .page, .page-wide { padding-left: var(--space-5); padding-right: var(--space-5); }
}

@media (max-width: 48em) {
    .top-nav { padding: 0 0.75rem; gap: 0.5rem; }
    .nav-search { display: none; }
    .nav-user-name { display: none; }
    .nav-user-chevron { display: none; }  /* collapse the chevron too - the avatar alone is the button */
    .nav-user-btn { padding: 0.25rem; }
    .page-header { flex-direction: column; align-items: flex-start; gap: var(--space-3); }
    /* Brand gets a hair more breathing room than the hamburger so the
       logo doesn't look glued to the menu button. */
    .top-nav-brand { padding: 0 0.25rem; }
    .top-nav-brand-logo { max-width: min(9rem, 42vw); }
    /* .form-row stacking is handled at 40rem in the FORMS section above so
       it's grouped with the rest of the form-row rules. */
}

/* Ultra-narrow: every pixel matters. At 360px and below the nav has
   four fixed-width items (hamburger, brand, bell, avatar) with nothing
   flexible between - shrink the gap and padding so they all fit on one
   row with room to breathe. */
@media (max-width: 22.5em) {
    .top-nav { padding: 0 0.5rem; gap: 0.375rem; }
    .top-nav-brand { padding: 0; }
    .top-nav-brand-logo { max-width: min(7rem, 36vw); }
    .mobile-menu-btn { width: 2.5rem; height: 2.5rem; padding: 0.5rem 0.4375rem; }
    .nav-action-btn { min-width: 2.5rem; min-height: 2.5rem; }
    .nav-user-btn { padding: 0.1875rem; }
    .nav-user-avatar { width: 1.5rem; height: 1.5rem; font-size: 0.6875rem; }
}

@media (max-width: 30em) {
    .page { padding: 0.75rem; }
    /* Use the same horizontal padding as the page so card content aligns
       with the page edge instead of double-indenting. */
    .card-header { padding: var(--space-3) var(--space-4); }
    .card-body { padding: var(--space-4); }
    .card-footer { padding: var(--space-3) var(--space-4); }
    .stat-card { padding: var(--space-4); }
    .stat-card-value { font-size: var(--text-xl); }
    .tabs { gap: 0; margin-bottom: var(--space-4); }
    /* Don't shrink the tab height - touch targets must stay tappable.
       Only shrink horizontal padding so more tabs fit on screen. */
    .tab { padding: 0 var(--space-3); font-size: var(--text-xs); }
    .command-palette { width: 95%; }
}

/* 23.4375em (375px) removed - merged into the official 22.5em (360px)
   floor below so the micro-phone tier fires from one canonical width. */

/* ── Touch target floor - Apple HIG / WCAG 2.5.5 ──
   Buttons sized via .btn-sm / .btn-xs are intentionally compact at desktop,
   but on touch devices they fall well under the 2.75x2.75rem minimum tap target.
   This rule enlarges every actionable .btn / .btn-icon on coarse pointers
   without affecting mouse layouts. We override BOTH `height` and `min-height`
   so the height-locked control system honours the floor. */
@media (pointer: coarse) {
    .btn,
    .btn-icon,
    .btn-sm,
    .btn-xs,
    .btn-icon.btn-sm,
    .btn-icon.btn-xs {
        height: var(--control-h-lg);
        min-height: var(--control-h-lg);
    }
    .btn-icon,
    .btn-icon.btn-sm,
    .btn-icon.btn-xs {
        width: var(--control-h-lg);
        min-width: var(--control-h-lg);
    }
    /* Tabs and table-row actions are also tappable. */
    .tab { min-height: var(--control-h-lg); }
    /* Form controls match so a button next to an input still aligns. */
    .form-input,
    .form-select,
    .form-input-sm,
    .form-select-sm {
        height: var(--control-h-lg);
        min-height: var(--control-h-lg);
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MLS & STATUS BADGES
   ═══════════════════════════════════════════════════════════════════════════ */

.badge-active { background: #e8f5e9; color: #2e7d32; border: 0.0625rem solid #c8e6c9; }
.badge-pending { background: #fff3e0; color: #e65100; border: 0.0625rem solid #ffe0b2; }
.badge-sold { background: #e3f2fd; color: #1565c0; border: 0.0625rem solid #bbdefb; }
.badge-expired { background: var(--bg-subtle); color: var(--faint); border: 0.0625rem solid var(--border-light); }
.badge-high-confidence { border: 0.0625rem solid var(--success); }
.badge-medium-confidence { border: 0.0625rem solid var(--warning); }
.badge-low-confidence { border: 0.0625rem solid var(--error); }

/* ═══════════════════════════════════════════════════════════════════════════
   ANIMATION UTILITIES

   Two-tier system:
     1. .animate-* utilities - drop on any element to play a one-shot
        entrance animation. They use ``both`` so the start state holds
        before the animation runs (no flash of the final state).
     2. .stagger-N modifiers - additive delays applied alongside an
        .animate-* class. Use them to reveal a row/grid sequentially
        without writing per-child CSS.

   Every animation here is GPU-only (transform + opacity) and respects
   ``prefers-reduced-motion`` via the global block below.
   ═══════════════════════════════════════════════════════════════════════════ */

.animate-in       { animation: fadeInUp var(--anim-slow) var(--easing) both; }
.animate-in-fast  { animation: fadeInUp var(--anim-fast) var(--easing) both; }
.animate-scale-in { animation: scaleIn var(--anim-normal) var(--easing) both; }
.animate-slide-right { animation: slideInRight var(--anim-normal) var(--easing) both; }
/* Premium additions used by the views to give every page a soft entrance */
.animate-page-enter { animation: fadeInUp var(--anim-normal) var(--easing) both; }
.animate-pop      { animation: popIn var(--anim-normal) var(--easing) both; }
.animate-pop-out  { animation: popOut var(--anim-fast) var(--easing) both; }

/* Stagger delays: 50ms increments, max 12 items (600ms total cascade).
   Matches the property page stagger and dashboard entrance cascade. */
.stagger-1  { animation-delay: 50ms; }
.stagger-2  { animation-delay: 100ms; }
.stagger-3  { animation-delay: 150ms; }
.stagger-4  { animation-delay: 200ms; }
.stagger-5  { animation-delay: 250ms; }
.stagger-6  { animation-delay: 300ms; }
.stagger-7  { animation-delay: 350ms; }
.stagger-8  { animation-delay: 400ms; }
.stagger-9  { animation-delay: 450ms; }
.stagger-10 { animation-delay: 500ms; }
.stagger-11 { animation-delay: 550ms; }
.stagger-12 { animation-delay: 600ms; }

/* ── Page envelope: every SPA view fades up softly on mount ──
   The router clears .main-content and remounts a new view inside it.
   This selector adds the entrance animation to whatever <div class="page">
   the view rendered, so consumers don't have to remember to add it. */
.main-content > .page,
.main-content > .page-narrow {
    animation: fadeInUp var(--anim-normal) var(--easing) both;
}

/* ── Animated three-dot loader ──
   Inline replacement for a static "Loading..." label inside a button
   or chip. Each dot bounces up + brightens with a staggered delay so
   the three dots wave together. Inherits color via `currentColor` so
   it sits naturally on any button (gold-primary, ghost, link, etc.).
   Usage: button.innerHTML = 'Finding<span class="bb-dot-loader" aria-hidden="true"><span></span><span></span><span></span></span>'; */
.bb-dot-loader {
    display: inline-flex;
    gap: 0.1875rem;
    align-items: center;
    margin-left: 0.375rem;
    vertical-align: baseline;
}
.bb-dot-loader > span {
    display: inline-block;
    width: 0.3125rem;
    height: 0.3125rem;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.3;
    animation: bb-dot-bounce 1.2s ease-in-out infinite;
}
.bb-dot-loader > span:nth-child(2) { animation-delay: 0.18s; }
.bb-dot-loader > span:nth-child(3) { animation-delay: 0.36s; }
@keyframes bb-dot-bounce {
    0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
    40%          { opacity: 1;    transform: translateY(-0.1875rem); }
}
@media (prefers-reduced-motion: reduce) {
    .bb-dot-loader > span { animation: none; opacity: 0.6; }
}

/* ── Hover lifts ── A small upward slide on hover that signals
   interactivity. Used by .card-hoverable / lead-list-card / insight-card.
   Defined here so the easing matches everything else, and so authors don't
   re-type the cubic-bezier. */
.card-hoverable {
    transition: transform var(--anim-fast) var(--easing),
                box-shadow var(--anim-fast) var(--easing),
                border-color var(--anim-fast) var(--easing);
    will-change: transform;
}
.card-hoverable:hover {
    transform: translateY(-0.0625rem);
    box-shadow: var(--shadow-elevated);
}
.card-hoverable:active {
    /* Tactile press: scale ever-so-slightly so the click feels physical. */
    transform: translateY(0) scale(0.995);
    transition-duration: 80ms;
}
.card-hoverable:focus-visible {
    outline: none;
    box-shadow: var(--shadow-gold);
}

/* ── Button press feedback ──
   A subtle scale-down on :active gives every button tactile feedback.
   The transition is defined on the main .btn rule above so there's a single
   source of truth for button timing. */
.btn:active:not(:disabled) {
    transform: scale(0.97);
}
.btn-primary:hover:not(:disabled) {
    /* Subtle upward slide on the primary CTA so the hot button feels alive. */
    transform: translateY(-0.0625rem);
}
.btn-primary:active:not(:disabled) {
    /* Override the upward slide on press so the click feels grounded. */
    transform: translateY(0) scale(0.97);
}

/* Focus ring appears instantly via the static var(--shadow-gold) on
   .form-input:focus - no animation needed. The border-color transition
   already provides the visual handshake. */

/* ── Tab content cross-fade ──
   The Tabs component swaps panel.hidden and toggles .active. Replace the
   instant pop with a soft fade-in so switching tabs feels gentle. */
.tab-panel.active {
    animation: fadeInUp var(--anim-normal) var(--easing) both;
}

/* ── Sidebar nav: sliding gold marker on the active item ──
   We draw a 3px gold bar on the left edge of the active item. The
   ::before pseudo-element scales from 0.4 → 1 vertically so it slides in
   when the user navigates instead of popping. The transition lives on
   transform/opacity (not height) so it's GPU-driven. */
.sidebar-nav-item {
    position: relative;
}
.sidebar-nav-item::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.375rem;
    bottom: 0.375rem;
    width: 0.1875rem;
    background: var(--gold);
    border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
    transform: scaleY(0.4);
    opacity: 0;
    transform-origin: center;
    transition: transform var(--anim-normal) var(--easing-spring),
                opacity var(--anim-fast) var(--easing);
    pointer-events: none;
}
.sidebar-nav-item.active::before {
    transform: scaleY(1);
    opacity: 1;
}

/* ── Top nav links: underline-grow on hover ──
   Subtle gold underline draws under nav search and links when they're
   hovered. Origin is left so it grows from the start of the text. */
/* Nav search focus ring handled by the static box-shadow transition. */

/* ── Smart-list chip / category chip / tab hover scale ──
   A 1.5% scale-up on chip hover gives them a subtle "press me" personality
   without the cartoon energy of a 10% bounce. */
.smart-list-chip,
.insights-category-chip,
.help-section-nav-link {
    transition: transform var(--anim-fast) var(--easing),
                color var(--anim-fast) var(--easing),
                background var(--anim-fast) var(--easing),
                border-color var(--anim-fast) var(--easing);
    will-change: transform;
}
.smart-list-chip:hover,
.insights-category-chip:hover,
.help-section-nav-link:hover {
    transform: translateY(-0.0625rem);
}
.smart-list-chip:active,
.insights-category-chip:active {
    transform: translateY(0) scale(0.96);
}

/* ── Filter pills: spring in / spring out ──
   Pills are added to the filter bar when a filter is set, and removed
   when cleared. The popIn keyframe makes them feel responsive. The
   removed-state is handled by the JS - pills are torn down on click. */
.filter-pill {
    animation: popIn var(--anim-normal) var(--easing) both;
    will-change: transform;
}
.filter-pill .filter-pill-remove {
    transition: transform var(--anim-fast) var(--easing),
                color var(--anim-fast) var(--easing);
}
.filter-pill .filter-pill-remove:hover {
    color: var(--error);
}

/* ── Lead-score gauge sweep ──
   Score gauges are SVG paths with a stroke-dashoffset that fills from
   the empty side to the target offset. The custom properties on the
   path itself drive the keyframe. */
.score-gauge-fill {
    animation: gaugeSweep var(--anim-slow) var(--easing) both;
    will-change: stroke-dashoffset;
}

/* ── Stat-card count-up settle ──
   When UI.animateNumber finishes counting up, it adds .is-settled to the
   stat-card-value element. The drawNumber keyframe fires once for a tiny
   "snap into place" feel. Subtle but premium. */
.stat-card-value.is-settled {
    animation: drawNumber var(--anim-fast) var(--easing) both;
}

/* ── Notification bell badge entrance ──
   When a new notification arrives the dot is given .badge-dot-new.
   Simple popIn entrance - no bounce. */
.badge-dot.badge-dot-new {
    animation: popIn var(--anim-normal) var(--easing);
}

/* ── Skeleton → content cross-fade ──
   When a skeleton is removed and replaced by real content, the new
   content gets .animate-in automatically (handled in the views). The
   skeleton itself gets .is-fading-out which lowers opacity so the swap
   isn't a hard pop. */
.skeleton-card.is-fading-out,
.skeleton-table.is-fading-out,
.skeleton-stats.is-fading-out,
.skeleton.is-fading-out {
    transition: opacity var(--anim-fast) var(--easing);
    opacity: 0;
}

/* ── Mobile menu hamburger morph ──
   The three-line hamburger morphs into an X when the menu is open. The
   button has three <span> children; .active rotates the top and bottom
   into a cross while the middle fades out. */
.mobile-menu-btn span {
    transition: transform var(--anim-normal) var(--easing),
                opacity var(--anim-fast) var(--easing);
    transform-origin: center;
}
.mobile-menu-btn.active span:nth-child(1) {
    transform: translateY(0.5rem) rotate(45deg);
}
.mobile-menu-btn.active span:nth-child(2) {
    opacity: 0;
    transform: scaleX(0);
}
.mobile-menu-btn.active span:nth-child(3) {
    transform: translateY(-0.5rem) rotate(-45deg);
}

/* ── Section divider grow ──
   The Account page section dividers (line - text - line) grow in width
   from the center on first paint. */
.section-divider {
    animation: fadeInUp var(--anim-normal) var(--easing) both;
}

/* ── Reduced-motion override ──
   prefers-reduced-motion: reduce respects the user's accessibility
   choice. We disable every animation we just added so the platform
   stays usable without shifting any layout. */
@media (prefers-reduced-motion: reduce) {
    .animate-in,
    .animate-in-fast,
    .animate-scale-in,
    .animate-slide-right,
    .animate-page-enter,
    .animate-pop,
    .animate-pop-out,
    .main-content > .page,
    .main-content > .page-narrow,
    .tab-panel.active,
    .filter-pill,
    .score-gauge-fill,
    .stat-card-value.is-settled,
    .badge-dot.badge-dot-new,
    .section-divider,
    .form-input:focus,
    .form-input-sm:focus,
    .nav-search-input:focus {
        animation: none;
    }
    .card-hoverable,
    .card-hoverable:hover,
    .card-hoverable:active,
    .btn,
    .btn:active:not(:disabled),
    .btn-primary:hover:not(:disabled),
    .btn-primary:active:not(:disabled),
    .smart-list-chip,
    .smart-list-chip:hover,
    .smart-list-chip:active,
    .insights-category-chip:hover,
    .insights-category-chip:active,
    .help-section-nav-link:hover,
    .filter-pill .filter-pill-remove:hover,
    .mobile-menu-btn span,
    .sidebar-nav-item::before {
        transform: none !important;
        transition: none !important;
    }
}

.surface-elevated { background: var(--surface-elevated); }
.surface-inset { background: var(--surface-inset); }

.text-gradient-gold {
    background: var(--gold-gradient);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
}

/* Skeleton loading variants */
.skeleton-text { height: 0.875rem; border-radius: var(--radius-sm); margin-bottom: 0.5rem; }
.skeleton-text:last-child { width: 60%; }
.skeleton-heading { height: 1.5rem; width: 40%; border-radius: var(--radius-sm); margin-bottom: 0.75rem; }
.skeleton-rect { height: 8rem; border-radius: var(--radius-md); }
.skeleton-circle { border-radius: 50%; }
.skeleton-card {
    background: var(--warm-white);
    border: 0.0625rem solid var(--border-light);
    border-radius: var(--radius-lg);
    padding: 1.25rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   COMMAND PALETTE
   ═══════════════════════════════════════════════════════════════════════════ */

.command-palette-overlay {
    position: fixed;
    inset: 0;
    background: rgba(28, 35, 49, 0.6);
    backdrop-filter: blur(0.25rem);
    z-index: var(--z-modal);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    /* Position the palette near the top of the viewport but never overlap
       the bottom edge. min() picks the smaller of 20vh and 6rem so the
       palette stays useful on a 20rem-tall landscape phone. */
    padding: min(20vh, 6rem) 1rem 1rem;
    animation: fadeInDown var(--anim-fast) var(--easing);
}
.command-palette {
    background: var(--warm-white);
    border: 0.0625rem solid var(--border);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-modal);
    width: 100%;
    max-width: 36rem;
    /* Cap palette height at viewport height minus its top padding so the
       palette never extends below the viewport. */
    max-height: calc(100vh - min(20vh, 6rem) - 1rem);
    max-height: calc(100dvh - min(20vh, 6rem) - 1rem);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: scaleIn var(--anim-fast) var(--easing);
}
.command-palette-input {
    width: 100%;
    padding: 1rem 1.25rem;
    border: none;
    border-bottom: 0.0625rem solid var(--border-light);
    font-family: var(--font-body);
    font-size: 1rem;
    color: var(--text);
    background: transparent;
    outline: none;
    flex-shrink: 0;
}
.command-palette-input::placeholder { color: var(--faint); }
.command-palette-results {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}
.command-palette-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.625rem 1.25rem;
    cursor: pointer;
    transition: background var(--anim-fast);
    font-size: 0.875rem;
    color: var(--text-secondary);
}
.command-palette-item:hover,
.command-palette-item.highlighted { background: var(--bg-hover); color: var(--text); }
.command-palette-item-icon {
    width: 1.5rem;
    text-align: center;
    font-size: 1rem;
    opacity: 0.5;
    flex-shrink: 0;
}
.command-palette-item-label { flex: 1; font-weight: var(--weight-medium); }
.command-palette-item-hint {
    font-size: var(--text-xs);
    color: var(--faint);
    font-family: var(--font-mono);
}
.command-palette-footer {
    padding: 0.5rem 1.25rem;
    border-top: 0.0625rem solid var(--border-light);
    display: flex;
    gap: 1rem;
    font-size: var(--text-xs);
    color: var(--faint);
}
.command-palette-footer kbd {
    background: var(--bg-subtle);
    border: 0.0625rem solid var(--border-light);
    border-radius: var(--radius-sm);
    padding: 0 0.25rem;
    font-family: var(--font-mono);
    font-size: 0.625rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   NOTIFICATION DROPDOWN
   ═══════════════════════════════════════════════════════════════════════════ */

/* Notification dropdown - viewport-anchored so it ALWAYS fits no matter
   how wide the top nav is. Previous implementation used position:absolute
   inside .nav-actions which overflowed off the right edge of the screen
   on every viewport below ~87.5rem. The new rules pin the dropdown to the
   viewport's top-right with proper safe-area gutters and a max-height
   that respects the viewport height minus the top-nav height. */
.notification-dropdown {
    display: none;
    position: fixed;
    top: calc(var(--top-nav-h) + 0.5rem);
    right: 0.75rem;
    left: auto;
    width: min(22rem, calc(100vw - 1.5rem));
    max-height: calc(100vh - var(--top-nav-h) - 1.5rem);
    max-height: calc(100dvh - var(--top-nav-h) - 1.5rem);
    background: var(--warm-white);
    border: 0.0625rem solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-modal);
    /* Nav-priority panel tier - when the bell is pressed the panel
       always overlaps every other floating UI on the page (modals,
       toasts, cookie banner). Paired with `.nav-user-dropdown` at the
       same level; both sit one step above the top-nav (9500). */
    z-index: 9510;
    overflow: hidden;
    flex-direction: column;
}
.notification-dropdown.active {
    display: flex;
    animation: fadeInDown var(--anim-fast) var(--easing);
}
.notification-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.875rem 1rem;
    border-bottom: 0.0625rem solid var(--border-light);
    flex-shrink: 0;
    background: linear-gradient(180deg, var(--surface-card) 0%, var(--bg-subtle) 100%);
}
.notification-header-title {
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: var(--weight-bold);
    color: var(--slate);
}
.notification-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}
.notification-item {
    /* The unread dot (::before) sits beside the text content. The text
       content itself stacks vertically - title, body, time - inside a
       child column. Without `flex-direction: column` on that inner
       wrapper the title/body/time were laid out side-by-side, each
       getting ~5rem of width, which made every word wrap onto its own
       line. */
    display: flex;
    align-items: flex-start;
    gap: 0.625rem;
    padding: 0.75rem 1rem;
    border-bottom: 0.0625rem solid var(--border-light);
    transition: background var(--anim-fast);
    cursor: pointer;
}
.notification-item:hover { background: var(--bg-hover); }
.notification-item:last-child { border-bottom: none; }
.notification-item.unread { background: rgba(181, 148, 42, 0.03); }
.notification-item.unread::before {
    content: '';
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 50%;
    background: var(--gold);
    flex-shrink: 0;
    margin-top: 0.5rem;
}
.notification-item-content {
    flex: 1 1 auto;
    min-width: 0;           /* allow the text to wrap normally instead of overflowing */
    display: flex;
    flex-direction: column;
    gap: 0.1875rem;
}
.notification-item-title {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text);
    line-height: 1.35;
    word-wrap: break-word;
    overflow-wrap: anywhere;
}
.notification-item-body {
    font-size: 0.8125rem;
    color: var(--muted);
    line-height: 1.45;
    word-wrap: break-word;
    overflow-wrap: anywhere;
}
.notification-item-text { font-size: 0.75rem; color: var(--muted); margin-top: 0.125rem; }
.notification-item-time {
    font-size: var(--text-xs);
    color: var(--faint);
    margin-top: 0.125rem;
}
.notification-empty {
    text-align: center;
    padding: 2rem;
    color: var(--muted);
    font-size: var(--text-sm);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SIDEBAR COLLAPSE
   ═══════════════════════════════════════════════════════════════════════════ */

/* Responsive collapse/expand arrow. One control, three behaviors driven
   by the body's sidebar-mode class:
     drawer mode → closes the drawer (chevron points left)
     rail mode   → expands to full (chevron points right)
     full mode   → collapses to rail (chevron points left)
   The SVG starts pointing left and is rotated 180° in rail mode. */
.sidebar-collapse-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;
    width: 1.75rem;
    height: 1.75rem;
    margin: 0.5rem 0 calc(0.5rem + var(--safe-bottom, 0rem));
    padding: 0;
    border: 0.0625rem solid var(--border-light);
    border-radius: 62.4375rem;
    background: var(--white);
    color: var(--muted);
    cursor: pointer;
    flex-shrink: 0;
    box-shadow: var(--shadow-sm);
    transition: color var(--anim-fast) var(--easing),
                background var(--anim-fast) var(--easing),
                border-color var(--anim-fast) var(--easing),
                transform var(--anim-fast) var(--easing),
                box-shadow var(--anim-fast) var(--easing);
    -webkit-tap-highlight-color: transparent;
}
.sidebar-collapse-btn svg {
    width: 0.9375rem;
    height: 0.9375rem;
    display: block;
    transition: transform var(--anim-normal) var(--easing);
}
/* Rail mode: rotate the chevron so it points right (expand) */
body.sb-rail .sidebar-collapse-btn svg { transform: rotate(180deg); }

.sidebar-collapse-btn:hover {
    background: var(--slate);
    color: #fff;
    border-color: var(--slate);
    box-shadow: var(--shadow-md);
}
.sidebar-collapse-btn:active { transform: scale(0.94); }
.sidebar-collapse-btn:focus-visible {
    outline: none;
    border-color: var(--gold);
    box-shadow: 0 0 0 0.1875rem var(--gold-muted), var(--shadow-sm);
}

/* In rail mode the footer is hidden, but we still want the arrow. Keep
   it positioned at the very bottom of the rail with a generous margin. */
body.sb-rail .sidebar-collapse-btn {
    margin-top: auto;
    margin-bottom: calc(0.625rem + var(--safe-bottom, 0rem));
}

/* ═══════════════════════════════════════════════════════════════════════════
   PRINT STYLESHEET
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Print chrome - hidden on screen. The @media print block below
     promotes them to a running header + footer (on every page) plus a
     one-shot cover block (first page only). */
.print-running-header,
.print-running-footer,
.print-cover { display: none; }

@media print {
    @page {
        size: Letter;
        margin: 1in 0.55in 0.7in 0.55in;
    }
    /* First page gets a larger top margin so the extended cover block
       has room to breathe above the body content. */
    @page :first {
        margin-top: 0.75in;
    }

    /* Hide all interactive app chrome. */
    .top-nav,
    .sidebar-nav,
    .sidebar-collapse-btn,
    .prop-action-bar,
    .page-actions,
    .toast-container,
    .modal-overlay,
    .command-palette-overlay,
    .mobile-nav-overlay,
    .boot-splash,
    button,
    .btn,
    input[type="button"],
    input[type="submit"],
    .map-controls-rail,
    #viewer-3d-fullscreen,
    .info-panel-actions,
    .popup-actions,
    .split-toggle,
    [data-no-print] { display: none !important; }

    /* ── Running header - every page, top edge ──
       Previously stamped the full Blue Belmont wordmark SVG on every
       page header. Combined with the cover logo on page 1, a 5-page
       report ended up with 5 visible logos - institutional research
       it didn't look like. Drop to a text-only running line: the
       cover carries the prominent brand mark; every subsequent page
       gets a single tight serif "Blue Belmont" wordmark in text, the
       report title, and the date. One logo image per report, period. */
    .print-running-header {
        display: flex !important;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        align-items: center;
        justify-content: space-between;
        padding: 0.12in 0.55in 0.08in;
        border-bottom: 0.5pt solid #b5942a;
        background: #fff;
        height: 0.4in;
        box-sizing: border-box;
        z-index: 9999;
    }
    /* Suppress every brand mark on the running header. The cover
       carries the single horizontal logo for the whole report - no
       duplicate text wordmark on every subsequent page. The title +
       date in .print-running-meta still anchor the running header. */
    .print-running-logo { display: none; }
    .print-running-meta { display: flex; flex-direction: column; align-items: flex-end; text-align: right; }
    .print-running-title {
        font-family: 'Source Sans 3', Arial, sans-serif;
        font-weight: 600;
        font-size: 9pt;
        color: #4a4842;
        line-height: 1.1;
    }
    .print-running-date {
        font-size: 7.5pt;
        color: #706b63;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        margin-top: 0.03in;
    }

    /* ── Running footer - every page, bottom edge ── */
    .print-running-footer {
        display: flex !important;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        justify-content: space-between;
        align-items: center;
        padding: 0.08in 0.55in 0.15in;
        border-top: 0.5pt solid #cfcfcf;
        font-family: 'Source Sans 3', Arial, sans-serif;
        font-size: 7.5pt;
        color: #706b63;
        letter-spacing: 0.05em;
        background: #fff;
        height: 0.35in;
        box-sizing: border-box;
    }
    /* Browser-native print dialogs already stamp "Page X of Y" on every
       sheet. Don't duplicate it - CSS counter(page) inside a fixed
       footer rendered "Page 0" on every page across most engines. */
    .print-page-number { display: none !important; }

    /* ── Cover block - only on page 1, inline at top of body ── */
    .print-cover {
        display: flex !important;
        flex-direction: column;
        padding: 0.2in 0.3in 0.3in;
        border: 0.5pt solid #e6e1d7;
        border-left: 3pt solid #b5942a;
        border-radius: 2pt;
        background: #fdfbf6;
        margin: 0 0 0.25in;
        page-break-after: avoid;
        break-after: avoid;
    }
    .print-cover-logo {
        /* Horizontal Blue Belmont mark (viewBox 1400x340, ratio 4.12:1).
           2.4in wide reads strongly on the cover without dominating
           the eyebrow + title that follow. This is the ONE brand
           mark in the whole report - running header, body, and
           property.css all suppress their own wordmarks so the
           printed page never duplicates it. */
        width: 2.4in;
        height: auto;
        margin-bottom: 0.12in;
    }
    .print-cover-eyebrow {
        font-family: 'Source Sans 3', Arial, sans-serif;
        font-size: 8pt;
        font-weight: 600;
        letter-spacing: 0.12em;
        text-transform: uppercase;
        color: #7a6315;
        margin-bottom: 0.05in;
    }
    .print-cover-title {
        font-family: 'Playfair Display', Georgia, serif;
        font-weight: 700;
        font-size: 22pt;
        line-height: 1.1;
        color: #1c2331;
        margin: 0 0 0.04in;
    }
    .print-cover-subtitle {
        font-size: 10pt;
        color: #4a4842;
        letter-spacing: 0.03em;
        margin-bottom: 0.1in;
    }
    .print-cover-subtitle:empty { display: none; }
    .print-cover-divider {
        height: 0.5pt;
        background: #b5942a;
        width: 100%;
        margin: 0.08in 0 0.12in;
        opacity: 0.4;
    }
    .print-cover-meta { display: flex; flex-direction: column; gap: 0.04in; }
    .print-cover-meta-row { display: flex; font-size: 9pt; }
    .print-cover-meta-label {
        width: 0.9in;
        font-weight: 600;
        color: #706b63;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        font-size: 7.5pt;
    }
    .print-cover-meta-val { color: #1c2331; }
    .print-cover-confidential {
        margin-top: 0.12in;
        font-size: 7.5pt;
        color: #706b63;
        letter-spacing: 0.05em;
        text-transform: uppercase;
        font-style: italic;
    }

    /* ── Body layout ── */
    body {
        background: #fff !important;
        overflow: visible;
        height: auto;
        color: #1c2331;
        font-family: 'Source Sans 3', Arial, sans-serif;
        font-size: 9.5pt;
        line-height: 1.4;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
        margin: 0 !important;
        padding: 0 !important;
    }
    .app-layout { height: auto !important; padding-top: 0 !important; display: block !important; }
    .main-content {
        margin-left: 0 !important;
        height: auto !important;
        overflow: visible !important;
        background: #fff !important;
    }
    .page, .page-wide, .page-full {
        max-width: 100% !important;
        padding: 0 !important;
    }

    /* ── Typography ── */
    h1, h2, h3, h4, h5 {
        font-family: 'Playfair Display', Georgia, serif;
        color: #1c2331;
        break-after: avoid;
        page-break-after: avoid;
    }
    h1 { font-size: 16pt; margin: 0.08in 0 0.08in; }
    h2 { font-size: 12.5pt; margin: 0.16in 0 0.06in; padding-bottom: 0.03in; border-bottom: 0.5pt solid #e6e1d7; }
    h3 { font-size: 10.5pt; margin: 0.1in 0 0.04in; }
    p { margin: 0 0 0.06in; }
    a { color: #1c2331; text-decoration: none; }
    a[href^="http"]::after,
    a[href^="mailto:"]::after { content: " (" attr(href) ")"; font-size: 0.8em; color: #706b63; }

    /* ── Cards & sections ── */
    .card, .prop-section, .prop-lux-intel, .prop-lux-rail-card,
    .info-grid, .prop-lux-kv-grid, .dash-card {
        break-inside: avoid;
        page-break-inside: avoid;
        box-shadow: none !important;
        border: 0.5pt solid #d8d3c8 !important;
        background: #fff !important;
        padding: 0.12in !important;
        margin: 0 0 0.1in !important;
        border-radius: 0 !important;
    }
    .prop-lux-hero { background: #fdfbf6 !important; }

    /* ── Tables - the workhorse for printed data ── */
    table {
        width: 100% !important;
        border-collapse: collapse;
        font-size: 8.5pt;
        margin: 0.06in 0 0.1in;
    }
    th, td {
        padding: 0.045in 0.07in;
        border-bottom: 0.25pt solid #d8d3c8;
        text-align: left;
        vertical-align: top;
    }
    th {
        background: #f7f3ea !important;
        font-weight: 700;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        font-size: 7.5pt;
        color: #4a4842;
        border-bottom: 0.75pt solid #b5942a;
    }
    thead { display: table-header-group; }
    tfoot { display: table-footer-group; }
    tr, img, svg { break-inside: avoid; page-break-inside: avoid; }
    img { max-width: 100%; height: auto; }

    /* ── Metric / KPI tiles - never truncate in print ──
       The screen styles clip big numbers with overflow:hidden + ellipsis +
       nowrap. In print we force-show the full value. Lower font size to fit. */
    .stat-card {
        padding: 0.1in 0.12in !important;
        border: 0.5pt solid #d8d3c8 !important;
        box-shadow: none !important;
        break-inside: avoid;
        page-break-inside: avoid;
        overflow: visible !important;
    }
    .stat-card-value {
        font-family: 'Source Sans 3', Arial, sans-serif !important;
        font-size: 13pt !important;
        line-height: 1.15 !important;
        overflow: visible !important;
        text-overflow: clip !important;
        white-space: normal !important;
        word-break: break-word !important;
        font-variant-numeric: tabular-nums;
    }
    .stat-card-label {
        font-size: 7pt !important;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: #706b63 !important;
        white-space: normal !important;
    }
    .stat-card-sub { font-size: 7.5pt !important; color: #706b63 !important; }
    /* Metric grid - 4 across max so numbers have breathing room */
    .grid-5, .grid-4 {
        display: grid !important;
        grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
        gap: 0.08in !important;
    }
    .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
    .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }

    /* Any element clipping text on screen - release the clamp for print. */
    .truncate, .ellipsis,
    .table-cell-truncate,
    [class*="truncate"] {
        overflow: visible !important;
        text-overflow: clip !important;
        white-space: normal !important;
        word-break: break-word !important;
    }

    /* Charts - previously hidden entirely because raster bar charts
       printed as blurry cartoons. Now that distributions use clean SVG
       donuts, let them through so the printed page actually contains the
       data it advertises (empty chart cards were showing up before).
       Canvases are still hidden - canvas content doesn't scale on print.
       SVG donuts/pies (rendered by UI.DonutChart) print at vector fidelity. */
    canvas { display: none !important; }
    svg { max-width: 100%; height: auto; }
    .chart-summary, .chart-fallback { display: block !important; }

    /* Key-value grid - 2-column print layout for any info-grid-style block */
    .info-grid, .prop-lux-kv-grid {
        display: grid !important;
        grid-template-columns: 1fr 1fr !important;
        gap: 0.08in 0.2in !important;
    }
    .info-grid-cell-label, .prop-lux-kv-label {
        font-size: 7pt !important;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: #706b63 !important;
        white-space: normal !important;
    }
    .info-grid-cell-value, .prop-lux-kv-value,
    .prop-lux-value-gap-value,
    .prop-lux-stat-label, .prop-lux-stat-value, .prop-lux-stat-sub {
        font-size: 10pt !important;
        color: #1c2331 !important;
        overflow: visible !important;
        text-overflow: clip !important;
        white-space: normal !important;
        word-break: break-word !important;
    }
    .prop-lux-stat-value { font-size: 12pt !important; font-weight: 600 !important; }
    .prop-lux-stat-label { font-size: 7pt !important; color: #706b63 !important; }
    .prop-lux-stat-sub   { font-size: 7.5pt !important; color: #706b63 !important; }
    .prop-lux-rail, .prop-lux-tabs, .prop-lux-tab-nav { display: none !important; }
    .prop-lux-stat, .prop-lux-stat-row {
        padding: 0.08in 0.1in !important;
        border: 0.5pt solid #d8d3c8 !important;
        box-shadow: none !important;
    }

    /* Badges - print as inline labels, not rounded color chips */
    .badge {
        background: transparent !important;
        border: 0.5pt solid #b5942a !important;
        color: #1c2331 !important;
        font-size: 7.5pt !important;
        padding: 0 0.04in !important;
        border-radius: 1pt !important;
    }

    /* Explicit print-only / screen-only utilities any view can use. */
    .print-only { display: block !important; }
    .no-print { display: none !important; }
}

/* Hide helpers from screen - only visible in the print layout. */
.print-only { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   BOOT SPLASH
   The first thing every user sees, every session. Branded mark, wordmark,
   indeterminate progress bar, and a status line that says what we're loading.
   Replaces the bare spinner that used to ship.
   ═══════════════════════════════════════════════════════════════════════════ */
.boot-splash {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    background:
        radial-gradient(ellipse at top, rgba(212, 175, 55, 0.06) 0%, transparent 50%),
        radial-gradient(ellipse at bottom, rgba(28, 35, 49, 0.04) 0%, transparent 50%),
        var(--cream);
    animation: fadeInUp var(--anim-slow) var(--easing) both;
}
.boot-splash-logo {
    display: block;
    height: clamp(2rem, 1.25rem + 2.5vw, 3.25rem);
    width: auto;
    max-width: min(22rem, 70vw);
}
.boot-splash-tagline {
    font-family: var(--font-body);
    font-weight: var(--weight-semibold);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--gold-dark);
    text-align: center;
    max-width: 32rem;
}
.boot-splash-progress {
    position: relative;
    width: 14rem;
    height: 0.1875rem;
    background: rgba(181, 148, 42, 0.12);
    border-radius: var(--radius-pill);
    overflow: hidden;
    margin-top: 0.5rem;
}
.boot-splash-progress-bar {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 35%;
    background: linear-gradient(90deg, transparent 0%, var(--gold) 50%, transparent 100%);
    border-radius: var(--radius-pill);
    animation: bootProgress 1600ms var(--easing) infinite;
}
@keyframes bootProgress {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(385%); }
}
.boot-splash-status {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--muted);
    letter-spacing: 0.04em;
    transition: opacity 180ms ease;
    max-width: 28rem;
    text-align: center;
    line-height: 1.4;
}

/* Reduced motion: don't animate the mark or progress bar pulse. */
@media (prefers-reduced-motion: reduce) {
    .boot-splash-mark { animation: none; }
    .boot-splash-progress-bar { animation: none; transform: none; width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ALIGNMENT POLISH LAYER
   ───────────────────────────────────────────────────────────────────────────
   The bottom-of-stylesheet layer that cleans up alignment edge cases that
   are easier to fix globally than to chase through every component file.
   Each rule below has a documented purpose so it doesn't get pruned by a
   well-meaning refactor.
   ═══════════════════════════════════════════════════════════════════════════ */

/* 1. Universal min-width:0 on flex/grid children - without this, a long
      truncated label or an oversized SVG can blow out a flex/grid track
      and force horizontal scrolling on a card that should fit. */
.flex > *,
.grid > * { min-width: 0; }

/* 2. SVG icons are block elements by default and add a phantom 0.25rem gap
      under the baseline. Force them to inline-block + middle-align so
      they sit cleanly inside a row of text + buttons. */
svg { vertical-align: middle; }
.btn svg,
.tab svg,
.btn-icon svg,
.badge svg { display: inline-block; flex-shrink: 0; }

/* 3. The shared icon helper used by components.js (UI.iconHTML) - make
      sure it always vertically centers in a flex parent regardless of the
      surrounding font baseline. */
.ui-icon, .ui-icon-wrap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    vertical-align: middle;
}

/* 4. Page container - establish a uniform horizontal gutter that respects
      iOS safe-area insets so a notch / home indicator never clips
      content on the bleeding edge of the layout. */
.page {
    padding: var(--space-6);
    padding-left: max(var(--space-6), env(safe-area-inset-left));
    padding-right: max(var(--space-6), env(safe-area-inset-right));
    padding-bottom: max(var(--space-6), env(safe-area-inset-bottom));
    max-width: 100%;
    overflow-x: hidden;
}

/* 5. Page header - keep title and action group on one row at desktop,
      stack at tablet/phone, and never let a long title push the actions
      off the right edge. */
.page-header {
    align-items: center;
}
.page-header > :first-child { min-width: 0; flex: 1; }
.page-header .page-title { line-height: var(--leading-tight); }

/* 6. Stat card - value + label baseline. The original .stat-card-value
      uses font-mono with line-height 1.2; without an explicit padding
      pattern, two stat cards in a grid can have their values land on
      slightly different y positions. The block grid below makes them
      line up perfectly. */
.stat-card {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    min-height: 6rem;
    gap: var(--space-2);
}
.stat-card-value { font-variant-numeric: tabular-nums; }

/* 7. Toolbar wrap - when a row of toolbar controls wraps onto two lines,
      give the second line the same gap as the first. */
.toolbar,
.table-toolbar { row-gap: var(--space-2); }

/* 8. Inline action group - a row of small buttons (e.g. inside a card
      header). Forces equal-height children regardless of variant mix. */
.action-group {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.action-group .btn,
.action-group .btn-icon { vertical-align: middle; }

/* 9. Page-fill containers - explorer + map + 3D viewer routes use
      ``.page-full``. Make sure they actually fill the viewport below the
      top nav and don't accidentally inherit a padding from .page. */
.page-full {
    padding: 0;
    height: calc(100vh - var(--top-nav-h));
    height: calc(100dvh - var(--top-nav-h));
    overflow: hidden;
}

/* 10. Truncate utility - single-line ellipsis that works inside a flex
       child (the missing min-width:0 used to break it). */
.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* 11. KV grid (definition list-ish) - used on the property detail page
       and elsewhere. Force consistent vertical spacing. */
.kv-list { display: flex; flex-direction: column; gap: var(--space-2); }
.kv-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: 1.5rem;
}
.kv-label {
    font-size: var(--text-xs);
    color: var(--muted);
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    flex-shrink: 0;
}
.kv-value {
    font-size: var(--text-sm);
    color: var(--text);
    font-weight: var(--weight-semibold);
    text-align: right;
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* 12. Mobile responsive page padding - at narrow viewports the .page
       padding shrinks so dense data tables can use the full screen. */
@media (max-width: 40em) {
    .page { padding: var(--space-3); }
}
@media (max-width: 30em) {
    .page { padding: var(--space-3) var(--space-2); }
}

/* 13. Final responsive sweep - at common phone widths every primitive
       gets a sane fallback. Locks alignment from 20rem up. */
@media (max-width: 22.5em) {
    /* bp-2xs: micro phone floor (360px). Merges the old 23.4375em block
       that was one pixel off the official scale. */
    :root { font-size: 0.875rem; --control-px: 0.75rem; --control-px-sm: 0.5rem; }
    .page { padding: 0.5rem; }
    .top-nav { gap: 0.5rem; }
    .nav-user-btn { padding: 0.25rem; }
    .nav-user-chevron { display: none; }
    .page-title { font-size: 1.25rem; }
    .badge { font-size: var(--text-2xs); padding: 0 0.375rem; }
    .data-table th, .data-table td {
        padding-left: var(--space-3);
        padding-right: var(--space-3);
    }
}

/* 14. Long-content overflow safety net - anywhere a record is rendered
       with potentially-long business names, parcel ids, addresses, or
       legal descriptions, the container needs to break inside a word
       as a last resort instead of overflowing its row. This rule applies
       to every text-bearing primitive that doesn't already opt into
       single-line truncation. */
.card-title,
.modal-title,
.empty-state-title,
.empty-state-text,
.prop-lux-section-title,
.prop-lux-section-subtitle,
.popup-value,
.notification-item-text,
.dash-metric-sub,
.kv-row .kv-label {
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* 15. Universal overflow guard for non-truncating text containers in
       a flex/grid context. ``min-width: 0`` is the magic that lets a
       child shrink below its intrinsic content width - without it the
       child wins the size war and pushes the parent past its bounds. */
.flex > *,
.split-left > *,
.split-right > * {
    min-width: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   16. LABEL PRESETS - reusable micro-label typography

   The same uppercase, small-caps, letter-spaced pattern appears 49+ times
   across property.css, components.css, and dashboard.css. Two tiers cover
   every existing case:

     .label-2xs  - tightest (0.5625rem / bold / 0.06em) for stat strips,
                   score gauges, factor pills, bar labels.
     .label-xs   - standard  (0.6875rem / semibold / 0.04em) for card
                   headers, data labels, form section titles.

   Component selectors like .prop-char-label still exist for scoping but
   should inherit one of these presets instead of redeclaring the quartet.
   ═══════════════════════════════════════════════════════════════════════════ */

.label-2xs {
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--faint);
}
.label-xs {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
   17. LAYOUT UTILITIES - high-frequency patterns extracted from inline JS

   These appear 5+ times as inline styles or cssText across the JS views.
   Centralising them means the map explorer, dashboard, lists, and search
   views can reference a class instead of hardcoding the same rule.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Full-height split layout - used by search, lists, explorer, analytics. */
.full-height {
    height: calc(100vh - var(--top-nav-h));
    height: calc(100dvh - var(--top-nav-h));
    overflow: hidden;
}

/* Flex row shortcuts - replace the 20+ inline ``display:flex;
   align-items:center; justify-content:space-between`` blocks. */
.flex-between {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.flex-center {
    display: flex;
    align-items: center;
    justify-content: center;
}
.flex-col {
    display: flex;
    flex-direction: column;
}

/* Cursor - 14 inline ``cursor:pointer`` occurrences in JS views. */
.cursor-pointer { cursor: pointer; }

/* ═══════════════════════════════════════════════════════════════════════════
   18. SPACING UTILITIES - token-backed margin / padding shorthands

   Replace the 40+ inline ``style="margin-bottom:1rem"`` / ``padding:2rem``
   blocks scattered through JS template strings.
   ═══════════════════════════════════════════════════════════════════════════ */

.p-xs  { padding: var(--space-1); }
.p-sm  { padding: var(--space-2); }
.p-md  { padding: var(--space-4); }
.p-lg  { padding: var(--space-6); }
.px-sm { padding-left: var(--space-2); padding-right: var(--space-2); }
.px-md { padding-left: var(--space-4); padding-right: var(--space-4); }
.py-sm { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-md { padding-top: var(--space-4); padding-bottom: var(--space-4); }

/* ═══════════════════════════════════════════════════════════════════════════
   19. TYPOGRAPHY UTILITIES - fill gaps in the existing .text-* classes
   ═══════════════════════════════════════════════════════════════════════════ */

.font-mono { font-family: var(--font-mono); }
.font-display { font-family: var(--font-display); }
.tabular-nums { font-variant-numeric: tabular-nums; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-uppercase { text-transform: uppercase; letter-spacing: 0.04em; }
