  :root{
    /* Neutral grays (R=G=B), not the old blue-tinted ones (e.g.
       #131317 - blue channel a few points higher than red/green across
       the whole palette) - matches the map's own actual neutral gray
       tiles instead of clashing with them. */
    --bg: #0a0a0a;
    --panel: #141414;
    --panel-2: #1a1a1a;
    --border: #262626;
    --text: #eaeaea;
    --muted: #868686;
    --new: #ff8f52;
    --upcoming: #ff8f52;
    --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  }
  *{box-sizing:border-box;}
  html,body{margin:0;padding:0;height:100%;background:var(--bg);color:var(--text);font-family:var(--font);overflow:hidden;}

  /* Dark scrollbars - Chrome/Edge/Safari */
  ::-webkit-scrollbar{width:10px;height:10px;}
  ::-webkit-scrollbar-track{background:var(--panel);}
  ::-webkit-scrollbar-thumb{background:#3a3a40;border-radius:5px;border:2px solid var(--panel);}
  ::-webkit-scrollbar-thumb:hover{background:#4a4a52;}

  /* Dark scrollbars - Firefox */
  *{scrollbar-width:thin;scrollbar-color:#3a3a40 var(--panel);}

  .app{display:grid;grid-template-columns:340px 1fr;height:100vh;height:100dvh;position:relative;gap:12px;padding:12px;box-sizing:border-box;}

  /* SIDEBAR */
  /* overflow-y:auto is the fallback for when the filters above the list
     (status toggles, shortlist, school type, search boxes) are alone
     taller than the sidebar - without it, flexbox just squeezes .manifest
     down toward nothing rather than ever letting the sidebar scroll, so
     the leads list (and any filters below the fold) become unreachable.
     Harmless in the normal case: if everything already fits, there's
     nothing to scroll and .manifest's own internal scroll still handles
     a long lead list exactly as before. */
  .sidebar{background:var(--panel);border:1px solid var(--border);border-radius:16px;display:flex;flex-direction:column;min-height:0;overflow-y:auto;transform:translateZ(0);}
  /* Everything the resize handle collapses/expands (status filters,
     shortlist, school type, search boxes). overflow-y:auto rather than
     hidden - collapsing it below its natural content height still needs
     to leave every filter reachable via a scroll, not clip them away
     with no way back short of dragging the handle back out. The
     border-bottom is a fixed visual boundary regardless of scroll
     position, so a filter row cut off mid-scroll never bleeds directly
     into the manifest header below it. Height is only ever set inline by
     the drag handler below; unset (auto) until then. */
  .filters-wrapper{overflow-y:auto;flex-shrink:0;border-bottom:1px solid var(--border);transform:translateZ(0);}
  .brand{padding:22px 20px 16px;border-bottom:1px solid var(--border);}
  .brand-name{font-family:var(--font);font-weight:700;font-size:20px;letter-spacing:-0.02em;display:flex;align-items:center;gap:8px;color:inherit;text-decoration:none;}
  .brand-name span{color:var(--new);}
  .brand-sub{font-family:var(--font);font-size:10.5px;color:var(--muted);letter-spacing:0.08em;text-transform:uppercase;margin-top:4px;}
  /* The logo mark's center dot - a slow, subtle breathing pulse rather
     than a blinking alert, since it's brand chrome shown constantly,
     not a status signal. */
  @keyframes logo-dot-pulse{0%,100%{transform:scale(1);opacity:1;}50%{transform:scale(1.4);opacity:0.7;}}
  .logo-dot{transform-box:fill-box;transform-origin:center;animation:logo-dot-pulse 2.4s ease-in-out infinite;}
  @media (prefers-reduced-motion:reduce){.logo-dot{animation:none;}}

  .filters{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;flex-direction:column;gap:8px;}
  .filter-label{font-family:var(--font);font-size:10px;color:var(--muted);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:4px;}

  .search-box{padding:14px 20px;border-bottom:1px solid var(--border);position:relative;}
  .category-input{width:100%;background:var(--panel-2);border:1px solid var(--border);border-radius:6px;padding:8px 12px;color:var(--text);font-family:var(--font);font-size:12.5px;outline:none;box-sizing:border-box;margin-top:8px;}
  .category-input::placeholder{color:var(--muted);}
  .category-input:focus{border-color:#4a4a52;}

  .toolbar-row{display:flex;align-items:center;justify-content:space-between;padding:0 20px 10px;}
  .freshness{font-family:var(--font);font-size:9.5px;color:var(--muted);letter-spacing:0.04em;}
  .export-btn{font-family:var(--font);font-size:10px;letter-spacing:0.06em;text-transform:uppercase;color:var(--muted);background:var(--panel-2);border:1px solid var(--border);border-radius:5px;padding:4px 9px;cursor:pointer;}
  .export-btn:hover{color:var(--text);border-color:#3a3a42;}
  .sort-select{font-family:var(--font);font-size:9.5px;letter-spacing:0.04em;color:var(--muted);background:var(--panel-2);border:1px solid var(--border);border-radius:5px;padding:4px 6px;cursor:pointer;outline:none;}
  .sort-select:hover{color:var(--text);border-color:#3a3a42;}
  .entry-enrollment{font-family:var(--font);font-size:9.5px;color:var(--muted);margin-left:6px;}
  .search-input{width:100%;background:var(--panel-2);border:1px solid var(--border);border-radius:6px;padding:9px 34px 9px 12px;color:var(--text);font-family:var(--font);font-size:13px;outline:none;transition:border-color .15s ease;box-sizing:border-box;}
  .search-input::placeholder{color:var(--muted);}
  .search-input:focus{border-color:#4a4a52;}
  .search-icon{position:absolute;right:32px;top:23px;width:14px;height:14px;color:var(--muted);pointer-events:none;}
  .search-status{font-family:var(--font);font-size:10.5px;color:var(--muted);margin-top:6px;min-height:14px;}
  .search-status.error{color:#ff6b6b;}

  /* Login overlay */
  .login-overlay{position:fixed;inset:0;background:var(--bg);z-index:2000;display:flex;align-items:center;justify-content:center;}
  .login-box{width:340px;background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:28px;}
  .login-brand{font-family:var(--font);font-weight:700;font-size:22px;margin-bottom:2px;display:flex;align-items:center;gap:8px;}
  .login-brand span{color:var(--new);}
  .login-sub{font-family:var(--font);font-size:10.5px;color:var(--muted);letter-spacing:0.08em;text-transform:uppercase;margin-bottom:22px;}
  .login-field{margin-bottom:12px;}
  .login-field label{display:block;font-size:12px;color:var(--muted);margin-bottom:5px;}
  .login-field input{width:100%;background:var(--panel-2);border:1px solid var(--border);border-radius:6px;padding:9px 12px;color:var(--text);font-family:var(--font);font-size:13px;outline:none;box-sizing:border-box;}
  .login-field input:focus{border-color:#4a4a52;}
  .login-field select{width:100%;background:var(--panel-2);border:1px solid var(--border);border-radius:6px;padding:9px 12px;color:var(--text);font-family:var(--font);font-size:13px;outline:none;box-sizing:border-box;}
  .login-field select:focus{border-color:#4a4a52;}
  .login-remember{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--muted);cursor:pointer;user-select:none;margin-top:2px;}
  .login-remember input{cursor:pointer;}
  .login-btn{width:100%;padding:10px;border-radius:6px;border:none;background:var(--new);color:#0a0a0c;font-weight:600;font-size:13px;cursor:pointer;margin-top:6px;}
  .login-btn:hover{opacity:0.9;}
  .login-btn:disabled{opacity:0.5;cursor:default;}
  .login-toggle{text-align:center;margin-top:14px;font-size:12px;color:var(--muted);cursor:pointer;}
  .login-toggle:hover{color:var(--text);}
  .login-status{font-size:12px;margin-top:10px;min-height:16px;color:var(--muted);}
  .login-status.error{color:#ff6b6b;}
  .login-status.success{color:var(--existing,#9a9aa5);}
  .app.hidden{display:none;}

  /* Lives in the sidebar's own brand row now, not floating over the map -
     that spot collided with the Total/New/Upcoming stat panel occupying
     the same corner (the settings/sign-out buttons were getting rendered
     UNDER it, only visible for a flash before the stat panel mounted on
     reload). Sign out moved into the Settings panel itself instead of
     sitting next to this as its own separate top-level button. */
  .brand-header{display:flex;align-items:center;justify-content:space-between;}
  .settings-btn{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--text);cursor:pointer;font-size:14px;background:var(--panel-2);border:1px solid var(--border);flex-shrink:0;}
  .settings-btn:hover{border-color:#4a4a52;background:#26262c;}

  /* Settings is a real dialog now, not a full-page takeover: there
     isn't enough content here (an identity summary, one toggle, a key,
     a sign-out) to justify a whole screen, and every full-page attempt
     just produced empty space or arbitrary column-splitting to fill
     it. A modal sized to its own content, over the dimmed map, has no
     leftover space to fill in the first place. */
  .settings-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.65);backdrop-filter:blur(3px);z-index:2000;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box;}
  /* Two-layer shadow (a tight contact shadow + a wide ambient one) plus
     a faint inset top highlight reads as a genuinely raised object,
     versus a single flat shadow which looks pasted-on at this size. */
  .settings-modal{width:100%;max-width:520px;max-height:min(720px,90vh);background:var(--panel);border-radius:20px;box-shadow:0 8px 16px -8px rgba(0,0,0,.5),0 32px 80px -20px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.04);display:flex;flex-direction:column;overflow:hidden;}
  .settings-modal-header{padding:28px 32px 20px;border-bottom:1px solid var(--border);flex-shrink:0;}
  .settings-modal-title{display:flex;align-items:center;gap:8px;font-family:var(--font);font-weight:700;font-size:22px;letter-spacing:-0.02em;text-transform:uppercase;color:var(--text);}
  .settings-modal-sub{font-family:var(--font);font-size:13px;color:var(--muted);margin-top:6px;line-height:1.5;}
  .settings-modal-body{flex:1;overflow-y:auto;padding:8px 32px 4px;}
  .settings-section-title{font-family:var(--font);font-size:12px;font-weight:700;color:var(--text);letter-spacing:0.02em;margin:36px 0 12px;padding-bottom:10px;border-bottom:1px solid var(--border);}
  .settings-section-title:first-child{margin-top:24px;}
  .settings-section-title + .settings-row,.settings-section-title + .settings-action,
  .settings-section-title + div > .settings-row:first-child,.settings-section-title + div > .settings-action:first-child{border-top:none;}
  .settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 0;border-top:1px solid var(--border);font-size:13px;}
  .settings-row span:first-child{color:var(--muted);}
  .settings-row span:last-child{color:var(--text);font-weight:600;text-align:right;word-break:break-word;}
  .settings-action{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;text-align:left;padding:12px 0;border-top:1px solid var(--border);font-size:13px;font-weight:600;color:var(--new);background:none;border:none;cursor:pointer;font-family:var(--font);}
  .settings-action:hover{opacity:0.85;}
  .settings-action:not(.danger)::after{content:'\203A';color:var(--muted);font-size:15px;font-weight:600;line-height:1;}
  .settings-action.danger{color:#ff6b6b;}
  .settings-link-btn{background:none;border:none;color:var(--new);font-family:var(--font);font-size:12px;font-weight:600;cursor:pointer;padding:0;margin-left:4px;}
  .settings-link-btn:hover{opacity:0.85;}
  .settings-key-value{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;background:var(--panel-2);border-radius:8px;padding:10px 12px;margin:8px 0 4px;word-break:break-all;color:var(--text);}
  .settings-warning{font-size:12px;color:#ff9f45;line-height:1.6;padding:0 0 8px;}
  .settings-modal-footer{display:flex;align-items:center;justify-content:space-between;padding:18px 32px;border-top:1px solid var(--border);flex-shrink:0;}
  .settings-signout-link{background:none;border:none;color:#ff6b6b;font-size:13px;font-weight:600;cursor:pointer;font-family:var(--font);padding:8px 4px;}
  .settings-signout-link:hover{opacity:0.8;}
  .settings-modal-done{background:var(--new);color:#0a0a0c;border:none;border-radius:8px;padding:10px 22px;font-size:13px;font-weight:700;cursor:pointer;font-family:var(--font);}
  .settings-modal-done:hover{background:#ffa370;}

  /* On-map territory-drawing banner, positioned relative to .map-wrap. */
  .draw-banner{position:absolute;top:18px;left:50%;transform:translateX(-50%);z-index:600;display:flex;align-items:center;gap:12px;background:rgba(19,19,23,.92);backdrop-filter:blur(6px);border:1px solid var(--border);border-radius:10px;padding:9px 10px 9px 14px;font-family:var(--font);font-size:12px;color:var(--text);}
  .draw-banner-cancel{font-size:11px;font-weight:600;color:#ff6b6b;background:var(--panel-2);border:1px solid var(--border);border-radius:6px;padding:6px 10px;cursor:pointer;white-space:nowrap;}
  .draw-banner-cancel:hover{background:var(--panel);}
  /* Positioned relative to .map-wrap (top-left of the map), matching
     .stat-bar's floating chrome treatment (same bg/blur/border/radius) so
     it reads as part of the app rather than a bare native <select>. Admin-
     only and most users never see it, so kept small and out of the way of
     both the sidebar and the stats counter. */
  .org-switcher{position:absolute;top:18px;left:18px;z-index:500;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-color:rgba(19,19,23,.9);backdrop-filter:blur(6px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2382828c' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;border:1px solid var(--border);border-radius:8px;color:var(--text);font-family:var(--font);font-size:10px;letter-spacing:0.04em;padding:7px 26px 7px 10px;cursor:pointer;outline:none;max-width:150px;}
  .settings-select{appearance:none;-webkit-appearance:none;-moz-appearance:none;background-color:var(--bg);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2382828c' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;border:1px solid var(--border);border-radius:8px;color:var(--text);font-family:var(--font);font-size:13px;font-weight:600;padding:6px 28px 6px 10px;cursor:pointer;outline:none;}
  .settings-select:focus{border-color:#4a4a52;}
  .org-switcher:hover{border-color:#333338;}
  .org-switcher:focus{border-color:#4a4a52;}
  .toggle{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;border-radius:6px;background:var(--panel-2);border:1px solid var(--border);cursor:pointer;user-select:none;transition:border-color .15s ease;}
  .toggle:hover{border-color:#333338;}
  .toggle-left{display:flex;align-items:center;gap:9px;}
  .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;}
  .toggle-name{font-size:13px;font-weight:500;}
  .toggle-count{font-family:var(--font);font-size:11px;color:var(--muted);}
  .switch{width:30px;height:17px;border-radius:9px;background:#2a2a30;position:relative;transition:background .15s ease;flex-shrink:0;}
  .switch.on{background:rgba(255,143,82,.3);}
  .switch::after{content:'';position:absolute;width:13px;height:13px;border-radius:50%;background:#5a5a62;top:2px;left:2px;transition:all .15s ease;}
  .switch.on::after{left:15px;background:var(--new);}
  .toggle[data-status="new"] .switch.on{background:rgba(255,107,53,.25);}
  .toggle[data-status="new"] .switch.on::after{background:var(--new);}
  .toggle[data-status="upcoming"] .switch.on{background:rgba(167,139,250,.25);}
  .toggle[data-status="upcoming"] .switch.on::after{background:var(--upcoming);}
  .toggle[data-status="visited"] .switch.on{background:rgba(45,212,191,.25);}
  .toggle[data-status="visited"] .switch.on::after{background:#9a9aa5;}
  .toggle[data-status="saved"] .switch.on{background:rgba(234,234,236,.2);}
  .toggle[data-status="saved"] .switch.on::after{background:#eaeaec;}
  /* Compact segmented control for school type (Public/Private/Daycare) -
     replaces 3 full toggle rows with 1 row of pill segments, each just
     dimming to 40% opacity when off rather than needing a full switch. */
  .segmented{display:flex;gap:6px;}
  .segment{flex:1;display:flex;align-items:center;justify-content:center;gap:5px;padding:7px 6px;border-radius:6px;background:var(--panel-2);border:1px solid var(--border);cursor:pointer;user-select:none;font-size:11px;font-weight:500;color:var(--text);transition:opacity .15s ease,border-color .15s ease;white-space:nowrap;}
  .segment:hover{border-color:#333338;}
  .segment:not(.on){opacity:.4;}
  .segment .dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;}
  .segment-count{font-family:var(--font);font-size:9.5px;color:var(--muted);}

  /* Status segmented control - New/Upcoming/Visited, single-select (see
     setStatusFilter). Same concept as the mobile app's swipe control
     (StatusSegmentedControl.js), just hover/click here instead of a drag
     gesture - a mouse-driven surface doesn't need the swipe affordance. */
  .segmented-status{display:flex;background:var(--panel-2);border:1px solid var(--border);border-radius:999px;padding:3px;gap:2px;}
  .segment-status{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 8px;border-radius:999px;font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;user-select:none;transition:background .15s ease,color .15s ease;}
  .segment-status:hover{color:var(--text);}
  .segment-status.active{background:var(--new);color:#0a0a0c;font-weight:600;}
  /* Clicking an already-active segment again "presses it in" instead of
     picking a different one - an inset shadow instead of the normal solid
     fill reads as recessed/off rather than raised/selected, while staying
     visually tied to that segment so it's clear what's toggled. Clears
     every pin off the map (see isVisible()) so you can see a blank
     territory outline/basemap on demand. */
  .segment-status.active.cleared{background:transparent;color:var(--new);box-shadow:inset 0 2px 5px rgba(0,0,0,.6), inset 0 0 0 1px rgba(255,143,82,.4);font-weight:500;}
  .segment-status-count{font-family:var(--font);font-size:10.5px;color:inherit;opacity:.75;}

  .resize-handle{height:12px;flex-shrink:0;cursor:ns-resize;display:flex;align-items:center;justify-content:center;touch-action:none;}
  .resize-handle::after{content:'';width:32px;height:4px;border-radius:2px;background:var(--border);transition:background .15s ease;}
  .resize-handle:hover::after,.resize-handle.dragging::after{background:#5a5a62;}
  /* transform:translateZ(0) promotes this to its own compositing layer -
     a real, widely-hit Chrome/Windows bug repaints every visible
     non-overlay scrollbar on the page on each frame of ANY animating
     element anywhere (here, the AI-summary loading spinner in a popup),
     which reads as a fast flicker on this list's scrollbar. Isolating the
     layer stops it from being swept into that repaint. */
  .manifest{flex:1;overflow-y:auto;padding:0 0 14px;min-height:160px;transform:translateZ(0);}
  .manifest-header{padding:0 20px 10px;font-family:var(--font);font-size:10px;color:var(--muted);letter-spacing:0.1em;text-transform:uppercase;}
  .entry{display:flex;align-items:center;gap:12px;padding:10px 18px;cursor:pointer;transition:background .1s ease;}
  .entry:hover{background:var(--panel-2);}
  .entry-avatar{width:36px;height:36px;border-radius:50%;background:var(--panel-2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;color:var(--text);flex-shrink:0;}
  .entry-avatar svg{width:17px;height:17px;}
  .entry-avatar.new{box-shadow:0 0 0 2px var(--new);}
  .entry-avatar.visited{opacity:.5;}
  .entry-body{flex:1;min-width:0;}
  .entry-actions{display:flex;align-items:center;gap:9px;flex-shrink:0;}
  .entry-action{width:16px;height:16px;color:var(--muted);cursor:pointer;transition:color .15s ease;}
  .entry-action:hover{color:var(--text);}
  .entry-action.active{color:var(--text);}
  .entry-saved-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:#eaeaec;flex-shrink:0;box-shadow:0 0 4px 1px rgba(234,234,236,.6);}
  .check-btn{width:16px;height:16px;border-radius:4px;border:1.5px solid var(--muted);flex-shrink:0;cursor:pointer;display:flex;align-items:center;justify-content:center;background:transparent;}
  .check-btn.checked{background:var(--existing,#9a9aa5);border-color:var(--existing,#9a9aa5);}
  .check-btn svg{width:11px;height:11px;color:#0a0a0c;display:none;}
  .check-btn.checked svg{display:block;}
  /* Visited + saved sit side by side as two independent actions, not
     stacked - they're unrelated axes (a rep can save a place they haven't
     visited, or keep a visited one saved), so reading them as a pair
     rather than a vertical list makes that independence clearer. Wraps on
     genuinely narrow popups rather than clipping. */
  .popup-actions{display:flex;align-items:center;justify-content:center;gap:16px;flex-wrap:wrap;margin-top:8px;}
  .popup-check{display:flex;align-items:center;gap:7px;padding:4px 2px;font-size:12px;color:var(--muted);cursor:pointer;}
  /* Save-for-later button - same layout as .popup-check/.check-btn, gold
     instead of gray so the two independent actions (visited vs saved)
     never look like the same toggle. A soft glow on the filled state
     (here, the map pin badge, and the sidebar dot) makes "saved" read as
     a small highlight rather than just another flat-colored dot. */
  .save-btn{width:16px;height:16px;border-radius:4px;border:1.5px solid var(--muted);flex-shrink:0;cursor:pointer;display:flex;align-items:center;justify-content:center;background:transparent;transition:box-shadow .15s ease;}
  .save-btn.saved{background:#eaeaec;border-color:#eaeaec;box-shadow:0 0 8px 1px rgba(234,234,236,.55);}
  .save-btn svg{width:11px;height:11px;color:#0a0a0c;display:none;}
  .save-btn.saved svg{display:block;}
  .popup-save{display:flex;align-items:center;gap:7px;padding:4px 2px;font-size:12px;color:var(--muted);cursor:pointer;}
  /* Opens the phone's own Maps app for turn-by-turn directions - mobile
     only (hidden on desktop, shown at the same 768px breakpoint the rest
     of the app's mobile layout uses) since a rep at a desk has no use for
     "directions from my current location," but a rep about to walk into
     a cold visit does. Hidden by default here, turned on in the mobile
     media query below. */
  .popup-directions{display:none;align-items:center;gap:6px;padding:5px 11px;margin-top:2px;font-family:var(--font);font-size:10.5px;letter-spacing:0.05em;text-transform:uppercase;color:var(--new);background:rgba(255,107,53,.1);border:1px solid rgba(255,107,53,.35);border-radius:14px;text-decoration:none;cursor:pointer;transition:background .15s ease,border-color .15s ease;}
  .popup-directions:hover{background:rgba(255,107,53,.18);border-color:rgba(255,107,53,.55);}
  .popup-directions svg{width:12px;height:12px;flex-shrink:0;}
  .popup-ai{margin-top:8px;padding-top:8px;border-top:1px solid #232328;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}
  .ai-generate-btn{font-size:11.5px;color:var(--new);cursor:pointer;}
  .ai-generate-btn:hover{color:#ff8555;}
  .ai-unavailable{font-size:11.5px;color:var(--muted);font-style:italic;}
  .ai-loading{display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--muted);}
  .ai-spinner{width:11px;height:11px;border:1.5px solid #3a3a42;border-top-color:var(--new);border-radius:50%;animation:ai-spin 0.7s linear infinite;flex-shrink:0;will-change:transform;}
  .ai-spinner-gray{width:12px;height:12px;border:2px solid #4a4a52;border-top-color:#d0d0d5;border-radius:50%;animation:ai-spin 0.7s linear infinite;flex-shrink:0;display:inline-block;will-change:transform;}
  @keyframes ai-spin{to{transform:rotate(360deg);}}
  .ai-error{font-size:11.5px;color:#ff6b6b;}
  .ai-summary-text{font-size:12px;color:#ccc;line-height:1.4;font-style:italic;}
  .ai-meta{font-size:10px;color:var(--muted);margin-top:5px;}
  .ai-regenerate-btn{color:var(--muted);cursor:pointer;text-decoration:underline;}
  .ai-regenerate-btn:hover{color:var(--text);}
  .ai-regenerate-disabled{color:#555;}
  .entry.active{background:var(--panel-2);}
  .entry-top{display:flex;align-items:center;gap:8px;margin-bottom:3px;}
  .entry-name{font-size:13.5px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;}
  .entry-meta{font-family:var(--font);font-size:10.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}

  /* MAP */
  .map-wrap{position:relative;border-radius:16px;overflow:hidden;border:1px solid var(--border);}
  #map{width:100%;height:100%;background:#0a0a0c;}
  /* Zoom buttons, restyled from Mapbox's default white to the dark theme
     (the +/- icons are dark SVGs, so they're inverted to light). */
  .mapboxgl-ctrl-group{background:var(--panel);border:1px solid var(--border);box-shadow:none;}
  .mapboxgl-ctrl-group button + button{border-top:1px solid var(--border);}
  .mapboxgl-ctrl-group button .mapboxgl-ctrl-icon{filter:invert(1) brightness(.9);}
  .mapboxgl-ctrl-group button:not(:disabled):hover{background-color:var(--panel-2);}

  /* Collapsed attribution control - a small "i" icon that expands into
     the full attribution pill on click, instead of an always-on text
     strip eating corner space. */
  .attribution-control{position:relative;display:flex;align-items:center;justify-content:flex-end;}
  .attribution-toggle{width:26px;height:26px;flex:0 0 auto;border:none;border-radius:50%;background:rgba(0,0,0,.4);color:#d5d5da;font:italic 600 13px/26px Georgia,'Times New Roman',serif;text-align:center;cursor:pointer;padding:0;transition:background .15s ease,color .15s ease;}
  .attribution-toggle:hover{background:rgba(0,0,0,.6);color:var(--text);}
  /* Positioned beside the "i" rather than in the flex flow, so expanding
     never reflows anything: it slides in from the icon (transform), fades
     (opacity) and wipes open right-to-left (clip-path) - no width/padding
     animation. visibility keeps the hidden links out of the tab order. */
  .attribution-panel{position:absolute;right:32px;top:0;padding:0 10px;opacity:0;visibility:hidden;pointer-events:none;white-space:nowrap;background:rgba(10,10,12,.75);color:var(--muted);font-size:11px;line-height:26px;border-radius:13px;transform:translateX(8px);clip-path:inset(0 0 0 100% round 13px);transition:transform .24s cubic-bezier(.2,.8,.2,1),clip-path .24s cubic-bezier(.2,.8,.2,1),opacity .18s ease,visibility 0s linear .24s;}
  .attribution-panel a{color:#9a9aa2;text-decoration:none;}
  .attribution-panel a:hover{color:var(--text);text-decoration:underline;}
  .attribution-control.expanded .attribution-panel{opacity:1;visibility:visible;pointer-events:auto;transform:none;clip-path:inset(0 0 0 0 round 13px);transition:transform .24s cubic-bezier(.2,.8,.2,1),clip-path .24s cubic-bezier(.2,.8,.2,1),opacity .18s ease,visibility 0s;}
  @media (prefers-reduced-motion:reduce){.attribution-panel,.attribution-control.expanded .attribution-panel{transition:opacity .15s ease,visibility 0s;transform:none;}}

  .stat-bar{position:absolute;top:18px;right:18px;background:rgba(19,19,23,.9);backdrop-filter:blur(6px);border:1px solid var(--border);border-radius:8px;padding:12px 16px;display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:20px;z-index:500;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}

  /* Shown over the map from login until the first batch of prospects has
     been fetched and turned into markers - covers the otherwise-empty map
     tiles + the moment dots are still populating in, which reads as
     broken/uncertain rather than "still loading" at DFW-wide marker counts. */
  .map-loading{position:absolute;inset:0;z-index:2000;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;background:var(--bg);transition:opacity .25s ease;}
  .map-loading.hidden{opacity:0;pointer-events:none;}
  .map-loading-spinner{width:34px;height:34px;border-radius:50%;border:3px solid var(--border);border-top-color:var(--new);animation:map-loading-spin .8s linear infinite;}
  .map-loading-text{font-family:var(--font);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);}
  @keyframes map-loading-spin{to{transform:rotate(360deg);}}

  .menu-toggle{display:none;position:fixed;bottom:34px;right:16px;z-index:9999;width:52px;height:52px;background:var(--new);border:2px solid #fff;border-radius:10px;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.6);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}
  .menu-toggle svg{color:#0a0a0c;}
  .menu-toggle svg{width:18px;height:18px;color:var(--text);}
  .sidebar-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9990;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}
  .sidebar-backdrop.open{display:block;}
  /* Column flex rather than text-align:right - the Upcoming number sits in
     an inline-block sparkle wrapper, and an inline-block inside a block
     picks up the line box's strut, pushing it ~5px below its neighbors. */
  /* Centered in equal-width columns (see .stat-bar's grid) - right-aligned,
     a short count like "62" hugged the edge under a much wider UPCOMING
     label and the three columns read as unevenly spaced. */
  .stat{display:flex;flex-direction:column;align-items:center;}
  .stat-num{font-family:var(--font);font-size:18px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums;}
  .stat-label{font-family:var(--font);font-size:9px;color:var(--muted);letter-spacing:0.08em;text-transform:uppercase;margin-top:3px;}
  /* Same twinkling sparkles as the map's upcoming pins, on the Upcoming
     stat number itself - reuses pin-sparkle-twinkle, just sized up for
     text instead of a 14px pin. */
  .stat-num-wrap{position:relative;display:inline-block;}
  /* Particle shapes, drawn in CSS rather than a ✦ glyph so they stay crisp
     at this size: thin four-ray cross glints. White only - a colored glow
     on something this small blends to yellow. */
  .spark-glint::before,.spark-glint::after{content:'';position:absolute;background:linear-gradient(var(--dir),transparent,#fff 50%,transparent);}
  .spark-glint::before{--dir:to bottom;left:50%;top:0;width:1px;height:100%;transform:translateX(-50%);}
  .spark-glint::after{--dir:to right;top:50%;left:0;height:1px;width:100%;transform:translateY(-50%);}
  .stat-sparkle{position:absolute;pointer-events:none;animation:pin-sparkle-twinkle 4.5s ease-in-out infinite;}
  /* Five points, staggered .9s apart so they glint one at a time,
     alternating right/left around the number. */
  .stat-sparkle-a{top:-5px;right:-8px;width:9px;height:9px;}
  .stat-sparkle-b{bottom:2px;left:-9px;width:7px;height:7px;animation-delay:.9s;}
  .stat-sparkle-c{top:5px;right:-9px;width:5px;height:5px;animation-delay:1.8s;}
  .stat-sparkle-d{top:-4px;left:-6px;width:6px;height:6px;animation-delay:2.7s;}
  .stat-sparkle-e{top:-7px;left:40%;width:5px;height:5px;animation-delay:3.6s;}
  @media (prefers-reduced-motion:reduce){.stat-sparkle{animation:none;opacity:.8;}}

  .pin{border-radius:50%;position:relative;box-shadow:0 0 0 3px rgba(0,0,0,.4);}
  /* Visited pins are still gray, not just dimmed further - this light ring
     is what keeps them findable against the dark basemap instead of
     reading as gone. */
  .pin.checked{box-shadow:0 0 0 2px rgba(255,255,255,.55),0 0 0 4px rgba(0,0,0,.4);}
  /* Upcoming shares --new's exact orange (single-select filter means
     they're never on screen together) but gets the same white ring
     .checked uses, so a pin can still be told apart as "upcoming"
     wherever it does show up next to another status (the stat bar,
     future multi-status views) without inventing a second orange. */
  .pin.upcoming{box-shadow:0 0 0 2px rgba(255,255,255,.55),0 0 0 4px rgba(0,0,0,.4);}
  /* Saved-for-later badge - a small dot in the corner of the pin, visible
     without opening the popup. A real child element rather than ::after
     since .pin::after is already the pulse-glow animation below. */
  .pin-star{position:absolute;top:-4px;right:-4px;width:7px;height:7px;border-radius:50%;background:#eaeaec;box-shadow:0 0 0 1.5px var(--bg), 0 0 5px 1.5px rgba(234,234,236,.65);}
  /* Small twinkling glyphs on upcoming pins only - same premium touch
     added to the mobile app's map pins and its Upcoming stat number.
     Sized down from mobile's since these pins are 14px, not 22px. */
  .pin-sparkle{position:absolute;pointer-events:none;animation:pin-sparkle-twinkle 4.5s ease-in-out infinite;}
  .pin-sparkle-a{top:-6px;left:-6px;width:7px;height:7px;}
  .pin-sparkle-b{bottom:-4px;right:-4px;width:5px;height:5px;animation-delay:2.2s;}
  /* A brief glint every 4.5s rather than a constant twinkle - hidden for
     most of the cycle so it reads as an occasional catch of light. */
  @keyframes pin-sparkle-twinkle{0%,22%,100%{opacity:0;transform:scale(.5);}10%{opacity:1;transform:scale(1.1);}}
  .map-zoomed-out .pin-sparkle{animation:none;display:none;}
  @media (prefers-reduced-motion:reduce){.pin-sparkle{animation:none;opacity:.8;}}

  /* "You are here" self-location marker - same glow technique as .pin
     (shares the pulse keyframes below) but with a white ring instead of
     .pin's dark one and a larger halo, so it reads as "this is me" rather
     than another lead pin even where markers overlap. */
  .self-pin{border-radius:50%;position:relative;box-shadow:0 0 0 3px rgba(255,255,255,.95),0 0 0 5px rgba(0,0,0,.35);}
  .self-pin::after{content:'';position:absolute;inset:-10px;border-radius:50%;opacity:.45;animation:pulse 2.2s ease-out infinite;}
  .map-zoomed-out .self-pin::after{animation:none;display:none;}
  @media (max-width: 768px){ .self-pin::after{display:none;} }

  /* Ordinary pins and clusters are drawn by Mapbox GL layers (map.js) -
     .pin below only styles the handful of upcoming pins, which stay DOM
     markers for their sparkles. */
  .upcoming-marker{cursor:pointer;}
  .pin::after{content:'';position:absolute;inset:-6px;border-radius:50%;opacity:.35;animation:pulse 2.2s ease-out infinite;}
  /* At high zoom-out with thousands of markers, animating every single
     one gets genuinely slow - same class of problem mobile already had
     at hundreds of markers, now happening on desktop too at 2,000+.
     Disable the pulse when zoomed out past a reasonable threshold. */
  .map-zoomed-out .pin::after{animation:none;display:none;}
  @keyframes pulse{0%{box-shadow:0 0 0 0 currentColor;opacity:.4;}100%{box-shadow:0 0 0 10px currentColor;opacity:0;}}
  /* Upcoming glimmer - hugs the pin instead of the generic 16px pulse: a
     white ring leads, the brand orange trails just outside it, and both
     fade out within ~8px of the pin's edge. Starts at the static white
     ring (-2px) so the glimmer reads as that ring breathing outward. */
  .pin.upcoming::after{inset:-2px;opacity:1;animation:upcoming-glimmer 1.8s ease-out infinite;}
  @keyframes upcoming-glimmer{
    0%{box-shadow:0 0 0 0 rgba(255,255,255,.85),0 0 0 0 rgba(255,143,82,.9);}
    100%{box-shadow:0 0 0 3px rgba(255,255,255,0),0 0 0 6px rgba(255,143,82,0);}
  }
  @media (prefers-reduced-motion:reduce){.pin.upcoming::after{animation:none;box-shadow:0 0 0 3px rgba(255,143,82,.35);}}

  /* Above the stat bar (z-index 500), as Leaflet's popup pane (700) was. */
  .caza-popup{z-index:700;}
  .caza-popup .mapboxgl-popup-content{width:260px;box-sizing:border-box;padding:14px 18px;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:8px;box-shadow:0 3px 14px rgba(0,0,0,.4);font:12px/1.5 "Helvetica Neue",Arial,Helvetica,sans-serif;}
  .caza-popup.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip{border-top-color:var(--panel);}
  .caza-popup.mapboxgl-popup-anchor-top .mapboxgl-popup-tip{border-bottom-color:var(--panel);}
  .caza-popup .mapboxgl-popup-close-button{color:var(--muted);font-size:18px;line-height:1;padding:4px 7px;top:2px;right:2px;border-radius:4px;}
  .caza-popup .mapboxgl-popup-close-button:hover{background:var(--panel-2);color:var(--text);}
  /* Empty by default (no photo yet, or confirmed none) - :empty collapses
     it to nothing so a photo-less prospect's popup takes no extra space,
     matching the mobile app's PhotoHero (renders nothing, not a
     placeholder box, when there's no photo). */
  .popup-photo:empty{display:none;}
  .popup-photo{width:100%;height:110px;border-radius:8px;overflow:hidden;margin-bottom:8px;background:var(--panel-2);}
  .popup-photo img{width:100%;height:100%;object-fit:cover;display:block;}
  /* min-height, not just max-height - without it a short/photo-less
     popup shrinks to fit its own content while a photo+summary one
     stretches tall, so cards read as wildly different sizes depending on
     what happened to load. min-height sized for the common case (photo +
     name + address + category + actions + one AI-summary line) means a
     shorter popup just carries a little extra bottom space instead of
     looking like a different-sized card; max-height + scroll still caps
     genuinely long AI summaries same as before. */
  /* scrollbar-gutter:stable reserves the scrollbar's width up front so
     toggling overflow-y between hidden/auto (e.g. while an AI summary
     goes from a short "generate" button to loading text to a full
     paragraph) doesn't jump the content width back and forth - that
     width jump was reading as a flickering scrollbar. */
  .popup-body{min-height:230px;max-height:60vh;overflow-y:hidden;scrollbar-gutter:stable;}
  .popup-body.popup-scrollable{overflow-y:auto;}
  .popup-body a.popup-name{display:block;text-align:center;font-weight:600;font-size:14px;margin-bottom:4px;color:var(--text);text-decoration:none;cursor:pointer;}
  .popup-body a.popup-name:hover{color:var(--muted);text-decoration:underline;}
  .popup-address{margin-top:6px;font-size:12px;color:#aaa;text-align:center;}
  .popup-note{margin-top:4px;font-size:11.5px;color:#888;text-align:center;}
  .popup-status{font-family:var(--font);font-size:10px;letter-spacing:.06em;text-transform:uppercase;padding:2px 7px;border-radius:4px;display:inline-block;}
  /* Mobile layout */
  @media (max-width: 768px){
    /* Edge-to-edge again on mobile - the desktop redesign's rounded
       floating panels (see .app/.sidebar/.map-wrap above) cost real
       screen space that isn't worth it on a phone, and the sidebar is
       already a fixed overlay here, not a grid column. */
    .app{grid-template-columns:1fr;gap:0;padding:0;}
    .map-wrap{border-radius:0;border:none;}
    .menu-toggle{display:flex;}
    .sidebar{
      position:fixed; top:0; left:0; height:100vh; width:85vw; max-width:320px;
      z-index:9995; transform:translateX(-100%); transition:transform .25s ease;
      box-shadow:2px 0 24px rgba(0,0,0,.5);
      border-radius:0; border:none; border-right:1px solid var(--border);
    }
    .sidebar.open{transform:translateX(0);}
    .stat-bar{top:14px;right:14px;padding:9px 12px;gap:12px;}
    .stat-num{font-size:15px;}
    .stat-label{font-size:8px;}
    .login-box{width:88vw;max-width:340px;padding:22px;}
    .pin::after{display:none;} /* pulse animation is too costly with hundreds of mobile markers */
    .popup-directions{display:flex;}

    /* iOS Safari force-zooms the whole page on focus of any form control
       with a computed font-size under 16px - the viewport meta tag can't
       override this (Apple ignores user-scalable=no since iOS 10). That's
       what caused the "zooms in on sign-in, stays zoomed until you tap the
       search bar" bug: every input/select here was under 16px. Bumping
       them to 16px on mobile stops the auto-zoom from firing at all. */
    .login-field input,
    .login-field select,
    .search-input,
    .category-input,
    .sort-select,
    .org-switcher,.settings-select{font-size:16px;}
  }
