/* ═══════════════════════════════════════════════════════════
   ODIOGOS CAMPUS MAP  ·  Design System v6  ·  "Sunrise Energy"
   ─────────────────────────────────────────────────────────
   TERRACOTTA  var(--wine)  → Primary · Residential · Hostel · Planned
   WARM ORANGE var(--gold)  → Secondary · Academic · Active
   DEEP NAVY   var(--navy)  → Accent · institutional emphasis
   PEACH WHITE var(--bg)    → Background
   No mixing. Clean, purposeful separation.

   --wine/--gold keep their historical variable names (100+ usages
   throughout this file) rather than a risky mass rename — they just now
   resolve to the new brand hexes below instead of the old wine/gold hues.
   --terracotta/--warm-orange/--deep-navy/--peach-white are the spec's own
   names, kept as aliases so the mapping stays legible from the tokens
   themselves rather than only from this comment.
═══════════════════════════════════════════════════════════ */
    :root {
      --terracotta: #C1440E;   /* Primary */
      --warm-orange: #F4A259;  /* Secondary */
      --deep-navy: #1B3A6B;    /* Accent */
      --peach-white: #FFF3E9;  /* Background */

      --gold: var(--warm-orange);
      --gold-dk: #D9823A;
      --gold-lt: #F7BE8C;
      --gold-xlt: #FDECD8;
      --gold-tint: rgba(244, 162, 89, .10);
      --gold-glow: rgba(244, 162, 89, .22);
      --gold-ring: rgba(244, 162, 89, .30);
      /* Warm Orange as small TEXT on a plain white/surface background
         measures well under WCAG's 4.5:1 minimum (base --gold is ~2.1:1).
         This darker variant is for that specific case only — everything
         else (buttons, icons, tinted backgrounds) keeps using
         --gold/--gold-dk/--gold-lt as before. */
      --gold-text: #8A4E14;

      --wine: var(--terracotta);
      --wine-dk: #93350A;
      --wine-lt: #E8794A;
      --wine-xlt: #FBE0D2;
      --wine-tint: rgba(193, 68, 14, .09);
      --wine-glow: rgba(193, 68, 14, .20);
      --wine-ring: rgba(193, 68, 14, .28);

      /* Deep Navy — the Accent role. Not part of the wine/gold "no mixing"
         pair; used sparingly and deliberately (focus rings, the compass
         needle, a few institutional-emphasis accents) rather than as a
         third general-purpose brand color. Dark enough on its own
         (~11:1 against white) to double as its own text color. */
      --navy: var(--deep-navy);
      --navy-dk: #12294D;
      --navy-lt: #5B7CB0;
      --navy-tint: rgba(27, 58, 107, .08);
      --navy-glow: rgba(27, 58, 107, .20);
      --navy-ring: rgba(27, 58, 107, .28);
      --navy-text: var(--deep-navy);

      /* Flat neutral gray canvas (not the spec's peach-white — overridden
         per follow-up direction) with two-tier hierarchy on top of it:
         --sidebar-bg for structural/navigation chrome (floating panel),
         --surface (white) for actual content cards (info panel, stat
         chips, weather). Sidebar sits closer to the page gray so it reads
         as chrome that recedes; cards are white so they read as the
         content the chrome is there to surface — see the "different
         colours for hierarchy" panel rules further down. */
      --bg: #F5F5F5;
      --bg2: #EDEDED;
      --sidebar-bg: #F0F0F0;
      --surface: #FFFFFF;
      --s2: #F1F1F1;
      --s3: #E6E6E6;
      --s4: #D6D6D6;
      --border: #E2E2E2;
      --border2: #CFCFCF;

      --ink: #1C1810;
      --ink2: #3A3224;
      --slate: #695E4E;
      /* Was #978A78 — measured ~3.4:1 against white/surface, under WCAG's
         4.5:1 minimum for normal text (this is used for a lot of small
         secondary/description text app-wide). Darkened just enough to
         clear the bar (~4.8:1) while staying the same warm brown-gray hue. */
      --muted: #7C7162;
      --dim: #BCB0A0;

      /* Odiogos type system: Satoshi for headings (Semi-Bold — portal
         titles, formal notices, registrar-style headers), Manrope for UI
         and body copy (multi-column lists, faculty portals, submission
         instructions), IBM Plex Mono for data/IDs (coordinates, stat
         counts, timetable-style numbers — see var(--mono) usages). --display
         kept as the heading alias so every existing heading rule keeps
         working without a rename. */
      --display: 'Satoshi', system-ui, sans-serif;
      --ui: 'Manrope', system-ui, sans-serif;
      --mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Consolas, monospace;

      /* Sharp, small-radius scale (bento-grid direction) — was 3/7/12/18/24.
         --r-full is untouched: it's what makes pills/circles (buttons,
         the search bar, the compass) actually round, a different job from
         "how square are this card's corners". */
      --r-xs: 2px;
      --r-sm: 3px;
      --r-md: 5px;
      --r-lg: 7px;
      --r-xl: 9px;
      --r-full: 999px;
      --ease: cubic-bezier(.16, 1, .3, 1);
      --fast: 130ms;
      --med: 220ms;
      --slow: 340ms;

      --sidebar-w: 320px; /* reserved left offset for the floating panel */
      --panel-w: 420px;
      --info-panel-w: 320px; /* current on-screen width of .info-panel — kept
        in sync with its actual `width` at every breakpoint so map-ctrl/
        layer-panel can shift out of its way via calc() instead of guessing */

      --sh-xs: 0 1px 3px rgba(28, 24, 16, .06);
      --sh-sm: 0 2px 8px rgba(28, 24, 16, .09);
      --sh-md: 0 4px 18px rgba(28, 24, 16, .12);
      --sh-lg: 0 8px 36px rgba(28, 24, 16, .14);
      --sh-xl: 0 16px 60px rgba(28, 24, 16, .16);

      /* ── Overlay stacking order ──────────────────────────────────────
         Every fixed/absolute element that floats above the map draws its
         z-index from one of these instead of a standalone number, so the
         whole stack — from "always-on map furniture" up to "boot splash"
         — is one readable list instead of scattered per-component values
         that drift out of sync as panels get added. This is what let the
         search-suggestions dropdown render *under* the view-pill, and the
         mobile layer switcher render *under* the directions FAB — both
         fixed by moving them to the correct tier below. Same value at
         every breakpoint on purpose: only position changes per screen
         size, not paint order. */
      --z-map-chrome: 600;      /* compass, zoom/map-ctrl, view-pill — passive map furniture */
      --z-search-drop: 650;     /* search suggestions + help popover — a header input's own popover, must clear map furniture */
      --z-panel: 700;           /* floating-panel (campuses/categories) + info-panel (place details/weather) */
      --z-panel-collapse: 750;  /* the collapse/expand tab riding each panel's edge */
      --z-header: 800;          /* app-header bar */
      --z-mobile-nav: 850;      /* hamburger toggle + its scrim, phone only */
      --z-fab: 900;             /* Get Directions floating button */
      --z-overlay-panel: 950;   /* panels opened by an explicit tap: layers switcher, directions panel */
      --z-task-overlay: 1000;   /* short focused-task overlays: place-hint, fix-location panel */
      --z-nav-hud: 1100;        /* live turn-by-turn banner — wins over the header during navigation */
      --z-lightbox: 1200;
      --z-onboarding: 1300;     /* first-run walkthrough — sits above everything it points at */
      --z-modal: 1400;          /* Add Facility / Suggest Data dialogs */
      --z-loading: 1500;        /* boot splash */

      /* Weather condition tints (css/weather.css .weather-current[data-cat]).
         Flat single-tone, not gradients (stakeholder: reduce gradients), and
         redefined under body.dark below so the card never goes muddy/washed
         out in dark mode instead of just inheriting these light values. */
      --wx-sunny:  #FBEFC8;
      --wx-partly: #F3ECDC;
      --wx-cloudy: #E8E6DE;
      --wx-rainy:  #DCE7F1;
      --wx-stormy: #D2DBE9;
      --wx-foggy:  #E5E4DF;
      --wx-snowy:  #E4EEF6;
      --wx-stat-bg: rgba(255, 255, 255, .55);
    }

    /* Frosted-glass fallback — floating-panel/info-panel/map-ctrl-group/
       zoom-group/layer-panel/view-pill/dir-panel all use a translucent
       rgba() background + backdrop-filter blur so the full-bleed map shows
       through them. Browsers without backdrop-filter support (older
       Firefox/Safari) would otherwise show a half-transparent panel with
       map content visibly cluttering the text on top of it — fall back to
       each panel's normal opaque --surface in that case. */
    @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
      .floating-panel, .info-panel, .map-ctrl-group, .zoom-group,
      .layer-panel, .view-pill, .dir-panel {
        background: var(--surface);
      }
    }

    /* ── Reset ── */
    *,
    *::before,
    *::after {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      /* No declaration anywhere in this file suppressed the browser's
         default tap-highlight box before this — every pin, icon button and
         panel row flashed a gray rectangle on touch, and since none of this
         app's feedback states are anything but :hover (which iOS Safari
         "sticks" on after a tap until you tap elsewhere), taps read as
         laggy/unpolished compared to Google/Apple Maps' instant, flash-free
         touch response. :active states (scale/opacity changes already used
         in a few places, e.g. .ah-icon-btn:active) remain the real feedback. */
      -webkit-tap-highlight-color: transparent;
    }

    html,
    body {
      width: 100%;
      /* height:100% (percentage of the viewport-derived initial containing
         block) has the same "mobile browser chrome show/hide" jump problem
         100vh is known for — 100dvh tracks the actually-visible viewport
         instead. Kept as a fallback for browsers without dvh support. */
      height: 100%;
      height: 100dvh;
      overflow: hidden;
      /* Skips the ~300ms tap-delay browsers otherwise reserve to detect a
         double-tap-to-zoom gesture on custom controls (buttons, panel rows,
         map chrome) — pinch-zoom on the map itself is untouched since
         Leaflet manages #map's own touch handling independently of this. */
      touch-action: manipulation;
    }

    body {
      background: var(--bg);
      color: var(--ink);
      font-family: var(--ui);
      font-size: 14px;
      line-height: 1.5;
      -webkit-font-smoothing: antialiased;
      display: flex;
      flex-direction: column;
    }

    button,
    input {
      font: inherit;
      color: inherit
    }

    button {
      cursor: pointer;
      border: none;
      background: none
    }

    /* ── Loading ── */
    .loading {
      position: fixed;
      inset: 0;
      z-index: var(--z-loading);
      background: #0A0908;
      overflow: hidden;
      transition: opacity .4s var(--ease), visibility .4s
    }

    body.ready .loading {
      opacity: 0;
      visibility: hidden;
      pointer-events: none
    }

    /* Full-bleed backdrop — same treatment as the Odiogos brand splash used
       elsewhere (full-screen, cropped to fill, muted+loop+playsinline).
       #0A0908 background above shows for the ~1 frame before the video has
       decoded its first frame, so there's never a flash of the wrong colour. */
    .loading-video {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    /* Text sits on top of whatever the video is doing at that moment, so
       the scrim's job is purely legibility — darker toward the bottom
       where the title/subtitle/bar actually sit, lighter up top so the
       video itself still reads clearly through it. */
    .loading-scrim {
      position: absolute;
      inset: 0;
      background: linear-gradient(180deg,
        rgba(10, 9, 8, .10) 0%,
        rgba(10, 9, 8, .18) 45%,
        rgba(10, 9, 8, .62) 78%,
        rgba(10, 9, 8, .78) 100%);
    }

    .load-inner {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 9%;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 16px;
      padding: 0 24px;
      animation: fadeUp .6s var(--ease) both
    }

    @keyframes fadeUp {
      from {
        opacity: 0;
        transform: translateY(16px)
      }

      to {
        opacity: 1;
        transform: none
      }
    }

    /* Always white/light — this sits over a video, not a themed surface,
       so it deliberately doesn't follow var(--ink)/var(--muted) (which
       would go dark-on-dark the moment body.dark is active at boot). */
    .load-title {
      font-family: var(--display);
      font-size: clamp(24px, 5.5vw, 34px);
      font-weight: 600;
      color: #fff;
      letter-spacing: -.3px;
      line-height: 1.25;
      text-align: center;
      max-width: 420px;
      text-shadow: 0 2px 16px rgba(0, 0, 0, .35);
      margin: 0 auto
    }

    .load-sub {
      font-size: 11px;
      font-weight: 600;
      letter-spacing: .14em;
      text-transform: uppercase;
      color: rgba(255, 255, 255, .68);
      text-align: center;
    }

    .load-bar {
      width: 140px;
      height: 2px;
      background: rgba(255, 255, 255, .22);
      border-radius: 99px;
      overflow: hidden;
      margin-top: 6px;
    }

    .load-fill {
      height: 100%;
      width: 0;
      background: var(--gold);
      border-radius: inherit;
      /* Matches the splash video's real runtime (~4.2s, see initLoadingVideo()
         in script.js) so the bar finishes right as the video ends and the
         app reveals — was tuned to the old fixed ~1s boot wait, now stale. */
      animation: fill 4s linear .2s forwards
    }

    @keyframes fill {
      to {
        width: 100%
      }
    }

    /* ── Shell ── */
    /* ═══════════════════════════════════════════════════════════
   HEADER — slim top bar: logo, name, search, profile/theme icon
═══════════════════════════════════════════════════════════ */
    .app-header {
      flex-shrink: 0;
      height: 64px;
      display: flex;
      align-items: center;
      padding: 0 24px;
      gap: 22px;
      background: var(--surface);
      border-bottom: 1px solid var(--border);
      box-shadow: var(--sh-sm);
      position: relative;
      z-index: var(--z-header);
    }

    /* Logo/name pinned to the true top-left corner, with its own extra
       margin (on top of the header's base gap) so it reads as clearly
       separate from the search bar rather than crowding it. */
    .app-header .brand { margin-bottom: 0; flex-shrink: 0; margin-right: 28px; }

    .app-header .srch-wrap {
      flex: 1;
      max-width: 500px;
    }

    .ah-profile {
      position: relative;
      flex-shrink: 0;
      width: 38px;
      height: 38px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: hidden;
      background: var(--wine-tint);
      border: 1.5px solid var(--wine-ring);
      color: var(--wine);
      cursor: pointer;
      transition: background var(--fast), border-color var(--fast), color var(--fast), transform 120ms var(--ease);
      margin-left: auto;
    }

    .ah-profile:hover {
      background: var(--gold-tint);
      border-color: var(--gold-ring);
      color: var(--gold-text);
    }

    .ah-profile:active {
      transform: scale(.9);
    }

    /* Sun/moon cross-transition — was an instant display:none/block swap,
       now the two icons rotate + scale + fade past each other so toggling
       reads as one deliberate motion instead of a hard cut. Both icons sit
       stacked via absolute positioning (the button is the containing
       block) so they can overlap mid-transition. Kept to a single subtle
       rotate/scale rather than anything bouncier, per [[feedback-glow-restraint]]. */
    .ah-profile svg {
      position: absolute;
      top: 50%; left: 50%;
      transition: transform .4s var(--ease), opacity .3s var(--ease);
    }
    #themeBtnMoon {
      transform: translate(-50%, -50%) rotate(0deg) scale(1);
      opacity: 1;
    }
    #themeBtnSun {
      transform: translate(-50%, -50%) rotate(-75deg) scale(.4);
      opacity: 0;
    }
    body.dark #themeBtnMoon {
      transform: translate(-50%, -50%) rotate(75deg) scale(.4);
      opacity: 0;
    }
    body.dark #themeBtnSun {
      transform: translate(-50%, -50%) rotate(0deg) scale(1);
      opacity: 1;
    }

    /* ═══════════════════════════════════════════════════════════
       PHONE (≤640px) — header becomes a floating Google-Maps-style
       search pill over a full-bleed map, instead of a boxed bar
    ═══════════════════════════════════════════════════════════ */
    @media (max-width: 640px) {
      .app-header {
        position: fixed;
        top: calc(10px + env(safe-area-inset-top));
        left: 62px;
        right: 10px;
        height: 50px;
        padding: 0 6px 0 16px;
        border: 1.5px solid var(--border);
        border-radius: var(--r-full);
        box-shadow: var(--sh-lg);
        z-index: var(--z-header);
        gap: 8px;
      }
      .app-header .brand { display: none; }
      /* Was 34px/32px — smaller than this app's own 40px touch-target
         convention (see .map-btn etc. in the touch-targets media query)
         and below Apple/Google's ~44px/48dp guidance, on the one class of
         device where every tap IS a fingertip. Phone should never shrink
         tap targets below desktop's mouse-sized ones. */
      .app-header .ah-profile { width: 40px; height: 40px; }
      .app-header .ah-icon-btn { width: 40px; height: 40px; }
      #fullscreenBtn { display: none; }
      /* This is the ONLY place display/size/position/z-index are set for
         .mob-toggle — it used to also be (differently) redeclared in the
         unconditional base rule further down the file, and because that
         later rule had equal selector specificity, source order made it
         win regardless of viewport, silently forcing display:none at every
         width and leaving the phone hamburger button permanently invisible.
         The base rule now only carries shared visual polish (colors,
         border, shadow) that doesn't conflict with this. */
      .mob-toggle {
        display: flex;
        top: calc(10px + env(safe-area-inset-top));
        left: 10px;
        width: 44px;
        height: 44px;
        border-radius: var(--r-full);
        z-index: var(--z-mobile-nav);
      }
    }

    /* ═══════════════════════════════════════════════════════════
       TABLET (641–1024px) — keep the normal header bar (there's
       room for it), just trim the brand description
    ═══════════════════════════════════════════════════════════ */
    @media (min-width: 641px) and (max-width: 1024px) {
      .app-header { padding: 0 18px; gap: 14px; }
      .app-header .brand-desc { display: none; }
    }

    /* ═══════════════════════════════════════════════════════════
       HEADER ADDITIONS — campus selector, help, fullscreen
    ═══════════════════════════════════════════════════════════ */
    .campus-sel-wrap { flex-shrink: 0; }
    .campus-sel {
      height: 38px;
      padding: 0 30px 0 14px;
      border: 1.5px solid var(--border);
      border-radius: var(--r-full);
      background: var(--s2);
      color: var(--ink);
      font-family: var(--ui);
      font-size: 12.5px;
      font-weight: 600;
      cursor: pointer;
      appearance: none;
      -webkit-appearance: none;
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23695E4E' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: right 10px center;
      background-size: 12px;
      transition: background-color var(--fast), border-color var(--fast);
    }
    .campus-sel:hover { background-color: var(--gold-tint); border-color: var(--gold-ring); }

    .ah-icon-btn {
      position: relative;
      flex-shrink: 0;
      width: 38px;
      height: 38px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: var(--s2);
      border: 1.5px solid var(--border);
      color: var(--slate);
      cursor: pointer;
      transition: background var(--fast), border-color var(--fast), color var(--fast), transform 120ms var(--ease);
    }
    .ah-icon-btn:hover { background: var(--gold-tint); border-color: var(--gold-ring); color: var(--gold-text); }
    .ah-icon-btn:active { transform: scale(.9); }

    .help-popover {
      position: absolute;
      top: 56px;
      right: 96px;
      width: 260px;
      background: var(--surface);
      border-radius: var(--r-md);
      box-shadow: var(--sh-lg);
      padding: 14px 16px;
      z-index: var(--z-search-drop);
      display: none;
    }
    .help-popover.open { display: block; }
    .help-popover-title { font-size: 12px; font-weight: 700; color: var(--ink); margin-bottom: 8px; }
    .help-popover-list { list-style: none; display: flex; flex-direction: column; gap: 8px; }
    .help-popover-list li {
      font-size: 12px;
      color: var(--slate);
      line-height: 1.5;
      padding-left: 14px;
      position: relative;
    }
    .help-popover-list li::before {
      content: '';
      position: absolute;
      left: 0; top: 7px;
      width: 5px; height: 5px;
      border-radius: 50%;
      background: var(--gold);
    }

    @media (max-width: 640px) {
      .campus-sel-wrap { display: none; }
      .help-popover { right: 10px; top: 64px; width: calc(100vw - 20px); max-width: 300px; }
    }

    /* ═══════════════════════════════════════════════════════════
       LAYER SWITCHER — real toggles for what the map actually has
       data for (Buildings, Satellite, Weather); the rest are shown
       disabled with a "no data yet" tooltip rather than faked.
    ═══════════════════════════════════════════════════════════ */
    .layer-panel {
      position: absolute;
      bottom: 28px;
      right: 58px;
      transition: right var(--slow) var(--ease);
      width: 226px;
      max-height: 70vh;
      overflow-y: auto;
      /* Solid card colour, not frosted glass — see the --sidebar-bg vs
         --surface hierarchy note on .floating-panel/.info-panel. This is
         opened-content, same tier as those, so it gets the same flat
         white card treatment instead of a translucent blur. */
      background: var(--surface);
      border-radius: var(--r-md);
      box-shadow: var(--sh-lg);
      padding: 12px 14px;
      /* Opened-overlay tier, not map-chrome tier — this used to share the
         passive compass/zoom-stack z-index (8500), which put it *below*
         the directions FAB and the phone floating-panel's peek handle.
         On phone both of those geometrically overlap this panel's bottom
         rows (the FAB sits at the same bottom:76px anchor — see the phone
         rule below), so with the old lower z-index they silently ate taps
         meant for the layer checkboxes underneath them. A panel the user
         just explicitly opened should always paint over passive chrome
         and other floating buttons, matching Google/Apple Maps' own
         layers-sheet behavior. */
      z-index: var(--z-overlay-panel);
      display: none;
    }
    .layer-panel.open { display: block; }
    .layer-panel-title { font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
    .layer-row { display: flex; align-items: center; gap: 9px; font-size: 12.5px; color: var(--ink); padding: 5px 0; cursor: pointer; }
    .layer-row input { accent-color: var(--wine); width: 15px; height: 15px; cursor: pointer; }
    .layer-row.disabled { color: var(--dim); cursor: not-allowed; }
    .layer-row.disabled input { cursor: not-allowed; }
    .layer-panel-sep { height: 1px; background: var(--border); margin: 4px 0 6px; }

    /* Crowdsource entry point on each "no mapped data yet" row — see
       initLayerSuggestions() in script.js. Sits inside the same disabled
       <label>, so it needs its own stopPropagation-free click target that
       doesn't fight the (already-inert, disabled) checkbox beside it. */
    .layer-suggest-btn {
      margin-left: auto;
      width: 20px; height: 20px;
      border-radius: 50%;
      border: 1.5px solid var(--border);
      background: var(--s2);
      color: var(--muted);
      font-size: 13px;
      line-height: 1;
      display: flex; align-items: center; justify-content: center;
      cursor: pointer;
      flex-shrink: 0;
      transition: all var(--fast);
    }
    .layer-suggest-btn:hover { background: var(--gold-tint); border-color: var(--gold-ring); color: var(--gold-text); }

    .layer-suggest-hint { font-size: 10.5px; color: var(--muted); line-height: 1.5; padding-top: 2px; }

    .layer-suggest-sub { font-size: 12px; color: var(--muted); margin: -6px 0 14px; font-weight: 600; }

    textarea.light-input { resize: vertical; min-height: 70px; font-family: var(--ui); }

    @media (max-width: 640px) {
      .layer-panel { bottom: 76px; right: 10px; max-height: 60vh; }
    }

    /* ═══════════════════════════════════════════════════════════
       COMPASS — standalone, top-right of the map canvas
    ═══════════════════════════════════════════════════════════ */
    .map-compass {
      position: absolute;
      top: 16px;
      right: 14px;
      transition: right var(--slow) var(--ease), box-shadow var(--fast), border-color var(--fast);
      z-index: var(--z-map-chrome);
      width: 48px;
      height: 48px;
      border-radius: 50%;
      background: rgba(255, 255, 255, .82);
      backdrop-filter: blur(14px) saturate(180%);
      -webkit-backdrop-filter: blur(14px) saturate(180%);
      border: 1.5px solid var(--border);
      box-shadow: var(--sh-lg);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 0;
      color: var(--slate);
      cursor: pointer;
    }
    .map-compass:hover { box-shadow: var(--sh-md); border-color: var(--gold-ring); }
    .map-compass-n { font-size: 8px; font-weight: 700; letter-spacing: .08em; color: var(--muted); margin-bottom: 1px; }

    /* Same reasoning as .map-ctrl/.layer-panel below — shift clear of the
       info panel on desktop/tablet instead of sitting underneath it. The
       min() caps how far left that shift can go at --sidebar-w (the same
       "floating-panel + clearance" budget dir-fab/nav-hud already assume)
       — without it, at tablet widths the uncapped formula could push this
       past the floating panel's own right edge and land on top of it. */
    @media (min-width: 641px) {
      .map-stage:has(.info-panel:not(.collapsed)) .map-compass {
        right: min(calc(var(--info-panel-w) + 30px), calc(100vw - 48px - var(--sidebar-w)));
      }
    }

    @media (max-width: 640px) {
      /* top:10px (matching desktop) put this directly UNDER the floating
         mobile header pill — same top:10/right:10 corner, header's higher
         z-index just hid the compass entirely. Clear the header's real
         height (50px) + its own safe-area-aware top offset instead. */
      .map-compass { top: calc(70px + env(safe-area-inset-top)); right: 10px; width: 44px; height: 44px; }
    }

    /* Marker cluster — restyled from the plugin defaults (green/orange/
       yellow) to the app's own wine/gold language instead of introducing
       a third, unrelated color. */
    .dti-cluster {
      display: flex; align-items: center; justify-content: center;
      width: 34px; height: 34px;
      border-radius: 50%;
      background: var(--wine);
      color: #fff;
      font-family: var(--ui);
      font-weight: 700;
      font-size: 12.5px;
      border: 2.5px solid #fff;
      box-shadow: 0 3px 10px rgba(28,24,16,.25);
    }

    /* ═══════════════════════════════════════════════════════════
   SIDEBAR — retired in favor of the header + floating panel, kept
   in the DOM (hidden) so existing list/search logic keeps working
═══════════════════════════════════════════════════════════ */
    .shell {
      position: relative;
      flex: 1;
      min-height: 0;
      width: 100%;
    }

    .sidebar {
      display: none;
    }

    /* ═══════════════════════════════════════════════════════════
   FLOATING QUICK-ACTIONS PANEL — replaces the full sidebar
═══════════════════════════════════════════════════════════ */
    .floating-panel {
      position: absolute;
      top: 16px;
      left: 16px;
      bottom: 16px;
      width: 260px;
      overflow-y: auto;
      /* Sidebar tier: solid --sidebar-bg, deliberately NOT the same colour
         as .info-panel/cards below. This is navigation/browsing chrome
         (campus list, category filters) rather than content, so it sits
         a shade off the page background and reads as structural — the
         white content cards (info panel, stat chips, weather) then pop
         forward against it instead of blending into an identical panel
         colour. No border/blur — flat card, shadow carries the depth. */
      background: var(--sidebar-bg);
      border-radius: var(--r-lg);
      box-shadow: var(--sh-lg);
      z-index: var(--z-panel);
      padding: 16px;
      display: flex;
      flex-direction: column;
      gap: 18px;
      scrollbar-width: thin;
      scrollbar-color: var(--border) transparent;
      transform: translateX(0);
      transition: transform var(--slow) var(--ease), opacity var(--med) var(--ease);
      /* Promotes this panel to its own compositor layer ahead of time, so
         the (already-expensive) backdrop-filter blur above gets composited
         once and just moved/faded rather than re-rasterized on every frame
         of the collapse/expand slide — the main jank source on the panel
         open/close animation, especially on mid-range Android GPUs. */
      will-change: transform, opacity;
      /* Soft fade at the scrollable top/bottom edges instead of content
         (like the Fix Building Location button) getting abruptly guillotined
         by the container edge — a standard "there's more, scroll" hint. */
      -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
      mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
    }

    /* Collapsed = slide fully off-canvas to the left rather than shrinking
       in place — the map is already full-bleed underneath, so hiding this
       panel is purely about decluttering the view, not reclaiming space.
       Scoped to desktop/tablet on purpose: .collapsed is a persisted
       localStorage preference, so it can still be set to '1' when the page
       loads at phone width (e.g. someone collapsed it on desktop, then
       resized down, or reloaded on a phone with the same saved prefs).
       .floating-panel.collapsed (2 classes) is MORE specific than the
       plain .floating-panel rule in the phone media query below, so
       without this scoping it would win and override the phone's own
       translateY peek-sheet transform — leaving the panel shoved off the
       left edge with no reopen control, since .fp-collapse-btn is also
       display:none on phone. Same reasoning applies to .info-panel.collapsed
       further down. */
    @media (min-width: 641px) {
      .floating-panel.collapsed {
        transform: translateX(calc(-100% - 24px));
        opacity: 0;
        pointer-events: none;
      }
    }

    .floating-panel::-webkit-scrollbar { width: 3px }
    .floating-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px }

    /* Collapse toggle — a small circular tab that reopens its panel.
       IMPORTANT: this is a sibling of .floating-panel/.info-panel in the
       markup now, NOT a child — it used to live inside the panel, but the
       panel slides fully off-canvas (translateX + opacity:0) when
       collapsed, which carried the reopen button off screen with it and
       left no way back in. As an independent sibling positioned against
       .map-stage, it instead just slides itself between two fixed slots:
       tucked against the panel's edge when open, parked at the map's own
       edge (still reachable) when the panel is closed.

       Shared visual style lives on .panel-collapse-btn; each button ALSO
       carries exactly one of .fp-collapse-btn (left panel → sets `left`)
       or .info-collapse-btn (right panel → sets `right`) for its own
       anchor side. These two used to be combined onto both buttons (the
       right button carried .fp-collapse-btn AND .info-collapse-btn), and
       since `left`+`right`+a fixed `width` is an over-constrained box,
       the CSS spec always resolves that in favor of `left` — so the right
       panel's button silently rendered at the LEFT panel's position,
       exactly on top of (and blocking clicks on) the real left-panel
       button. Keeping the anchor classes mutually exclusive per element
       is what makes this safe to combine with the shared style class. */
    .panel-collapse-btn {
      position: absolute;
      top: 16px;
      width: 26px;
      height: 26px;
      border-radius: 50%;
      background: var(--surface);
      border: 1.5px solid var(--border);
      box-shadow: var(--sh-sm);
      display: flex;
      align-items: center;
      justify-content: center;
      color: var(--slate);
      cursor: pointer;
      z-index: var(--z-panel-collapse);
      transition: background var(--fast), border-color var(--fast), color var(--fast), left var(--slow) var(--ease), right var(--slow) var(--ease);
    }
    .panel-collapse-btn:hover { background: var(--gold-tint); border-color: var(--gold-ring); color: var(--gold-text); }
    .fp-collapse-btn { left: calc(16px + 260px - 13px); }
    .fp-collapse-btn.collapsed { left: 16px; }
    .fp-collapse-btn.collapsed svg { transform: rotate(180deg); }
    .info-collapse-btn { right: calc(16px + var(--info-panel-w) - 13px); }
    .info-collapse-btn.collapsed { right: 16px; }
    .info-collapse-btn.collapsed svg { transform: rotate(180deg); }

    .fp-section { display: flex; flex-direction: column; gap: 8px; flex-shrink: 0; }

    /* Drag-handle strip — only shown on phone, where the panel becomes a
       peeking bottom sheet (see PHONE media query below) */
    .fp-peek-handle {
      display: none;
      width: 100%;
      flex-direction: column;
      align-items: center;
      gap: 10px;
      padding: 10px 0 6px;
      flex-shrink: 0;
      cursor: pointer;
    }
    .fp-peek-bar { width: 36px; height: 4px; border-radius: 99px; background: var(--border2); }
    .fp-peek-handle:hover .fp-peek-bar { background: var(--gold); }
    .fp-peek-teaser {
      display: flex;
      align-items: center;
      gap: 6px;
      color: var(--slate);
    }
    .fp-peek-teaser svg { flex-shrink: 0; color: var(--wine); }
    .fp-peek-teaser-text { font-size: 13px; font-weight: 700; color: var(--ink); }
    .fp-peek-teaser-text #fpPeekTeaserCount { font-weight: 500; color: var(--muted); }

    .fp-title {
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .12em;
      text-transform: uppercase;
      color: var(--muted);
      padding-bottom: 8px;
      border-bottom: 1.5px solid var(--border);
    }

    .fp-actions { flex-direction: column; gap: 8px; }
    .fp-actions-row { display: flex; flex-direction: row; gap: 8px; }

    .fp-action-full {
      flex-direction: row;
      justify-content: center;
      padding: 10px 8px;
      color: var(--wine);
      border-style: dashed;
    }
    .fp-action-full:hover { background: var(--gold-tint); border-color: var(--gold-ring); color: var(--gold-text) }

    .fp-action-btn {
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 6px;
      padding: 12px 8px;
      border-radius: var(--r-md);
      border: 1.5px solid var(--border);
      background: var(--s2);
      color: var(--wine);
      font-size: 11.5px;
      font-weight: 600;
      cursor: pointer;
      transition: all var(--fast);
    }

    .fp-action-btn:hover { background: var(--gold-tint); border-color: var(--gold-ring); color: var(--gold-text); transform: translateY(-1px) }

    .fp-action-btn.primary { background: var(--wine); color: #fff; border-color: transparent; box-shadow: 0 2px 10px var(--wine-glow) }
    .fp-action-btn.primary:hover { background: var(--gold); color: var(--ink); box-shadow: 0 4px 14px var(--gold-glow); transform: translateY(-1px) }

    /* Locate Me while live-tracking is on — gold "active" state (see
       locateMe()/stopLocateTracking() in script.js) plus a small pulsing
       dot on the icon so the on/off state is visible at a glance. */
    .fp-action-btn.primary.tracking { background: var(--gold); color: var(--ink); box-shadow: 0 0 0 2px var(--gold-ring), 0 2px 10px var(--gold-glow) }
    .fp-action-btn.primary.tracking:hover { background: var(--gold); color: var(--ink) }
    .fp-action-btn.tracking .fp-action-icon { position: relative }
    .fp-action-btn.tracking .fp-action-icon::after {
      content: ''; position: absolute; top: -3px; right: -3px; width: 8px; height: 8px;
      border-radius: 50%; background: var(--wine); border: 1.5px solid #fff;
      animation: trackDotPulse 1.4s ease-in-out infinite;
    }

    .fp-action-icon svg { display: block }

    .fp-hidden { display: none !important }

    /* Nav header — shown only when inside a campus, with a way back out */
    .fp-nav-header {
      order: -2;
      flex-direction: row;
      align-items: center;
      justify-content: space-between;
      gap: 8px;
      padding-bottom: 12px;
      border-bottom: 1.5px solid var(--border);
    }

    .fp-back-btn {
      display: flex;
      align-items: center;
      gap: 5px;
      flex-shrink: 0;
      height: 26px;
      padding: 0 10px 0 8px;
      border-radius: var(--r-full);
      background: var(--s2);
      border: 1.5px solid var(--border);
      color: var(--slate);
      font-family: var(--ui);
      font-size: 11px;
      font-weight: 600;
      cursor: pointer;
      transition: all var(--fast);
    }

    .fp-back-btn:hover { background: var(--gold-tint); border-color: var(--gold-ring); color: var(--gold-text) }
    .fp-back-btn svg { flex-shrink: 0 }

    .fp-nav-title {
      flex: 1;
      min-width: 0;
      text-align: right;
      font-size: 12px;
      font-weight: 700;
      color: var(--wine);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* Campuses — the primary way to jump between the school's campuses */
    .fp-campuses { order: -1 }

    /* Buildings & Facilities — shown when a campus is active */
    .fp-buildings { order: -1 }

    .fp-building-list { display: flex; flex-direction: column; gap: 10px }

    .fp-building-group { display: flex; flex-direction: column; gap: 2px }

    .fp-building-group-lbl {
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-size: 9.5px;
      font-weight: 700;
      letter-spacing: .1em;
      text-transform: uppercase;
      color: var(--muted);
      padding: 2px 2px 4px;
    }

    .fp-building-group-count {
      font-size: 9px;
      font-weight: 700;
      padding: 1px 6px;
      border-radius: var(--r-full);
      background: var(--s3);
      color: var(--muted);
    }

    .fp-building-item {
      display: flex;
      align-items: center;
      gap: 10px;
      width: 100%;
      padding: 7px 8px;
      border-radius: var(--r-sm);
      cursor: pointer;
      transition: background var(--fast), border-color var(--fast), transform var(--fast) var(--ease);
      border: 1.5px solid transparent;
      background: transparent;
      text-align: left;
    }

    .fp-building-item:hover { background: var(--s2); transform: translateX(2px); }
    .fp-building-item.on { background: var(--wine-tint); border-color: var(--wine-ring) }
    .fp-building-item.on .fp-building-name { color: var(--wine) }

    .fp-building-thumb {
      width: 24px;
      height: 24px;
      border-radius: 50%;
      flex-shrink: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
    }

    .fp-building-body { flex: 1; min-width: 0 }

    .fp-building-name {
      font-size: 12px;
      font-weight: 600;
      color: var(--ink);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      display: block;
      transition: color var(--fast);
    }

    .fp-building-sub {
      font-size: 10.5px;
      color: var(--muted);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      display: block;
    }

    .fp-building-planned {
      flex-shrink: 0;
      font-size: 8.5px;
      font-weight: 700;
      letter-spacing: .05em;
      text-transform: uppercase;
      color: var(--wine);
      background: var(--wine-tint);
      border: 1px solid var(--wine-ring);
      border-radius: var(--r-full);
      padding: 2px 7px;
    }

    .fp-campus-list { display: flex; flex-direction: column; gap: 3px }

    .fp-campus-item {
      display: flex;
      align-items: center;
      gap: 11px;
      width: 100%;
      padding: 8px;
      border-radius: var(--r-md);
      cursor: pointer;
      transition: background var(--fast), border-color var(--fast), transform var(--fast) var(--ease);
      border: 1.5px solid transparent;
      background: transparent;
      text-align: left;
    }

    .fp-campus-item:hover { background: var(--s2); transform: translateX(2px); }

    .fp-campus-item.on {
      background: var(--wine-tint);
      border-color: var(--wine-ring);
    }

    .fp-campus-item.on .fp-campus-name { color: var(--wine) }

    .fp-campus-thumb {
      width: 34px;
      height: 34px;
      border-radius: 50%;
      flex-shrink: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
      box-shadow: 0 2px 6px rgba(28, 24, 16, .18);
    }

    .fp-campus-body { flex: 1; min-width: 0 }

    .fp-campus-name {
      font-size: 12.5px;
      font-weight: 700;
      color: var(--ink);
      white-space: normal;
      line-height: 1.25;
      display: block;
      transition: color var(--fast);
    }

    .fp-campus-role {
      font-size: 10.5px;
      color: var(--muted);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      display: block;
      margin-top: 1px;
    }

    .fp-campus-item.all-campuses {
      border-bottom: 1.5px solid var(--border);
      border-radius: 0;
      margin-bottom: 4px;
      padding-bottom: 12px;
    }

    .fp-campus-item.all-campuses .fp-campus-thumb {
      background: var(--s3);
      color: var(--wine);
    }

    .fp-cat-list { display: flex; flex-direction: column; gap: 2px }

    .fp-cat-item {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 7px 8px;
      border-radius: var(--r-sm);
      cursor: pointer;
      transition: background var(--fast), border-color var(--fast);
      border: 1.5px solid transparent;
      font-size: 12.5px;
      font-weight: 600;
      color: var(--slate);
    }

    .fp-cat-item:hover { background: var(--s2) }
    .fp-cat-item.on { background: var(--wine-tint); border-color: var(--wine-ring); color: var(--wine) }

    .fp-cat-icon {
      width: 26px;
      height: 26px;
      border-radius: 50%;
      background: var(--s3);
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
      color: var(--slate);
      transition: all var(--fast);
    }

    .fp-cat-item.on .fp-cat-icon { background: var(--wine); color: #fff }
    .fp-cat-count {
      margin-left: auto;
      font-size: 10px;
      font-weight: 700;
      color: var(--muted);
      background: var(--s3);
      border-radius: var(--r-full);
      padding: 1px 7px;
      flex-shrink: 0;
    }

    .fp-legend-list { display: flex; flex-direction: column; gap: 8px }
    .fp-legend-item { display: flex; align-items: center; gap: 9px; font-size: 12px; color: var(--slate) }
    .fp-legend-dot { width: 11px; height: 11px; border-radius: 50%; flex-shrink: 0 }
    .fp-legend-dot.photo { background: var(--s4); border: 2px solid #fff; box-shadow: 0 0 0 1px var(--border) }
    .fp-legend-dot.outline { background: var(--s2); border: 2px dashed var(--border2); width: 9px; height: 9px }

    @media (max-width: 640px) {
      .floating-panel {
        position: absolute;
        flex: none;
        margin: 0;
        top: auto;
        /* Sits 30vh off the true bottom edge — matching #infoPanel's own
           resting max-height on phone (see the phone .info-panel rule
           further down) — instead of bottom:0. #infoPanel is ALSO a
           bottom-anchored sheet on phone (the "weather always reachable"
           design); both panels now share the same --z-panel tier, so with
           both at bottom:0 this panel's peek handle would render on top
           of/overlapping the weather sheet underneath it — two
           independent bottom sheets fighting for the same strip of
           screen. Offsetting this one clear of that space fixes it for
           the idle (nothing selected) case; the .has-selection case below
           (which fully displaces this panel via transform + pointer-events,
           not z-index) handles the taller, 85dvh selected-state sheet. */
        bottom: 30vh;
        left: 0;
        right: 0;
        width: 100%;
        max-height: 72dvh;
        border-radius: var(--r-xl) var(--r-xl) 0 0;
        padding: 0 14px calc(14px + env(safe-area-inset-bottom));
        z-index: var(--z-panel);
        /* Resting state peeks a handle strip above the bottom edge so the
           campus list is discoverable, not fully hidden — tap to expand. */
        transform: translateY(calc(100% - 64px));
        transition: transform var(--slow) var(--ease), bottom var(--slow) var(--ease);
        box-shadow: 0 -8px 40px rgba(28, 24, 16, .18);
        opacity: 1;
        pointer-events: auto;
      }
      .floating-panel.open { transform: translateY(0) }
      /* Once something is selected, #infoPanel grows to a tall 85dvh sheet
         (see .info-panel.has-selection) — at that height a fixed 30vh
         offset would land this panel's peek handle mid-way UP that sheet
         instead of clear of it. Simplest correct fix: this panel isn't
         useful to keep peeking while the visitor is looking at a specific
         place's details anyway, so hide it fully in that state rather than
         chase a moving offset. Tapping the weather/back controls in
         #infoPanel already returns to the idle state, which restores this
         panel's normal peek via the rule above. */
      .map-stage:has(.info-panel.has-selection) .floating-panel {
        transform: translateY(100%);
        pointer-events: none;
      }
      .fp-peek-handle { display: flex }
      /* Collapse rail is a desktop/tablet concept — phone already has the
         peek-handle drag pattern for showing/hiding this panel. */
      .fp-collapse-btn { display: none; }
    }

    /* Dark-mode counterpart of the sidebar/card split above: sits between
       --bg (#0A0908) and --surface (#211B14) so the sidebar still reads as
       structural chrome one step below the (lighter) content cards, same
       relative hierarchy as light mode's --sidebar-bg vs --surface. */
    body.dark .floating-panel { background: #17130E; box-shadow: 0 0 40px rgba(0,0,0,.5) }
    body.dark .fp-action-btn { background: var(--s2); border-color: var(--border) }

    /* Header */
    .sb-top {
      flex-shrink: 0;
      padding: 20px 20px 14px;
      border-bottom: 1px solid var(--border)
    }

    .brand {
      display: flex;
      align-items: center;
      gap: 13px;
      margin-bottom: 16px
    }

    .brand-logo {
      width: 44px;
      height: 44px;
      flex-shrink: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: var(--display);
      font-size: 14px;
      font-weight: 800;
      color: #fff;
    }

    /* No badge background/shadow — just the Odiogos logo as shipped,
       white space included (images/odiogos-logo.png), filling its box. */
    .brand-logo img {
      width: 100%;
      height: 100%;
      object-fit: contain;
    }

    .brand-name {
      font-family: var(--display);
      font-size: 15px;
      font-weight: 600;
      color: var(--ink);
      line-height: 1.25
    }

    .brand-desc {
      font-size: 11px;
      color: var(--muted);
      margin-top: 2px;
      line-height: 1.4
    }

    /* Search */
    .srch-wrap {
      position: relative
    }

    .srch-icon {
      position: absolute;
      left: 12px;
      top: 50%;
      transform: translateY(-50%);
      color: var(--dim);
      pointer-events: none;
      width: 14px;
      height: 14px
    }

    .srch-input {
      width: 100%;
      height: 42px;
      padding: 0 36px 0 36px;
      background: var(--s2);
      border: 1.5px solid var(--border);
      border-radius: var(--r-sm);
      color: var(--ink);
      font-size: 13.5px;
      outline: none;
      transition: border-color var(--fast), box-shadow var(--fast)
    }

    .srch-input::placeholder {
      color: var(--dim)
    }

    .srch-input:focus {
      border-color: var(--gold-ring);
      box-shadow: 0 0 0 3px var(--gold-tint);
      background: var(--surface)
    }

    /* "/" shortcut hint — real, not decorative (see srchKbd wiring in
       initSearch()). Hides once there's a query or the field has focus, so
       it never sits on top of typed text or the clear button. */
    .srch-kbd {
      position: absolute;
      right: 12px;
      top: 50%;
      transform: translateY(-50%);
      font-family: var(--ui);
      font-size: 11px;
      font-weight: 600;
      color: var(--dim);
      background: var(--s2);
      border: 1px solid var(--border);
      border-radius: 5px;
      padding: 2px 6px;
      pointer-events: none;
    }
    .srch-input:focus ~ .srch-kbd,
    .srch-input:not(:placeholder-shown) ~ .srch-kbd { display: none; }

    .srch-clear {
      position: absolute;
      right: 10px;
      top: 50%;
      transform: translateY(-50%);
      width: 20px;
      height: 20px;
      display: none;
      align-items: center;
      justify-content: center;
      border-radius: 50%;
      background: var(--s3);
      color: var(--muted);
      font-size: 10px;
      cursor: pointer
    }

    .srch-input:not(:placeholder-shown)~.srch-clear {
      display: flex
    }

    .srch-drop {
      position: absolute;
      top: calc(100% + 6px);
      left: 0;
      right: 0;
      background: var(--surface);
      border-radius: var(--r-md);
      box-shadow: var(--sh-lg);
      /* Was 700 — well below the view-pill/compass/map-ctrl/layer-panel
         (8000-8500 under the old numbering), so a full-height results
         list had its lower rows silently painted over by the view-pill
         label sitting on top of it. A search input's own dropdown must
         always clear the map chrome underneath it. */
      z-index: var(--z-search-drop);
      max-height: 300px;
      overflow-y: auto;
      display: none
    }

    .srch-drop.open {
      display: block
    }

    .sr-item {
      display: flex;
      align-items: center;
      gap: 11px;
      padding: 10px 14px;
      cursor: pointer;
      transition: background var(--fast);
      border-bottom: 1px solid var(--border)
    }

    .sr-item:last-child {
      border: none
    }

    .sr-item:hover {
      background: var(--s2)
    }

    .sr-icon {
      width: 34px;
      height: 34px;
      border-radius: 8px;
      background: var(--s3);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 15px;
      flex-shrink: 0
    }

    .sr-name {
      font-size: 13px;
      font-weight: 600;
      color: var(--ink)
    }

    .sr-name mark {
      background: var(--gold-xlt);
      color: var(--gold-text);
      border-radius: 2px
    }

    .sr-meta {
      font-size: 11px;
      color: var(--muted);
      margin-top: 2px
    }

    .sr-badge {
      font-size: 9.5px;
      font-weight: 700;
      letter-spacing: .07em;
      text-transform: uppercase;
      padding: 3px 8px;
      border-radius: var(--r-full);
      background: var(--s3);
      color: var(--muted);
      flex-shrink: 0
    }

    .sr-empty {
      padding: 18px;
      text-align: center;
      color: var(--muted);
      font-size: 13px
    }

    /* Chips */
    .chip-row {
      overflow-x: auto;
      scrollbar-width: none;
      margin: 0 -20px;
      padding: 10px 0 3px
    }

    .chip-row::-webkit-scrollbar {
      display: none
    }

    .chip-inner {
      padding: 0 20px;
      display: flex;
      gap: 6px
    }

    .chip {
      flex-shrink: 0;
      height: 28px;
      padding: 0 13px;
      background: var(--s2);
      border: 1.5px solid var(--border);
      border-radius: var(--r-full);
      color: var(--slate);
      font-size: 11.5px;
      font-weight: 600;
      letter-spacing: .01em;
      transition: all var(--fast);
      cursor: pointer
    }

    .chip:hover {
      border-color: var(--gold-ring);
      color: var(--ink);
      background: var(--s3)
    }

    .chip.on {
      background: var(--gold);
      color: #fff;
      border-color: transparent;
      box-shadow: 0 2px 10px var(--gold-glow)
    }

    /* Context bar */
    .ctx-bar {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 10px 20px;
      border-bottom: 1px solid var(--border);
      background: var(--bg2);
      min-height: 42px
    }

    .ctx-txt {
      flex: 1;
      min-width: 0;
      font-size: 12px;
      font-weight: 500;
      color: var(--muted);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis
    }

    .ctx-txt strong {
      color: var(--slate);
      font-weight: 700;
      font-size: 12.5px
    }

    .back-btn {
      display: flex;
      align-items: center;
      gap: 5px;
      height: 28px;
      padding: 0 12px;
      border-radius: var(--r-full);
      background: var(--surface);
      border: 1.5px solid var(--border);
      color: var(--slate);
      font-size: 11.5px;
      font-weight: 600;
      transition: all var(--fast);
      cursor: pointer;
      flex-shrink: 0
    }

    .back-btn:hover {
      background: var(--s3);
      color: var(--ink)
    }

    .back-btn.hidden {
      display: none
    }

    /* Nav list */
    .sb-list {
      flex: 1;
      overflow-y: auto;
      overflow-x: hidden;
      scrollbar-width: thin;
      scrollbar-color: var(--border) transparent;
      padding: 4px 0
    }

    .sb-list::-webkit-scrollbar {
      width: 2px
    }

    .sb-list::-webkit-scrollbar-thumb {
      background: var(--border);
      border-radius: 4px
    }

    /* Campus card */
    .c-card {
      display: flex;
      align-items: center;
      gap: 13px;
      width: 100%;
      padding: 13px 20px;
      background: transparent;
      border: none;
      border-bottom: 1px solid var(--border);
      cursor: pointer;
      text-align: left;
      transition: background var(--fast)
    }

    .c-card:hover {
      background: var(--s2)
    }

    .c-card.on {
      background: var(--wine-tint);
      border-left: 3px solid var(--wine)
    }

    .c-card.on .c-name {
      color: var(--wine)
    }

    .c-thumb {
      width: 44px;
      height: 44px;
      flex-shrink: 0;
      border-radius: 10px;
      overflow: hidden;
      background: var(--s3);
      border: 1.5px solid var(--border);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 20px;
      transition: border-color var(--fast)
    }

    .c-card.on .c-thumb {
      border-color: var(--gold-ring)
    }

    .c-thumb img {
      width: 100%;
      height: 100%;
      object-fit: cover
    }

    .c-body {
      flex: 1;
      min-width: 0
    }

    .c-name {
      font-size: 13.5px;
      font-weight: 700;
      color: var(--ink);
      margin-bottom: 3px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      letter-spacing: -.1px
    }

    .c-role {
      font-size: 11.5px;
      color: var(--muted);
      line-height: 1.4
    }

    .c-dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      flex-shrink: 0
    }

    /* Building items */
    .grp-lbl {
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .12em;
      text-transform: uppercase;
      color: var(--muted);
      padding: 14px 20px 6px;
      display: flex;
      align-items: center;
      justify-content: space-between
    }

    .grp-count {
      font-size: 10px;
      font-weight: 700;
      padding: 2px 7px;
      border-radius: var(--r-full);
      background: var(--s3);
      color: var(--muted)
    }

    .b-item {
      display: flex;
      align-items: center;
      gap: 11px;
      width: 100%;
      padding: 11px 20px;
      text-align: left;
      transition: background var(--fast);
      border: none;
      background: transparent;
      border-bottom: 1px solid var(--border);
      cursor: pointer
    }

    .b-item:hover {
      background: var(--s2)
    }

    .b-item.on {
      background: var(--wine-tint);
      border-left: 3px solid var(--wine)
    }

    .b-item.on .b-name {
      color: var(--wine)
    }

    .b-dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      flex-shrink: 0
    }

    .b-body {
      flex: 1;
      min-width: 0
    }

    .b-name {
      font-size: 13px;
      font-weight: 600;
      color: var(--ink);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      display: block;
      margin-bottom: 2px
    }

    .b-sub {
      font-size: 11.5px;
      color: var(--muted);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      display: block;
      line-height: 1.4
    }

    .b-badge {
      font-size: 9.5px;
      font-weight: 700;
      letter-spacing: .06em;
      text-transform: uppercase;
      padding: 3px 8px;
      border-radius: var(--r-full);
      background: var(--s3);
      color: var(--slate);
      border: 1px solid var(--border);
      flex-shrink: 0
    }

    .empty-st {
      padding: 36px 20px;
      text-align: center;
      color: var(--muted);
      font-size: 13px;
      line-height: 1.6
    }

    /* Footer */
    .sb-foot {
      flex-shrink: 0;
      padding: 12px 20px;
      border-top: 1px solid var(--border);
      display: flex;
      align-items: center;
      justify-content: space-between;
      background: var(--s2)
    }

    .foot-lbl {
      display: flex;
      align-items: center;
      gap: 7px;
      font-size: 11.5px;
      font-weight: 600;
      color: var(--slate)
    }

    .foot-dot {
      width: 7px;
      height: 7px;
      border-radius: 50%;
      background: var(--gold)
    }

    .ico-btn {
      width: 32px;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: var(--r-sm);
      color: var(--muted);
      transition: all var(--fast);
      border: 1.5px solid transparent;
      cursor: pointer
    }

    .ico-btn:hover {
      background: var(--surface);
      color: var(--ink);
      border-color: var(--border)
    }

    /* ═══════════════════════════════════════════════════════════
   MAP
═══════════════════════════════════════════════════════════ */
    .map-stage {
      position: relative;
      overflow: visible;
      background: #ECE4D3;
      width: 100%;
      height: 100%;
      /* Full-bleed immersive layout (Apple/Google Maps convention): the map
         canvas always fills the entire stage, and .floating-panel/.info-panel
         float above it as positioned overlays (see their own rules) instead
         of sharing a flex row that squeezes the map narrower whenever a
         panel is open. The map's real estate never changes based on panel
         state — only what's drawn on top of it does. */
    }

    .map-canvas {
      position: absolute;
      inset: 0;
      overflow: hidden;
    }

    #map {
      width: 100%;
      height: 100%
    }

    .leaflet-container {
      background: #ECE4D3 !important;
      font-family: var(--ui) !important
    }

    /* A `filter` used to sit here to mute Voyager's busy tile colors. Removed
       for performance: CSS `filter` on .leaflet-tile-pane forces the browser
       to recompute/recomposite the filter on every tile during pan and zoom
       instead of using cheap GPU-accelerated transforms, which is a well-known
       cause of laggy/stuttery map panning. Tiles now render at their normal
       CartoDB Voyager colors — slightly more saturated than before. */

    .leaflet-control-zoom {
      display: none
    }

    .leaflet-control-attribution {
      font-size: 9px !important;
      background: rgba(255, 255, 255, .75) !important;
      backdrop-filter: blur(8px);
      border: none !important;
      border-radius: 6px 0 0 0 !important;
      padding: 2px 10px !important;
      color: var(--muted) !important
    }

    /* Map controls — fused into one connected rounded pill (Google/Apple
       Maps convention) instead of loose separately-shadowed buttons, with
       hairline dividers between each real action. */
    .map-ctrl {
      position: absolute;
      bottom: 28px;
      right: 14px;
      transition: right var(--slow) var(--ease);
      z-index: var(--z-map-chrome);
      display: flex;
      flex-direction: column;
      gap: 8px;
    }

    /* The info panel now floats OVER the map instead of squeezing it
       narrower, so on desktop/tablet (where it docks to the right edge)
       the control stack and layer panel shift left to stay clear of it
       whenever it's open — mirrors what Apple/Google Maps do when their
       own place-card is showing. Mobile's info panel is a bottom sheet,
       not a right-edge overlay, so this stays out of that breakpoint.

       Both shifts are capped with min() at --sidebar-w — the flat
       `info-panel-w + gap` formula was tuned for desktop's wide map area;
       at tablet widths (641–1024px) it isn't, and pushes .layer-panel
       (226px wide) far enough left to land its *entire* width on top of
       the floating panel instead of just clearing the info panel's edge.
       The cap keeps this element's left edge no further left than the
       floating panel already assumes elsewhere (dir-fab, nav-hud), so it
       still shifts clear of the info panel wherever there's room, but
       never crosses into the floating panel's own space. */
    @media (min-width: 641px) {
      .map-stage:has(.info-panel:not(.collapsed)) .map-ctrl {
        right: min(calc(var(--info-panel-w) + 30px), calc(100vw - 44px - var(--sidebar-w)));
      }
      .map-stage:has(.info-panel:not(.collapsed)) .layer-panel {
        right: min(calc(var(--info-panel-w) + 74px), calc(100vw - 226px - var(--sidebar-w)));
      }
    }

    .map-ctrl-group {
      display: flex;
      flex-direction: column;
      background: rgba(255, 255, 255, .82);
      backdrop-filter: blur(16px) saturate(180%);
      -webkit-backdrop-filter: blur(16px) saturate(180%);
      border: 1.5px solid var(--border);
      border-radius: var(--r-md);
      box-shadow: var(--sh-lg);
      overflow: hidden;
    }

    .map-btn {
      width: 38px;
      height: 38px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: transparent;
      border: none;
      border-bottom: 1px solid var(--border);
      border-radius: 0;
      box-shadow: none;
      color: var(--slate);
      font-size: 18px;
      font-weight: 300;
      line-height: 1;
      transition: background var(--fast), color var(--fast);
      cursor: pointer
    }

    .map-ctrl-group .map-btn:last-child { border-bottom: none; }

    .map-btn:hover {
      color: var(--ink);
      background: var(--gold-tint);
    }

    .map-btn.on {
      background: var(--wine);
      color: #fff;
      border-color: transparent;
      box-shadow: 0 3px 12px var(--wine-glow)
    }

    .map-btn svg {
      fill: none;
      stroke: currentColor;
      stroke-width: 1.75;
      stroke-linecap: round;
      stroke-linejoin: round;
      display: block
    }

    /* Grouped zoom control — its own fused pill, same convention as
       .map-ctrl-group above, kept visually separate (own gap) since it's
       a different kind of action (continuous zoom vs. one-shot toggles). */
    .zoom-group {
      display: flex;
      flex-direction: column;
      background: rgba(255, 255, 255, .82);
      backdrop-filter: blur(16px) saturate(180%);
      -webkit-backdrop-filter: blur(16px) saturate(180%);
      border-radius: var(--r-md);
      overflow: hidden;
      box-shadow: var(--sh-lg);
      border: 1.5px solid var(--border);
    }

    .zoom-group .zoom-btn {
      border: none;
      border-bottom: 1px solid var(--border);
      border-radius: 0;
      box-shadow: none;
    }

    .zoom-group .zoom-btn:last-child {
      border-bottom: none;
    }

    /* Standalone floating "locate me" — a circle (not the fused rectangular
       .map-ctrl-group pill) so it visually reads as its own action, same
       convention .map-compass already uses. Sits directly under the zoom
       stack via normal flex flow in .map-ctrl (no extra positioning). */
    .map-locate-btn {
      width: 44px;
      height: 44px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: rgba(255, 255, 255, .82);
      backdrop-filter: blur(16px) saturate(180%);
      -webkit-backdrop-filter: blur(16px) saturate(180%);
      border: 1.5px solid var(--border);
      box-shadow: var(--sh-lg);
      color: var(--slate);
      cursor: pointer;
      transition: background var(--fast), color var(--fast), box-shadow var(--fast);
    }
    .map-locate-btn:hover { color: var(--ink); background: var(--gold-tint); }
    .map-locate-btn.tracking { background: var(--wine); color: #fff; border-color: transparent; box-shadow: 0 3px 12px var(--wine-glow); }
    .map-locate-btn svg { display: block; }

    body.dark .map-locate-btn { background: rgba(33, 27, 20, .82); border-color: var(--border); }
    body.dark .map-locate-btn:hover { background: rgba(254,193,16,.12); }

    /* View pill */
    .view-pill {
      position: absolute;
      top: 16px;
      left: 50%;
      transform: translateX(-50%);
      z-index: var(--z-map-chrome);
      display: flex;
      align-items: center;
      gap: 7px;
      padding: 6px 16px;
      border-radius: var(--r-full);
      background: rgba(255, 255, 255, .82);
      backdrop-filter: blur(14px) saturate(180%);
      -webkit-backdrop-filter: blur(14px) saturate(180%);
      border: 1.5px solid var(--border);
      box-shadow: var(--sh-sm);
      font-size: 11.5px;
      font-weight: 600;
      color: var(--slate);
      white-space: nowrap
    }

    .view-dot {
      width: 5px;
      height: 5px;
      border-radius: 50%;
      background: var(--gold);
      flex-shrink: 0;
      transition: background .4s ease;
    }

    /* Faint, real-weather-driven tint on the view pill's dot — set once
       js/weather.js's first request resolves (body[data-weather-cat] in
       renderCurrentWeather()). Deliberately the only piece of chrome outside
       the weather card itself that reacts to condition, to keep this a
       light touch rather than a wholesale re-theme. */
    body[data-weather-cat="rainy"]  .view-dot,
    body[data-weather-cat="stormy"] .view-dot { background: #4A7FB5; }
    body[data-weather-cat="foggy"]  .view-dot,
    body[data-weather-cat="cloudy"] .view-dot { background: var(--slate); }
    body[data-weather-cat="snowy"]  .view-dot { background: #7FA8C9; }

    /* display/size/position/z-index are NOT set here — they live entirely
       in the @media(max-width:640px) rule near .app-header above, which is
       the only place this button is ever shown. They used to ALSO be set
       here (unconditionally, further down the file than that phone rule),
       and because both rules had equal selector specificity, plain source
       order made this later, unconditional block win regardless of
       viewport — forcing display:none at every width and hiding the phone
       hamburger button entirely. Explicitly scoping the "hidden by
       default" case to min-width:641px below keeps the two rules mutually
       exclusive instead of relying on file order. */
    .mob-toggle {
      position: fixed;
      background: var(--surface);
      border: 1.5px solid var(--border);
      border-radius: var(--r-sm);
      box-shadow: var(--sh-md);
      color: var(--ink);
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: background var(--fast), color var(--fast)
    }

    @media (min-width: 641px) {
      .mob-toggle { display: none; }
    }

    .mob-toggle:hover {
      background: var(--s2);
      color: var(--gold)
    }

    /* Sidebar overlay (mobile) */
    .mob-overlay {
      display: none;
      position: fixed;
      inset: 0;
      z-index: var(--z-mobile-nav);
      background: rgba(28, 24, 16, .45);
      backdrop-filter: blur(2px);
      opacity: 0;
      pointer-events: none;
      transition: opacity var(--slow) var(--ease)
    }

    .mob-overlay.open {
      opacity: 1;
      pointer-events: auto !important
    }

    @media(max-width:640px) {
      .mob-overlay {
        display: block;
      }
    }

    @media(max-width:640px) {
      .map-ctrl {
        bottom: 90px;
        right: 14px;
        z-index: var(--z-map-chrome);
      }
    }

    /* ═══════════════════════════════════════════════════════════
   MARKERS
═══════════════════════════════════════════════════════════ */
    .dti-mk {
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: transform var(--fast) var(--ease);
      will-change: transform
    }

    .dti-mk:hover {
      transform: scale(1.12);
      z-index: 9999 !important
    }

    .dti-mk.sel {
      transform: scale(1.2);
      z-index: 9999 !important;
      filter: drop-shadow(0 0 0 3px rgba(193, 68, 14, .35)) drop-shadow(0 4px 10px rgba(193, 68, 14, .4));
    }

    /* Plain circular photo marker — no per-category pin shape/colour/icon.
       The real building/campus photo (IMG.hero / IMG.thumb) fills the
       circle; locations without one yet (mostly Berekuso's planned
       buildings) fall back to a flat neutral fill via .no-photo, never a
       colour standing in for a category. */
    .mk-photo {
      position: absolute;
      inset: 0;
      border-radius: 50%;
      overflow: hidden;
      background: var(--s3);
      border: 2.5px solid #fff;
      box-shadow: 0 3px 10px rgba(28, 24, 16, .28);
    }
    .mk-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
    .dti-mk-campus .mk-photo { border-width: 3px; }
    .dti-mk.no-photo .mk-photo { border-style: dashed; border-color: var(--border2); }

    /* Single brand colour, not residential/academic-coded — this is a
       "there's a place here" affordance, not a category indicator. */
    .mk-pulse {
      position: absolute;
      inset: -5px;
      border-radius: 50%;
      background: var(--wine);
      opacity: 0;
      pointer-events: none;
      animation: pulse 3s ease-out infinite
    }

    @keyframes pulse {
      0% {
        transform: scale(.3);
        opacity: .25
      }

      100% {
        transform: scale(2.2);
        opacity: 0
      }
    }

    .campus-tip {
      background: var(--surface);
      border: 1px solid rgba(28, 24, 16, .07);
      border-radius: var(--r-full);
      padding: 3px 11px;
      font-family: var(--ui);
      font-size: 11px;
      font-weight: 600;
      letter-spacing: .01em;
      color: var(--ink2);
      box-shadow: 0 1px 4px rgba(28, 24, 16, .10), 0 1px 2px rgba(28, 24, 16, .06);
      white-space: nowrap
    }

    .campus-tip.planned {
      border-color: rgba(193, 68, 14, .18);
      color: var(--wine)
    }

    /* Crowded campus clusters (Sabala/Main/Awale-T3 etc.) hide their
       always-on name pill until zoomed in enough that the pins themselves
       have real screen separation — see updateCrowdedTooltips() in
       script.js. Isolated campuses (no .crowded class) are unaffected and
       keep their label at every zoom, so the overview never loses all
       labeling. Pins and click/hover popups still work either way. */
    .campus-tip.crowded { display: none; }
    #map.zoomed-in .campus-tip.crowded { display: block; }

    .leaflet-tooltip {
      background: none !important;
      border: none !important;
      box-shadow: none !important;
      padding: 0 !important
    }

    .leaflet-tooltip-top::before,
    .leaflet-tooltip-bottom::before,
    .leaflet-tooltip-left::before,
    .leaflet-tooltip-right::before {
      display: none !important
    }

    /* ═══════════════════════════════════════════════════════════
   MARKER POPUP — Name / Category / Description / Directions
   White card, wine title, gold divider, rounded corners, soft shadow
═══════════════════════════════════════════════════════════ */
    .dti-popup .leaflet-popup-content-wrapper {
      background: var(--surface);
      border-radius: var(--r-lg);
      box-shadow: var(--sh-lg);
      padding: 0;
    }

    .dti-popup .leaflet-popup-content { margin: 0; line-height: 1.5 }
    .dti-popup .leaflet-popup-tip { background: var(--surface); box-shadow: var(--sh-sm) }

    /* Close button now floats over the photo (see .pop-photo below) instead
       of a plain background, so it needs its own dark scrim to stay legible
       on light photos, plus a round hit-target instead of Leaflet's default
       bare glyph. */
    .dti-popup .leaflet-popup-close-button {
      color: #fff !important;
      top: 10px !important;
      right: 10px !important;
      font-size: 15px !important;
      font-weight: 400 !important;
      background: rgba(28, 24, 16, .45) !important;
      border-radius: 50%;
      width: 22px !important;
      height: 22px !important;
      display: flex !important;
      align-items: center;
      justify-content: center;
      line-height: 1 !important;
    }

    .dti-popup .leaflet-popup-close-button:hover { background: rgba(28, 24, 16, .7) !important }

    /* Real photo (getHeroSrc() — same source as the info panel's own hero
       image, never fabricated) bleeding to the card's rounded top corners,
       Google-Maps-hover-card style. Sits above .dti-popup-inner's own
       padding rather than inside it. */
    .pop-photo {
      width: 100%;
      height: 108px;
      border-radius: var(--r-lg) var(--r-lg) 0 0;
      overflow: hidden;
      background: var(--s2);
    }
    .pop-photo img { width: 100%; height: 100%; object-fit: cover; display: block }

    .dti-popup-inner { padding: 16px 16px 14px; min-width: 190px }

    .pop-name {
      font-family: var(--display);
      font-size: 15.5px;
      font-weight: 600;
      color: var(--wine);
      line-height: 1.3;
      padding-right: 16px;
      margin-bottom: 6px;
    }

    .pop-cat {
      display: inline-block;
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .07em;
      text-transform: uppercase;
      color: var(--muted);
      padding-bottom: 9px;
      margin-bottom: 10px;
      border-bottom: 2.5px solid var(--gold);
    }

    .pop-desc {
      font-size: 12.5px;
      line-height: 1.6;
      color: var(--slate);
      margin-bottom: 13px;
    }

    .pop-actions { display: flex; gap: 7px }

    .pop-btn {
      flex: 1;
      height: 32px;
      border-radius: var(--r-sm);
      border: none;
      cursor: pointer;
      font-family: var(--ui);
      font-size: 11.5px;
      font-weight: 700;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all var(--fast);
      white-space: nowrap;
    }

    .pop-btn-primary { background: var(--wine); color: #fff }
    .pop-btn-primary:hover { background: var(--gold); color: var(--ink) }

    .pop-btn-secondary { background: var(--s2); color: var(--wine); border: 1.5px solid var(--border) }
    .pop-btn-secondary:hover { background: var(--gold-tint); border-color: var(--gold-ring); color: var(--gold-text) }

    body.dark .dti-popup .leaflet-popup-content-wrapper,
    body.dark .dti-popup .leaflet-popup-tip { background: var(--surface) }
    body.dark .pop-btn-secondary { background: var(--s2); border-color: var(--border) }

    /* ═══════════════════════════════════════════════════════════
   INFO PANEL  —  FIXED, never clipped
═══════════════════════════════════════════════════════════ */
    /* Right panel floats over the full-bleed map (desktop/tablet) — weather
       lives inside it as an always-present section (#ipWeather), with
       #pIdle / #pSelection swapping underneath depending on whether
       something's picked. See openPanel()/closePanel() in js/script.js.
       Collapsible via #ipCollapseBtn — slides off-canvas (.collapsed below)
       rather than shrinking in place, since the map never needs the space back. */
    .info-panel {
      position: absolute;
      top: 16px;
      right: 16px;
      bottom: 16px;
      width: var(--info-panel-w);
      max-width: calc(100vw - 32px);
      /* Card tier: solid white --surface, deliberately different from the
         sidebar's --sidebar-bg (see .floating-panel) — this is the actual
         content (place details, weather, stats) so it reads as the
         elevated "card" the muted sidebar chrome is there to lead into.
         No border/blur — flat card, shadow carries the depth. */
      background: var(--surface);
      border-radius: var(--r-lg);
      box-shadow: var(--sh-lg);
      display: flex;
      flex-direction: column;
      gap: 14px;
      padding: 16px 16px 0;
      z-index: var(--z-panel);
      overflow-y: auto;
      overflow-x: hidden;
      transform: translateX(0);
      transition: transform var(--slow) var(--ease), opacity var(--med) var(--ease);
      scrollbar-width: thin;
      scrollbar-color: var(--border) transparent;
      -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
      mask-image: linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%);
      /* Same reasoning as .floating-panel's will-change above — this panel
         carries the same backdrop-filter + transform-transition combo. */
      will-change: transform, opacity;
    }

    /* Collapsed = slide fully off-canvas to the right, same convention as
       .floating-panel.collapsed — the map underneath never resizes. Scoped
       to desktop/tablet for the same reason documented on
       .floating-panel.collapsed above: phone has its own bottom-sheet
       .info-panel rule with lower specificity, so an unscoped .collapsed
       here would win regardless of viewport and could shove the sheet
       off-canvas with no way back (button is display:none on phone). */
    @media (min-width: 641px) {
      .info-panel.collapsed {
        transform: translateX(calc(100% + 24px));
        opacity: 0;
        pointer-events: none;
      }
    }

    @media (min-width: 641px) and (max-width: 1024px) {
      :root { --info-panel-w: min(var(--panel-w), 56vw); }
    }

    /* Weather section — always present. Full card when idle (#pIdle
       showing), compact one-line strip once a campus/building is selected
       (js/script.js toggles this class in openPanel()/closePanel()). Inner
       styling (.weather-widget-header/.weather-current/etc.) comes from
       css/weather.css, loaded after this file. */
    .ip-weather { flex-shrink: 0; }
    .ip-weather.compact .weather-forecast { display: none; }
    .ip-weather.compact .weather-widget-body { padding-bottom: 8px; }
    /* True one-line strip: icon + temp + the friendly takeaway, no
       background tint/padding/stat grid/technical description — those
       stay reserved for the full idle-state card. */
    .ip-weather.compact .weather-current { padding: 4px 2px; background: none; gap: 8px; align-items: center; }
    .ip-weather.compact .wx-current-icon { font-size: 22px; filter: none; }
    .ip-weather.compact .wx-current-temp { font-size: 15px; display: inline; margin-right: 6px; }
    .ip-weather.compact .wx-current-takeaway { display: inline; font-size: 11px; margin: 0; color: var(--muted); font-weight: 500; }
    .ip-weather.compact .wx-current-desc,
    .ip-weather.compact .wx-current-time,
    .ip-weather.compact .wx-current-stats { display: none; }

    /* ── Stat chip row — real, computed numbers only ─────────────────
       No parking counts, no campus area, nothing this app can't
       actually derive from BUILDINGS/CAMPUSES. */
    .p-stat-row {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
      gap: 8px;
      padding: 0 22px 4px;
    }
    .p-stat-chip {
      text-align: center;
      background: var(--s2);
      border-radius: var(--r-md);
      padding: 8px 4px;
    }
    .p-stat-chip .v { font-family: var(--mono); font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.2; }
    .p-stat-chip .l { font-size: 9px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin-top: 2px; }

    /* ── Quick action row — Get Directions / Focus on map ─────────────
       Two buttons: primary full-width "Get Directions" (.focus-btn) plus
       a small icon-only "Focus on map" (.focus-btn.secondary), both real
       working actions. Styling lives in the .focus-btn rules below. */

    .p-idle {
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 24px 8px 32px;
      color: var(--muted);
      font-size: 12.5px;
      line-height: 1.6;
      gap: 22px;
    }

    /* Real, already-computed numbers — no invented parking counts or campus
       area, same "real data only" rule as .p-stat-row. Gives the idle state
       actual content instead of just a placeholder sentence over empty
       space (see renderIdleStats() in js/script.js). */
    .p-idle-stats {
      display: flex;
      gap: 10px;
    }
    .p-idle-stat {
      min-width: 74px;
      padding: 12px 14px;
      background: var(--s2);
      border: 1px solid var(--border);
      border-radius: var(--r-md);
    }
    .p-idle-stat .v { font-size: 20px; font-weight: 800; color: var(--ink); line-height: 1.2; }
    .p-idle-stat .l { font-size: 9.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; margin-top: 2px; }

    /* display:none → flex is a genuine re-entry into the render tree, so
       this animation replays every time a new campus/building is picked —
       a quiet fade+rise instead of content just snapping into place. */
    .p-selection {
      display: flex;
      flex-direction: column;
      margin: 0 -16px;
      flex: 1;
      min-height: 0;
      animation: pSelectionIn .38s var(--ease);
    }

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

    /* Stripe: gold = academic, wine = residential */
    .p-stripe {
      height: 4px;
      flex-shrink: 0;
      background: var(--gold);
      transition: background var(--med)
    }

    .p-stripe.wine {
      background: var(--wine)
    }

    .p-close {
      position: absolute;
      top: 14px;
      right: 14px;
      z-index: 10;
      width: 30px;
      height: 30px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: rgba(255, 255, 255, .92);
      backdrop-filter: blur(6px);
      border: 1.5px solid var(--border);
      border-radius: 6px;
      color: var(--muted);
      transition: all var(--fast);
      cursor: pointer
    }

    .p-close:hover {
      color: var(--ink);
      background: var(--s2)
    }

    /* Hero */
    .p-hero {
      flex-shrink: 0;
      height: 196px;
      overflow: hidden;
      position: relative;
      background: var(--s3)
    }

    .p-hero img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      transition: transform .55s var(--ease)
    }

    .p-hero:hover img {
      transform: scale(1.04)
    }

    .p-hero-overlay {
      position: absolute;
      inset: 0;
      background: linear-gradient(to top, rgba(28, 24, 16, .5) 0%, transparent 52%)
    }

    .p-hero-badge {
      position: absolute;
      top: 12px;
      left: 12px;
      padding: 3px 9px;
      border-radius: var(--r-full);
      font-size: 9.5px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: .09em;
      color: #fff;
      background: rgba(28, 24, 16, .42);
      backdrop-filter: blur(8px);
      border: 1px solid rgba(255, 255, 255, .2)
    }

    /* Placeholder hero */
    .p-hero-ph {
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 9px;
      background: var(--s3)
    }

    .p-hero-ph span {
      display: inline-flex;
      color: var(--dim);
    }

    .p-hero-ph small {
      font-size: 10px;
      font-weight: 600;
      letter-spacing: .08em;
      text-transform: uppercase;
      color: var(--dim)
    }

    /* Head */
    .p-head {
      flex-shrink: 0;
      padding: 20px 22px 16px;
      border-bottom: 1.5px solid var(--border)
    }

    .p-eyebrow {
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--gold);
      margin-bottom: 6px;
      display: block;
      transition: color var(--med)
    }

    .p-eyebrow.wine {
      color: var(--wine)
    }

    .p-title {
      font-family: var(--display);
      font-size: 22px;
      font-weight: 600;
      letter-spacing: -.3px;
      line-height: 1.25;
      color: var(--ink);
      padding-right: 40px;
      margin-bottom: 5px
    }

    .p-subtitle {
      font-size: 13px;
      color: var(--muted);
      margin-bottom: 12px;
      line-height: 1.5
    }

    .p-tags {
      display: flex;
      gap: 6px;
      flex-wrap: wrap
    }

    .p-tag {
      padding: 3px 10px;
      border-radius: var(--r-full);
      font-size: 10.5px;
      font-weight: 600;
      background: var(--s2);
      color: var(--muted);
      border: 1.5px solid var(--border)
    }

    .p-tag.gold {
      background: var(--gold-tint);
      color: var(--gold-text);
      border-color: var(--gold-ring)
    }

    .p-tag.wine {
      background: var(--wine-tint);
      color: var(--wine);
      border-color: var(--wine-ring)
    }

    /* Body */
    .p-body {
      flex: 1;
      overflow-y: auto;
      padding: 20px 22px 32px;
      scrollbar-width: thin;
      scrollbar-color: var(--border) transparent;
      display: flex;
      flex-direction: column;
      gap: 22px
    }

    .p-body::-webkit-scrollbar {
      width: 3px
    }

    .p-body::-webkit-scrollbar-thumb {
      background: var(--border);
      border-radius: 4px
    }

    .p-desc {
      font-size: 14px;
      line-height: 1.78;
      color: var(--slate)
    }

    .sec-title {
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .13em;
      text-transform: uppercase;
      color: var(--muted);
      padding-bottom: 10px;
      border-bottom: 1.5px solid var(--border);
      margin-bottom: 13px
    }

    /* Gallery */
    .gallery {
      display: flex;
      gap: 8px;
      overflow-x: auto;
      scrollbar-width: none;
      margin: 0 -22px;
      padding: 0 22px
    }

    .gallery::-webkit-scrollbar {
      display: none
    }

    .g-item {
      flex-shrink: 0;
      width: 134px;
      height: 90px;
      border-radius: 11px;
      overflow: hidden;
      border: 1.5px solid var(--border);
      cursor: pointer;
      position: relative;
      background: var(--s3);
      transition: transform var(--fast) var(--ease), box-shadow var(--fast)
    }

    .g-item:hover {
      transform: translateY(-3px);
      box-shadow: var(--sh-md)
    }

    .g-item img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      transition: transform .45s var(--ease)
    }

    .g-item:hover img {
      transform: scale(1.07)
    }

    .g-item-ph {
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 5px
    }

    .g-item-ph span {
      display: inline-flex;
      color: var(--dim);
    }

    .g-item-ph small {
      font-size: 9.5px;
      font-weight: 600;
      letter-spacing: .05em;
      text-transform: uppercase;
      color: var(--dim)
    }

    .g-caption {
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      padding: 5px 8px;
      background: linear-gradient(to top, rgba(28, 24, 16, .65), transparent);
      color: #fff;
      font-size: 9.5px;
      font-weight: 600
    }

    .g-item.ph {
      cursor: default
    }

    /* Stats */
    .stats-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 9px
    }

    .stat-card {
      padding: 14px 15px;
      background: var(--s2);
      border-radius: var(--r-lg);
    }

    .stat-val {
      font-family: var(--mono);
      font-size: 18px;
      font-weight: 600;
      color: var(--ink);
      line-height: 1.1
    }

    .stat-lbl {
      font-size: 11px;
      font-weight: 500;
      color: var(--muted);
      margin-top: 4px
    }

    /* Highlights */
    .hl-list {
      list-style: none;
      display: flex;
      flex-direction: column;
      gap: 10px
    }

    .hl-list li {
      display: flex;
      align-items: flex-start;
      gap: 11px;
      font-size: 13.5px;
      color: var(--slate);
      line-height: 1.65
    }

    .hl-list li::before {
      content: '';
      flex-shrink: 0;
      width: 5px;
      height: 5px;
      border-radius: 50%;
      background: var(--gold);
      margin-top: 8px
    }

    .hl-list li.wine::before {
      background: var(--wine)
    }

    /* Timeline */
    .tl {
      display: flex;
      flex-direction: column;
      position: relative
    }

    .tl::before {
      content: '';
      position: absolute;
      left: 27px;
      top: 10px;
      bottom: 10px;
      width: 1.5px;
      background: var(--border)
    }

    .tl-row {
      display: flex;
      align-items: flex-start;
      gap: 14px;
      padding: 8px 0
    }

    .tl-yr {
      flex-shrink: 0;
      width: 44px;
      font-size: 11px;
      font-weight: 600;
      color: var(--gold);
      text-align: right;
      padding-top: 2px;
      font-family: var(--mono)
    }

    .tl-dot {
      flex-shrink: 0;
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background: var(--gold);
      border: 2px solid var(--surface);
      box-shadow: 0 0 0 1.5px var(--gold);
      margin-top: 4px;
      z-index: 1
    }

    .tl-text {
      font-size: 13px;
      color: var(--slate);
      line-height: 1.6
    }

    /* Facilities */
    .fac-wrap {
      display: flex;
      flex-wrap: wrap;
      gap: 6px
    }

    .fac-chip {
      padding: 5px 13px;
      border-radius: var(--r-full);
      background: var(--s2);
      border: 1.5px solid var(--border);
      font-size: 12px;
      font-weight: 500;
      color: var(--slate)
    }

    /* Coords */
    .coord-row {
      display: flex;
      gap: 8px
    }

    .coord-cell {
      flex: 1;
      padding: 11px 13px;
      background: var(--s2);
      border-radius: var(--r-lg);
    }

    .coord-lbl {
      font-size: 9px;
      font-weight: 700;
      letter-spacing: .08em;
      text-transform: uppercase;
      color: var(--dim);
      margin-bottom: 3px
    }

    .coord-val {
      font-size: 12px;
      font-family: var(--mono);
      color: var(--slate)
    }

    /* Sticky action footer — pinned below the scrolling .p-body so the
       primary actions stay reachable without scrolling past Highlights/
       Timeline/Facilities on content-rich entries */
    .p-actions {
      position: relative;
      flex-shrink: 0;
      display: flex;
      gap: 8px;
      padding: 14px 22px calc(14px + env(safe-area-inset-bottom));
      border-top: 1.5px solid var(--border);
      background: var(--surface)
    }

    /* Focus button */
    .focus-btn {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      flex: 1;
      width: auto;
      height: 46px;
      background: var(--wine);
      color: #fff;
      border-radius: var(--r-sm);
      font-size: 13.5px;
      font-weight: 700;
      letter-spacing: .02em;
      transition: all var(--fast) var(--ease);
      border: none;
      cursor: pointer;
      box-shadow: 0 2px 10px var(--wine-glow)
    }

    .focus-btn:hover {
      background: var(--gold);
      color: var(--ink);
      box-shadow: 0 5px 20px var(--gold-glow);
      transform: translateY(-1px)
    }

    .focus-btn:active {
      transform: none
    }

    .focus-btn.wine {
      background: var(--wine);
      box-shadow: 0 2px 10px var(--wine-glow)
    }

    .focus-btn.wine:hover {
      background: var(--wine-dk);
      box-shadow: 0 5px 20px var(--wine-glow)
    }

    /* Secondary (icon-only) variant — used for "Focus on map" once
       "Get Directions" takes the primary slot in the action row */
    .focus-btn.secondary {
      flex: 0 0 auto;
      width: 46px;
      padding: 0;
      background: var(--s2);
      color: var(--wine);
      border: 1.5px solid var(--border);
      box-shadow: none
    }

    .focus-btn.secondary:hover {
      background: var(--gold-tint);
      border-color: var(--gold-ring);
      color: var(--gold-text);
      box-shadow: none;
      transform: none
    }

    .focus-btn svg {
      fill: none;
      stroke: currentColor;
      stroke-width: 2.2;
      stroke-linecap: round;
      display: block
    }

    /* Lightbox */
    .lb {
      position: fixed;
      inset: 0;
      /* --z-lightbox sits above the FAB/header/task-overlay/nav-hud tiers
         on purpose — this is a full-screen modal photo viewer, so nothing
         from those (all body-level fixed siblings, same stacking context)
         should be able to poke through its dark backdrop. Still below
         --z-onboarding/--z-modal/--z-loading, which stay the true top layer. */
      z-index: var(--z-lightbox);
      background: rgba(28, 24, 16, .92);
      display: flex;
      align-items: center;
      justify-content: center;
      opacity: 0;
      visibility: hidden;
      transition: opacity .22s, visibility .22s;
      cursor: pointer
    }

    .lb.open {
      opacity: 1;
      visibility: visible
    }

    .lb img {
      max-width: 92vw;
      max-height: 88vh;
      border-radius: 10px;
      box-shadow: 0 24px 80px rgba(0, 0, 0, .5);
      pointer-events: none
    }

    .lb-close {
      position: absolute;
      top: 18px;
      right: 18px;
      width: 38px;
      height: 38px;
      background: rgba(255, 255, 255, .14);
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
      font-size: 18px;
      cursor: pointer;
      border: 1px solid rgba(255, 255, 255, .2)
    }

    /* ═══════════════════════════════════════════════════════════
   DIRECTIONS — FAB BUTTON (fixed to viewport)
═══════════════════════════════════════════════════════════ */
    .dir-fab {
      position: fixed;
      bottom: 32px;
      left: calc(var(--sidebar-w) + 16px);
      z-index: var(--z-fab);
      display: flex;
      align-items: center;
      gap: 9px;
      height: 44px;
      padding: 0 20px;
      background: var(--wine);
      color: #fff;
      border-radius: var(--r-full);
      border: none;
      cursor: pointer;
      font-size: 13.5px;
      font-weight: 700;
      letter-spacing: .02em;
      box-shadow: 0 4px 22px var(--wine-glow), 0 2px 8px rgba(28, 24, 16, .18);
      transition: all var(--fast) var(--ease);
    }

    .dir-fab:hover {
      background: var(--gold);
      color: var(--ink);
      box-shadow: 0 8px 28px var(--gold-glow);
      transform: translateY(-2px)
    }

    .dir-fab.active {
      background: var(--wine-dk)
    }

    .dir-fab svg {
      flex-shrink: 0;
      width: 15px;
      height: 15px
    }

    @media(max-width:640px) {
      .dir-fab {
        left: 16px;
        /* clears the floating-panel's resting "peek" strip */
        bottom: 76px;
        z-index: var(--z-fab);
      }
    }

    /* ═══════════════════════════════════════════════════════════
   DIRECTIONS PANEL — right-side slide-in dialog. Anchored to the right
   edge, offset from the top bar and bottom edge, slides in from off-
   screen (translateX). Content (.dir-results) simply scrolls within a
   fixed-height column — no peek/expand states.
═══════════════════════════════════════════════════════════ */
    .dir-panel {
      position: fixed;
      right: 16px;
      top: 84px;
      bottom: 16px;
      width: min(400px, 92vw);
      z-index: var(--z-overlay-panel);
      background: var(--surface);
      border-radius: var(--r-lg);
      box-shadow: -8px 0 40px rgba(28, 24, 16, .16), 0 4px 24px rgba(28, 24, 16, .10);
      transform: translateX(calc(100% + 32px));
      transition: transform var(--slow) var(--ease);
      display: flex;
      flex-direction: column;
      overflow-y: auto;
      overscroll-behavior: contain;
      pointer-events: none;
    }

    .dir-panel.open {
      transform: translateX(0);
      pointer-events: all
    }

    @media(max-width:640px) {
      .dir-panel {
        left: 0;
        right: 0;
        top: auto;
        bottom: 0;
        width: 100%;
        max-height: 82dvh;
        border-radius: var(--r-xl) var(--r-xl) 0 0;
        transform: translateY(100%);
        padding-bottom: env(safe-area-inset-bottom);
      }
      .dir-panel.open { transform: translateY(0); }
    }

    /* header */
    .dir-hd {
      flex-shrink: 0;
      padding: 12px 16px 12px;
      border-bottom: 1.5px solid var(--border)
    }

    .dir-hd-row {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-bottom: 12px
    }

    .dir-hd-icon {
      width: 30px;
      height: 30px;
      background: var(--wine);
      border-radius: 8px;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0
    }

    .dir-hd-icon svg {
      stroke: #fff;
      fill: none;
      width: 14px;
      height: 14px;
      stroke-width: 2;
      stroke-linecap: round;
      stroke-linejoin: round
    }

    .dir-hd-title {
      font-family: var(--display);
      font-size: 15px;
      font-weight: 600;
      color: var(--ink);
      letter-spacing: -.1px;
      flex: 1
    }

    .dir-hd-close {
      width: 28px;
      height: 28px;
      border-radius: 50%;
      background: var(--s3);
      border: none;
      cursor: pointer;
      color: var(--muted);
      font-size: 12px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all var(--fast);
      flex-shrink: 0
    }

    .dir-hd-close:hover {
      background: var(--s4);
      color: var(--ink)
    }

    /* From / To inputs */
    .dir-fields {
      position: relative;
      display: flex;
      flex-direction: column;
      gap: 7px;
      padding-right: 40px
    }

    .dir-connector {
      position: absolute;
      left: 18px;
      top: 50px;
      width: 2px;
      height: 28px;
      background: repeating-linear-gradient(to bottom, var(--gold) 0, var(--gold) 5px, transparent 5px, transparent 9px)
    }

    .dir-field {
      display: flex;
      align-items: center;
      gap: 10px
    }

    .dir-dot {
      width: 30px;
      height: 30px;
      flex-shrink: 0;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 11px;
      font-weight: 800;
      z-index: 1
    }

    .dir-dot.a {
      background: var(--gold);
      color: #fff;
      box-shadow: 0 2px 8px var(--gold-glow)
    }

    .dir-dot.b {
      background: var(--wine);
      color: #fff;
      box-shadow: 0 2px 8px var(--wine-glow)
    }

    .dir-sel {
      flex: 1;
      height: 38px;
      padding: 0 12px;
      background: var(--s2);
      border: 1.5px solid var(--border);
      border-radius: var(--r-sm);
      color: var(--ink);
      font-family: var(--ui);
      font-size: 12.5px;
      font-weight: 500;
      outline: none;
      cursor: pointer;
      transition: border-color var(--fast), box-shadow var(--fast), background var(--fast);
      appearance: none;
      -webkit-appearance: none;
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 24 24' fill='none' stroke='%23BCB0A0' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: right 10px center;
      background-size: 10px;
      padding-right: 28px;
    }

    .dir-sel:focus {
      border-color: var(--gold-ring);
      box-shadow: 0 0 0 3px var(--gold-tint);
      background-color: var(--surface)
    }

    .dir-swap-btn {
      position: absolute;
      right: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 36px;
      height: 36px;
      border-radius: 50%;
      background: var(--surface);
      border: 1.5px solid var(--border);
      color: var(--slate);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: var(--sh-sm);
      transition: all var(--fast);
      flex-shrink: 0;
    }

    .dir-swap-btn:hover {
      background: var(--gold-tint);
      border-color: var(--gold-ring);
      color: var(--gold-text)
    }

    .dir-swap-btn svg {
      width: 14px;
      height: 14px;
      stroke: currentColor;
      fill: none;
      stroke-width: 2.2;
      stroke-linecap: round;
      stroke-linejoin: round
    }

    /* Go button — compact, auto width, centered */
    .dir-go {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 7px;
      height: 38px;
      padding: 0 20px;
      margin-top: 10px;
      background: var(--wine);
      color: #fff;
      border: none;
      cursor: pointer;
      border-radius: var(--r-full);
      font-family: var(--ui);
      font-size: 13px;
      font-weight: 700;
      letter-spacing: .02em;
      box-shadow: 0 3px 14px var(--wine-glow);
      transition: all var(--fast) var(--ease);
    }

    .dir-go:hover:not(:disabled) {
      background: var(--gold);
      color: var(--ink);
      transform: translateY(-1px);
      box-shadow: 0 6px 22px var(--gold-glow)
    }

    .dir-go:disabled {
      background: var(--s4);
      color: var(--muted);
      box-shadow: none;
      cursor: not-allowed
    }

    .dir-go svg {
      fill: none;
      stroke: currentColor;
      stroke-width: 2.2;
      stroke-linecap: round;
      width: 13px;
      height: 13px
    }

    /* Go button row — centers the button */
    .dir-go-row {
      display: flex;
      justify-content: center;
    }

    /* AI ask */
    .dir-ai-sep {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-top: 10px
    }

    .dir-ai-sep-line {
      flex: 1;
      height: 1px;
      background: var(--border)
    }

    .dir-ai-sep-txt {
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .1em;
      text-transform: uppercase;
      color: var(--dim);
      white-space: nowrap
    }

    .dir-ai-row {
      display: flex;
      gap: 7px;
      margin-top: 8px
    }

    .dir-ai-inp {
      flex: 1;
      height: 38px;
      padding: 0 12px;
      resize: none;
      background: var(--s2);
      border: 1.5px solid var(--border);
      border-radius: var(--r-sm);
      color: var(--ink);
      font-family: var(--ui);
      font-size: 12.5px;
      outline: none;
      transition: border-color var(--fast), box-shadow var(--fast);
      white-space: nowrap;
      overflow: hidden;
    }

    .dir-ai-inp:focus {
      border-color: var(--gold-ring);
      box-shadow: 0 0 0 3px var(--gold-tint);
      background: var(--surface)
    }

    .dir-ai-inp::placeholder {
      color: var(--dim)
    }

    .dir-ai-send {
      flex-shrink: 0;
      width: 38px;
      height: 38px;
      border-radius: var(--r-sm);
      background: var(--ink);
      color: #fff;
      border: none;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all var(--fast);
    }

    .dir-ai-send:hover:not(:disabled) {
      background: var(--ink2);
      transform: scale(1.04)
    }

    .dir-ai-send:disabled {
      background: var(--dim);
      cursor: not-allowed;
      transform: none
    }

    .dir-ai-send svg {
      width: 14px;
      height: 14px;
      stroke: currentColor;
      fill: none;
      stroke-width: 2.2;
      stroke-linecap: round
    }

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

    .dir-ai-send.busy svg {
      animation: dir-spin .7s linear infinite
    }

    /* scrollable results area */
    .dir-results {
      flex: 1;
      overflow-y: auto;
      padding: 12px 14px 20px;
      display: flex;
      flex-direction: column;
      gap: 10px;
      scrollbar-width: thin;
      scrollbar-color: var(--border) transparent;
    }

    .dir-results::-webkit-scrollbar {
      width: 3px
    }

    .dir-results::-webkit-scrollbar-thumb {
      background: var(--border);
      border-radius: 99px
    }

    /* empty state */
    .dir-empty {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 10px;
      padding: 28px 20px;
      text-align: center;
    }

    .dir-empty-icon {
      display: inline-flex;
      color: var(--dim);
    }

    .dir-empty-title {
      font-size: 14px;
      font-weight: 600;
      color: var(--ink)
    }

    .dir-empty-sub {
      font-size: 12.5px;
      color: var(--muted);
      line-height: 1.6;
      max-width: 280px
    }

    /* ═══════════════════════════════════════════════════════════
       PHONE (≤640px) — place-details panel becomes a bottom sheet
       (matches Google Maps' place-card behavior) instead of a
       right-edge drawer; old .sidebar is fully retired (display:none
       always — see base rule) so its mobile transform rules are gone
═══════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
       .info-panel {
         position: fixed;
         flex: none;
         top: auto;
         bottom: 0;
         left: 0;
         right: 0;
         width: 100%;
         height: auto;
         /* Compact by default (just the weather section) — expands to a
            tall bottom sheet once something's selected (js/script.js toggles
            .has-selection in openPanel()/closePanel()), instead of the old
            fully-hidden-until-selected drawer. Weather is always reachable. */
         max-height: 30vh;
         border-radius: var(--r-xl) var(--r-xl) 0 0;
         box-shadow: 0 -8px 40px rgba(28, 24, 16, .18);
         transition: max-height var(--slow) var(--ease);
         z-index: var(--z-panel);
         /* safe-area bottom inset is handled by .p-actions (the last visible
            child) instead, so it doesn't double up below the action row */
       }

       .info-panel.has-selection { max-height: 85dvh; }
       .info-collapse-btn { display: none; }

       .info-panel::before {
         content: '';
         display: block;
         flex-shrink: 0;
         width: 36px; height: 4px; border-radius: 99px;
         background: var(--border2);
         margin: 10px auto 0;
       }

       .view-pill {
         left: auto;
         right: 14px;
         transform: none;
         top: auto;
         /* dir-fab occupies bottom:76px + its 44px height — sit clear above it
            instead of sharing the same band, which let the two collide/overlap */
         bottom: 132px
       }

       .p-hero {
         height: 160px
       }

       .nav-hud {
         top: 72px;
         bottom: auto;
         left: 12px;
         right: 12px;
         width: auto;
         min-width: unset;
       }
     }

     /* ═══════════════════════════════════════════════════════════
        GLASSMORPHISM DIRECTIONS MODAL
     ═══════════════════════════════════════════════════════════ */
     .modal-overlay {
       position: fixed;
       top: 0; left: 0;
       width: 100%; height: 100%;
       background: rgba(28, 24, 16, 0.6);
       backdrop-filter: blur(10px);
       -webkit-backdrop-filter: blur(10px);
       display: flex;
       justify-content: center;
       align-items: center;
       z-index: var(--z-modal);
       opacity: 0;
       pointer-events: none;
       transition: opacity 0.35s cubic-bezier(.16,1,.3,1);
     }
     .modal-overlay.active {
       opacity: 1;
       pointer-events: auto;
     }
     .modal-close-x {
       position: absolute;
       top: 14px; right: 14px;
       width: 30px; height: 30px;
       border-radius: 50%;
       background: rgba(255,255,255,0.08);
       border: 1px solid rgba(255,255,255,0.14);
       color: rgba(255,255,255,0.6);
       font-size: 13px;
       display: flex; align-items: center; justify-content: center;
       cursor: pointer;
       transition: all 0.2s;
       line-height: 1;
     }
     .modal-close-x:hover { background: rgba(255,255,255,0.16); color: #fff; }
     .input-group {
       margin-bottom: 16px;
       position: relative;
     }
     .input-group label {
       display: block;
       font-size: 10.5px;
       font-weight: 700;
       letter-spacing: 0.1em;
       text-transform: uppercase;
       margin-bottom: 7px;
       color: rgba(255,255,255,0.5);
       font-family: 'DM Sans', sans-serif;
     }
     .modal-actions {
       display: flex;
       justify-content: flex-end;
       gap: 10px;
       margin-top: 22px;
     }
     /* ═══════════════════════════════════════════════════════════
        ADD FACILITY MODAL — light card, matches the spec's Modal Design
        (white bg, wine title, gold focus indicators, rounded corners)
     ═══════════════════════════════════════════════════════════ */
     .light-modal {
       position: relative;
       background: var(--surface);
       border-radius: var(--r-xl);
       padding: 28px 26px 24px;
       width: 92%;
       max-width: 400px;
       box-shadow: var(--sh-xl);
       transform: translateY(28px) scale(0.95);
       transition: transform 0.38s cubic-bezier(.16,1,.3,1), opacity 0.38s cubic-bezier(.16,1,.3,1);
       opacity: 0;
     }
     .modal-overlay.active .light-modal { transform: translateY(0) scale(1); opacity: 1; }
     .light-modal::before {
       content: '';
       position: absolute; top: 0; left: 26px; right: 26px; height: 3px;
       background: linear-gradient(90deg, transparent, var(--gold), transparent);
       border-radius: 0 0 3px 3px;
     }
     .light-modal h3 {
       margin: 0 0 20px 0;
       font-family: var(--display);
       color: var(--wine);
       font-size: 1.3rem;
       font-weight: 600;
       letter-spacing: -0.2px;
     }
     .modal-close-x.light {
       position: absolute; top: 14px; right: 14px;
       width: 28px; height: 28px; border-radius: 50%;
       background: var(--s2); border: 1px solid var(--border); color: var(--muted);
       font-size: 12px; display: flex; align-items: center; justify-content: center;
       cursor: pointer; transition: all 0.2s;
     }
     .modal-close-x.light:hover { background: var(--s3); color: var(--wine); }
     .light-modal .input-group { margin-bottom: 15px; }
     .light-modal .input-group label {
       display: block; font-size: 10.5px; font-weight: 700; letter-spacing: 0.09em;
       text-transform: uppercase; margin-bottom: 6px; color: var(--muted);
       font-family: var(--ui);
     }
     .light-input {
       width: 100%; padding: 11px 13px;
       background: var(--s2); border: 1.5px solid var(--border);
       border-radius: var(--r-sm); color: var(--ink);
       font-family: var(--ui); font-size: 13.5px; outline: none;
       box-sizing: border-box; transition: all var(--fast);
       appearance: none; -webkit-appearance: none;
     }
     select.light-input {
       background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 24 24' fill='none' stroke='%23978A78' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
       background-repeat: no-repeat; background-position: right 12px center; background-size: 10px;
       padding-right: 30px; cursor: pointer;
     }
     .light-input:focus {
       border-color: var(--gold-ring);
       box-shadow: 0 0 0 3px var(--gold-tint);
       background: var(--surface);
     }
     .modal-actions.light-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
     .btn-light {
       padding: 10px 20px; border-radius: var(--r-sm); border: none; cursor: pointer;
       font-family: var(--ui); font-size: 13px; font-weight: 700; transition: all 0.2s;
     }
     .btn-light.secondary { background: var(--s2); color: var(--slate); border: 1.5px solid var(--border); }
     .btn-light.secondary:hover { background: var(--s3); }
     .btn-light.primary { background: var(--wine); color: #fff; }
     .btn-light.primary:hover { background: var(--gold); color: var(--ink); }

     .place-hint {
       position: fixed; top: 78px; left: 50%;
       transform: translateX(-50%) translateY(-16px);
       z-index: var(--z-task-overlay);
       background: var(--wine); color: #fff;
       padding: 12px 10px 12px 18px; border-radius: var(--r-full);
       display: none; align-items: center; gap: 14px;
       box-shadow: 0 8px 28px rgba(193,68,14,.4);
       font-size: 13px; font-weight: 600;
       opacity: 0; transition: all 0.3s var(--ease);
     }
     .place-hint.active { display: flex; opacity: 1; transform: translateX(-50%) translateY(0); }
     .place-hint button {
       background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.3); color: #fff;
       border-radius: var(--r-full); padding: 6px 14px; font-size: 11.5px; font-weight: 700;
       font-family: var(--ui); cursor: pointer; transition: all var(--fast);
     }
     .place-hint button:hover { background: var(--gold); border-color: transparent; color: var(--ink); }

     /* ═══════════════════════════════════════════════════════════
        FIX BUILDING LOCATION — live GPS calibration tool
     ═══════════════════════════════════════════════════════════ */
     .fix-loc-panel {
       position: fixed;
       bottom: 28px;
       left: 50%;
       transform: translateX(-50%) translateY(16px);
       width: 300px;
       max-width: calc(100vw - 32px);
       z-index: var(--z-task-overlay);
       background: var(--surface);
       border-radius: var(--r-lg);
       box-shadow: var(--sh-xl);
       padding: 14px 16px 16px;
       opacity: 0;
       pointer-events: none;
       transition: all 0.3s var(--ease);
     }
     .fix-loc-panel.open { opacity: 1; pointer-events: all; transform: translateX(-50%) translateY(0); }

     .fix-loc-head {
       display: flex; align-items: center; justify-content: space-between;
       margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1.5px solid var(--border);
     }
     .fix-loc-title {
       display: flex; align-items: center; gap: 7px;
       font-size: 12.5px; font-weight: 700; color: var(--wine);
       letter-spacing: .01em;
     }
     .fix-loc-close {
       width: 22px; height: 22px; border-radius: 50%; border: none;
       background: var(--s2); color: var(--muted); cursor: pointer;
       font-size: 10px; display: flex; align-items: center; justify-content: center;
       transition: all var(--fast);
     }
     .fix-loc-close:hover { background: var(--s3); color: var(--wine); }

     .fix-loc-coords { display: flex; gap: 8px; margin-bottom: 8px; }
     .fix-loc-coord-cell {
       flex: 1; padding: 8px 10px; background: var(--s2);
       border-radius: var(--r-sm);
     }
     .fix-loc-coord-lbl {
       display: block; font-size: 8.5px; font-weight: 700; letter-spacing: .08em;
       text-transform: uppercase; color: var(--dim); margin-bottom: 2px;
     }
     .fix-loc-coord-val {
       font-family: var(--mono);
       font-size: 12px; color: var(--ink); font-weight: 600;
     }

     .fix-loc-accuracy {
       font-size: 11px; font-weight: 600; color: var(--muted);
       margin-bottom: 12px; padding: 5px 10px; border-radius: var(--r-sm);
       background: var(--s2); text-align: center;
     }
     .fix-loc-accuracy.good { background: var(--wine-tint); color: var(--wine) }
     .fix-loc-accuracy.ok { background: var(--gold-tint); color: var(--gold-text) }
     .fix-loc-accuracy.poor { background: var(--wine-tint); color: var(--wine-dk) }

     .fix-loc-panel .input-group { margin-bottom: 12px }
     .fix-loc-panel .input-group label {
       display: block; font-size: 10px; font-weight: 700; letter-spacing: .08em;
       text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
     }

     .fix-loc-set { width: 100%; }
     .fix-loc-set:disabled { background: var(--s4); color: var(--muted); cursor: not-allowed; }

     .fix-loc-status {
       margin-top: 9px; font-size: 11.5px; font-weight: 600; color: var(--wine);
       text-align: center; min-height: 14px;
     }

     @media (max-width: 640px) {
       .fix-loc-panel { bottom: 96px; }
     }

     @media (min-width: 641px) and (max-width: 1024px) {
       .fix-loc-panel { width: 320px; }
     }

     /* ═══════════════════════════════════════════════════════════
        TOUCH TARGETS — phone + tablet are both touch-first; rows and
        buttons sized fine for a mouse cursor are too tight for a
        fingertip (Google/Apple both target ~40-44px minimum)
     ═══════════════════════════════════════════════════════════ */
     @media (max-width: 1024px) {
       .fp-building-item, .fp-cat-item { padding: 10px; min-height: 40px; }
       .fp-campus-item { min-height: 44px; }
       /* 44px, not 40 — the mobile UX spec calls for ~44-52px touch targets
          on floating map controls specifically (zoom/locate/layers), and
          40px undershot that. */
       .map-btn { width: 44px; height: 44px; }
       .fp-action-btn { padding: 13px 8px; }
       .ah-profile { width: 40px; height: 40px; }
       /* Every text input/select in the app was under 16px (12.5-13.5px).
          iOS Safari force-zooms the ENTIRE PAGE in on focus of any form
          control smaller than 16px — the search box, the directions
          dropdowns, the AI directions field and every modal input all
          triggered this, then left the page zoomed in until the visitor
          manually pinched back out. Google/Apple Maps never do this
          because their inputs are always >=16px. Bumped here rather than
          globally so desktop's intentionally smaller type is untouched. */
       .srch-input, .dir-sel, .dir-ai-inp, .light-input, .campus-sel { font-size: 16px; }
     }

     body.dark .light-modal { background: var(--surface); }
     body.dark .light-input { background: var(--s2); border-color: var(--border); color: var(--ink); }
     body.dark .light-input:focus { background: var(--surface); }

    /* ═══════════════════════════════════════════════════════════
       DARK MODE
    ═══════════════════════════════════════════════════════════ */
    body.dark {
      /* Snapchat-inspired: a genuinely near-black canvas (not dark brown)
         with a bigger jump up to card/panel surfaces, so panels read as
         distinct, "floating" layers instead of a gradual gray ramp. */
      --bg:       #0A0908;
      --bg2:      #141110;
      --surface:  #211B14;
      --s2:       #2A2318;
      --s3:       #342C1E;
      --s4:       #403524;
      --border:   #4A3E2A;
      --border2:  #5C4E36;

      --ink:   #F0EBE0;
      --ink2:  #D8D0C0;
      --slate: #B8AE9C;
      --muted: #8A8070;
      --dim:   #5C5448;

      --gold-tint: rgba(244,162,89,.15);
      --gold-glow: rgba(244,162,89,.28);
      --gold-ring: rgba(244,162,89,.38);
      --gold-xlt:  rgba(244,162,89,.18);
      /* The light-mode dark --gold-text (#8A4E14) would fail contrast
         against this near-black surface — dark backgrounds need a LIGHT
         warm orange instead. --gold-lt already clears WCAG AA comfortably
         here (it was chosen for exactly this reason once before, for the
         nav HUD's "Live GPS" label), so reuse it rather than adding a
         third value. */
      --gold-text: var(--gold-lt);

      /* Terracotta's base hex is dark on its own, so a glow built from it
         reads as a muddy haze rather than popping — these were previously
         left un-redefined for dark mode entirely (falling back to the
         light-mode values), which is why terracotta primary buttons had
         almost no visible glow. Built from --wine-lt instead, matching how
         the gold glow is already tuned brighter/more saturated than its
         base hue. */
      --wine-tint: rgba(232,121,74,.16);
      --wine-glow: rgba(232,121,74,.40);
      --wine-ring: rgba(232,121,74,.45);

      /* Weather tints — dark-mode counterparts of the :root --wx-* values.
         Light versions would wash out/glow oddly on the near-black surface,
         so these are desaturated, low-luminance card tones instead (same
         hue family per condition, just recalibrated for a dark canvas). */
      --wx-sunny:  #4A3F1E;
      --wx-partly: #3C3627;
      --wx-cloudy: #332F27;
      --wx-rainy:  #263444;
      --wx-stormy: #232B3C;
      --wx-foggy:  #322F29;
      --wx-snowy:  #253442;
      --wx-stat-bg: rgba(0, 0, 0, .22);

      --sh-xs: 0 1px 3px  rgba(0,0,0,.25);
      --sh-sm: 0 2px 8px  rgba(0,0,0,.35);
      --sh-md: 0 4px 18px rgba(0,0,0,.45);
      --sh-lg: 0 8px 36px rgba(0,0,0,.55);
      --sh-xl: 0 16px 60px rgba(0,0,0,.65);
    }

    /* .loading intentionally NOT themed here — it's a video backdrop with
       a fixed #0A0908 fallback colour (see base rule), not a themed
       surface, so it should look identical in light/dark mode. */
    body.dark .sidebar        { background: var(--surface); border-color: var(--border) }
    body.dark .sb-foot        { background: var(--s2) }
    body.dark .ctx-bar        { background: var(--bg2) }
    body.dark .srch-input     { background: var(--s2); border-color: var(--border); color: var(--ink) }
    body.dark .srch-input:focus { background: var(--surface) }
    body.dark .srch-drop      { background: var(--surface); border-color: var(--border) }
    body.dark .sr-item:hover  { background: var(--s2) }
    body.dark .chip           { background: var(--s2); border-color: var(--border); color: var(--slate) }
    body.dark .chip:hover     { background: var(--s3) }
    body.dark .c-card:hover   { background: var(--s2) }
    body.dark .c-card.on      { background: var(--gold-tint) }
    body.dark .b-item:hover   { background: var(--s2) }
    body.dark .b-item.on      { background: var(--gold-tint) }
    body.dark .back-btn       { background: var(--surface); border-color: var(--border) }
    body.dark .back-btn:hover { background: var(--s3) }
    body.dark .c-thumb        { background: var(--s3); border-color: var(--border) }
    body.dark .map-btn        { background: var(--surface); border-color: var(--border) }
    body.dark .map-btn:hover  { background: var(--s2) }
    body.dark .view-pill      { background: rgba(33, 27, 20, .82); border-color: var(--border) }
    body.dark .info-panel     { background: var(--surface) }
    body.dark .map-ctrl-group { background: rgba(33, 27, 20, .82); border-color: var(--border) }
    body.dark .zoom-group     { background: rgba(33, 27, 20, .82); border-color: var(--border) }
    body.dark .layer-panel    { background: var(--surface) }
    body.dark .p-head         { border-color: var(--border) }
    body.dark .p-close        { background: rgba(28,24,16,.9); border-color: var(--border) }
    body.dark .p-hero-ph      { background: var(--s3) }
    body.dark .stat-card      { background: var(--s2) }
    body.dark .fac-chip       { background: var(--s2); border-color: var(--border) }
    body.dark .coord-cell     { background: var(--s2) }
    body.dark .p-tag          { background: var(--s2); border-color: var(--border) }
    body.dark .dir-panel      { background: var(--surface) }
    body.dark .dir-sel        { background: var(--s2); border-color: var(--border); color: var(--ink) }
    body.dark .dir-sel:focus  { background: var(--surface) }
    body.dark .dir-ai-inp     { background: var(--s2); border-color: var(--border); color: var(--ink) }
    body.dark .dir-ai-inp:focus { background: var(--surface) }
    body.dark .dir-ai-card    { background: var(--s2) }
    body.dark .dir-clear      { border-color: var(--border) }
    body.dark .dir-clear:hover { background: var(--s2) }
    body.dark .dir-hd-close   { background: var(--s3) }
    body.dark .dir-swap-btn   { background: var(--surface); border-color: var(--border) }
    body.dark .mob-toggle     { background: var(--surface); border-color: var(--border); color: var(--ink) }
    body.dark .ico-btn:hover  { background: var(--surface); border-color: var(--border) }
    body.dark .leaflet-container { background: #1A1A12 !important }
    body.dark .leaflet-control-attribution { background: rgba(28,24,16,.85) !important; color: var(--muted) !important }
    body.dark .campus-tip     { background: var(--surface); border-color: var(--border); color: var(--ink) }

    /* Dark mode theme button icon swap now lives up near .ah-profile as a
       rotate/scale/opacity cross-transition instead of display:none/block
       (display can't be transitioned, so the old version was an instant
       cut) — see the #themeBtnMoon/#themeBtnSun rules a few hundred lines
       up, right after .ah-profile. */

    /* ═══════════════════════════════════════════════════════════
       SNAPCHAT-STYLE DARK MODE GLOW
    ═══════════════════════════════════════════════════════════ */
    /* Logo used to stack three glow layers here (18px/40px/14px) on top of
       the base .brand-logo shadow that already exists in every mode —
       read as neon/over-designed rather than a real brand mark. Removed;
       the logo now just uses the same modest shadow every other primary
       element gets (dark mode's --wine-glow variable is already a bit
       punchier than light mode's, so it's not perfectly flat either). */
    body.dark .sidebar {
      box-shadow: 4px 0 32px rgba(0,0,0,.6), inset -1px 0 0 rgba(254,193,16,.08);
    }
    body.dark .mob-toggle {
      box-shadow: 0 0 14px rgba(254,193,16,.35), 0 4px 16px rgba(0,0,0,.5);
      border-color: rgba(254,193,16,.3) !important;
    }
    /* Fused pill containers now carry the shadow, not each individual
       button (see .map-ctrl-group/.zoom-group) — this just tints the
       hover state and the hairline dividers between buttons for dark mode. */
    body.dark .map-ctrl-group,
    body.dark .zoom-group {
      box-shadow: 0 2px 10px rgba(0,0,0,.45);
    }
    body.dark .map-ctrl-group:hover,
    body.dark .zoom-group:hover {
      box-shadow: 0 3px 14px rgba(0,0,0,.5);
    }
    body.dark .map-btn:hover {
      background: rgba(254,193,16,.12);
    }
    body.dark .zoom-group {
      box-shadow: 0 2px 10px rgba(0,0,0,.45);
    }
    body.dark .zoom-group .zoom-btn,
    body.dark .zoom-group .zoom-btn:hover {
      box-shadow: none;
    }
    body.dark .info-panel {
      box-shadow: 0 8px 36px rgba(0,0,0,.5);
    }
    body.dark .c-card.on {
      box-shadow: 0 0 16px rgba(254,193,16,.25), inset 0 0 0 1px rgba(254,193,16,.3);
    }
    body.dark .b-item.on {
      box-shadow: 0 0 14px rgba(254,193,16,.22), inset 0 0 0 1px rgba(254,193,16,.28);
    }
    body.dark .chip.on, body.dark .chip:hover {
      box-shadow: 0 0 12px rgba(254,193,16,.3);
    }
    body.dark .view-pill {
      box-shadow: 0 4px 18px rgba(0,0,0,.45);
    }
    body.dark .dir-panel {
      box-shadow: -8px 0 40px rgba(0,0,0,.5), 0 4px 24px rgba(0,0,0,.4);
    }
    /* Used to have a persistent gold glow + forced border here (a fixed
       selector that used to be dead CSS — see git history / project memory
       for that fix). Direct feedback: it read as excessive/AI-generated
       rather than a real UI affordance, especially next to the plain
       .ico-btn siblings around it. Removed — the theme button now behaves
       like every other icon button (transparent at rest, subtle
       background+border only on hover, via the shared .ico-btn:hover rule
       below), rather than being special-cased into a showpiece. */
    /* Wine-filled primary actions — these had no dark-mode glow at all
       before (the --wine-glow fix above now makes them worth lighting up),
       so they were flat/dim next to gold elements that already glowed. */
    body.dark .dir-fab {
      box-shadow: 0 0 16px var(--wine-glow), 0 2px 8px rgba(0,0,0,.5);
    }
    body.dark .pop-btn-primary {
      box-shadow: 0 0 10px var(--wine-glow);
    }
    body.dark .fp-action-btn.primary {
      box-shadow: 0 0 12px var(--wine-glow);
    }
    body.dark .fp-action-btn.primary.tracking {
      box-shadow: 0 0 12px var(--gold-glow);
    }
    /* Search focus ring — a soft glow instead of the flat light-mode ring */
    body.dark .srch-input:focus {
      box-shadow: 0 0 0 3px var(--gold-tint), 0 0 14px rgba(254,193,16,.25);
    }
    /* Map pins — campus pins are few and landmark-tier, so they get a
       persistent soft glow in their own color (Snap Map's glowing bitmoji
       pins). Building pins stay clean at rest — a whole campus of them
       glowing at once would be visual noise — and only light up on
       hover/selection, in their own color instead of a fixed wine tone. */
    body.dark .dti-mk-campus {
      filter: drop-shadow(0 0 7px currentColor) drop-shadow(0 2px 8px rgba(0,0,0,.55));
    }
    body.dark .dti-mk:hover {
      filter: drop-shadow(0 0 8px currentColor);
    }
    body.dark .dti-mk.sel {
      filter: drop-shadow(0 0 10px currentColor) drop-shadow(0 4px 10px rgba(0,0,0,.55));
    }
    /* Dark-mode tile filter removed for the same pan/zoom performance reason
       as the light-mode one above — dark mode already swaps to CARTO's own
       dark tile set (see _setBaseLayer in js/script.js), so it doesn't need
       a CSS filter on top to read as "dark." */

    /* mob-overlay — consolidated single rule set (no duplicates) */

    /* ═══════════════════════════════════════════════════════════
       LIVE NAV HUD
    ═══════════════════════════════════════════════════════════ */
    @keyframes userPulse {
      0% { transform: scale(0.5); opacity: 0.6; }
      100% { transform: scale(2.5); opacity: 0; }
    }
    @keyframes trackDotPulse {
      0%, 100% { opacity: 1; }
      50% { opacity: .35; }
    }
    /* Smooth glide between position updates — applied to both the nav-HUD
       live dot and the Locate Me live dot (see className in script.js).
       Leaflet positions markers via a CSS transform on this exact element,
       so transitioning "transform" makes setLatLng() glide instead of
       snap. Suspended during zoom (.no-glide, toggled in initMap()) since
       zoom ALSO repositions every marker via this same transform — without
       the pause the dot would visibly lag behind the map for a moment on
       every pinch/scroll zoom instead of snapping with everything else. */
    .live-dot-glide { transition: transform .5s linear; }
    .live-dot-glide.no-glide { transition: none !important; }

    /* Small directional chevron on a live dot, pointing toward device
       compass heading when available (see updateHeadingArrow() in
       script.js) — hidden by default (inline display:none in the marker
       HTML) since most devices/browsers never report heading at all. This
       rotates on its own; it's a separate element from the outer marker
       icon that .live-dot-glide/.no-glide apply to, so it's unaffected by
       the zoom-pause logic — a plain compass-needle turn is cheap and safe
       to always animate. */
    .dot-heading { transition: transform .3s ease-out; transform-origin: 50% 50%; }
    .nav-hud {
      position: fixed; top: 76px; left: calc(var(--sidebar-w) + 24px);
      transform: none;
      /* NOTE: this formula assumes desktop's info-panel is also open at
         full width (380px reserved) — on narrower viewports it's overridden
         by the TABLET media query below so it can't collapse to ~0. */
      z-index: var(--z-nav-hud); width: min(420px, calc(100vw - var(--sidebar-w) - 380px - 48px));
      background: linear-gradient(135deg,rgba(20,17,10,.97) 0%,rgba(14,12,7,.99) 100%);
      border: 1px solid rgba(254,193,16,.35); border-radius: 18px;
      box-shadow: 0 12px 48px rgba(0,0,0,.6),inset 0 1px 0 rgba(254,193,16,.14);
      padding: 14px 16px 12px; display: none; flex-direction: column; gap: 8px;
      color: #fff;
      /* No backdrop-filter here (unlike the modal glass panels below, which
         only ever sit over a static map) — this HUD is on screen for the
         entire walk, and handleNavPosition() calls map.panTo() on every
         single position update, so the browser would be re-sampling and
         re-blurring a moving backdrop continuously for the whole session.
         The background above is already 97-99% opaque, so the blur was
         doing very little visible work anyway — same performance/visual
         trade-off already made for the map tile pane earlier in this file. */
    }

    /* TABLET (641–1024px): the desktop width formula above assumes room
       for both the sidebar AND a full info-panel and goes near-zero on
       tablet viewports — clamp to a sane, never-negative width instead. */
    @media (min-width: 641px) and (max-width: 1024px) {
      .nav-hud { width: min(360px, calc(100vw - var(--sidebar-w) - 64px)); }
    }

    .nav-hud.active { display: flex }
    .nav-hud-top { display:flex; align-items:center; gap:12px }
    .nav-hud-arrow {
      flex-shrink:0; width:48px; height:48px; background:var(--wine);
      border-radius:12px; display:flex; align-items:center; justify-content:center;
      color:#fff; box-shadow:0 4px 16px rgba(193,68,14,.4); transition:transform .3s var(--ease)
    }
    .nav-hud-text { flex:1; min-width:0 }
    .nav-hud-action { font-family:var(--display); font-size:17px; font-weight:700; color:#fff; line-height:1.25; margin-bottom:2px }
    .nav-hud-detail { font-size:12.5px; color:rgba(255,255,255,.6); line-height:1.5 }
    .nav-hud-dist { flex-shrink:0; text-align:right }
    .nav-hud-dist-val { font-family:var(--mono); font-size:20px; font-weight:600; color:var(--gold-lt); line-height:1 }
    .nav-hud-dist-unit { font-size:10px; color:rgba(255,255,255,.4); text-transform:uppercase; letter-spacing:.08em }

    /* Live GPS / Simulated status row — handleNavPosition() is called
       identically whether the coordinates come from a real watchPosition
       fix or the scripted route-point fallback (see _startSimulation() in
       script.js), so without this there's no way to tell which one is
       actually driving the dot. */
    .nav-hud-status { display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:11px }
    .nav-hud-mode { display:flex; align-items:center; gap:5px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; flex-shrink:0 }
    .nav-hud-mode::before { content:''; width:6px; height:6px; border-radius:50%; background:currentColor; flex-shrink:0 }
    .nav-hud-mode.locating { color:rgba(255,255,255,.4) }
    .nav-hud-mode.locating::before { animation: trackDotPulse 1.2s ease-in-out infinite }
    .nav-hud-mode.live { color:var(--gold-lt) }
    .nav-hud-mode.live::before { animation: trackDotPulse 1.8s ease-in-out infinite }
    .nav-hud-mode.simulated { color:var(--wine-lt) }
    .nav-hud-near { color:rgba(255,255,255,.5); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-align:right }

    .nav-hud-progress { display:flex; align-items:center; gap:10px }
    .nav-hud-bar { flex:1; height:3px; background:rgba(255,255,255,.12); border-radius:99px; overflow:hidden }
    .nav-hud-fill { height:100%; background:var(--wine); border-radius:inherit; transition:width .6s var(--ease) }
    .nav-hud-step-count { font-size:11px; color:rgba(255,255,255,.4); white-space:nowrap }
    .nav-hud-controls { display:flex; gap:8px }
    .nav-hud-btn {
      flex:1; height:36px; border-radius:9px; border:none; cursor:pointer;
      font-family:var(--ui); font-size:12.5px; font-weight:600;
      transition:all .15s; display:flex; align-items:center; justify-content:center; gap:6px
    }
    .nav-hud-btn.stop { background:var(--wine); color:#fff; width:100%; padding:0 14px; border-radius:10px; font-size:13px; transition:background var(--fast), transform var(--fast); }
    .nav-hud-btn.stop:hover { background:var(--wine-dk); transform:translateY(-1px) }
    .nav-hud-btn.stop:active { transform:translateY(0) }

    /* Start Moving button (leaflet marker popup) */
    .start-nav-popup {
      background:var(--wine); color:#fff; border:2.5px solid #fff;
      border-radius:99px; padding:6px 16px;
      font-family:var(--ui); font-size:12px; font-weight:700;
      cursor:pointer; box-shadow:0 4px 14px rgba(193,68,14,.5);
      transition:all .15s; white-space:nowrap; display:block; margin:4px auto 0;
    }
    .start-nav-popup:hover { background:var(--gold); color:var(--ink) }

    /* Voice banner inside directions panel */
    .dir-voice-banner {
      display: flex;
      align-items: center;
      gap: 12px;
      background: var(--gold-tint);
      border: 1.5px solid rgba(254,193,16,.28);
      border-radius: 12px;
      padding: 12px 14px;
      margin-bottom: 14px;
    }
    .dir-voice-avatar {
      width: 36px; height: 36px;
      background: var(--wine);
      border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      font-size: 16px; flex-shrink: 0;
      box-shadow: 0 3px 10px var(--gold-glow);
    }
    .dir-voice-text {
      flex: 1; font-size: 12.5px; color: var(--slate); line-height: 1.55;
    }
    .dir-voice-text strong {
      display: block;
      color: var(--gold-text); font-weight: 700;
      font-size: 10px; letter-spacing: .08em;
      text-transform: uppercase; margin-bottom: 2px;
    }
    .dir-replay-btn {
      height: 32px; padding: 0 14px; border-radius: var(--r-full);
      background: var(--wine); color: #fff; border: none;
      font-family: var(--ui); font-size: 11.5px; font-weight: 700;
      cursor: pointer; transition: all .15s;
      display: flex; align-items: center; gap: 5px; flex-shrink: 0;
      box-shadow: 0 2px 8px var(--wine-glow);
    }
    .dir-replay-btn:hover { background: var(--gold); color: var(--ink); transform: translateY(-1px) }
    .dir-replay-btn:active { transform: none }

    /* Summary banner — from / to / walk */
    .dir-banner {
      display: flex;
      border-radius: var(--r-lg);
      overflow: hidden;
      background: var(--surface);
      margin-bottom: 14px;
      box-shadow: var(--sh-xs);
    }
    .dir-ban-cell {
      flex: 1;
      padding: 11px 14px;
      border-right: 1.5px solid var(--border);
      min-width: 0;
    }
    .dir-ban-cell:last-child { border-right: none }
    .dir-ban-lbl {
      font-size: 9.5px; font-weight: 700;
      letter-spacing: .1em; text-transform: uppercase;
      color: var(--muted); margin-bottom: 4px;
    }
    .dir-ban-val {
      font-size: 12.5px; font-weight: 700; color: var(--ink);
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
      line-height: 1.3;
    }
    .dir-ban-val.gold { color: var(--gold-text) }
    .dir-ban-val.wine { color: var(--wine) }

    /* Route alternatives — only rendered when OSRM actually returned more
       than one genuinely different path; picks which route drives the map
       + step list + Start Navigation. */
    .dir-route-switch {
      display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap;
    }
    /* Background (not a border) is what separates an unselected option
       from the white .dir-panel it sits on now — border stays transparent
       by default (keeps layout stable) and only picks up colour on
       hover/.active, where it's doing selection-affordance work rather
       than card framing. */
    .dir-route-opt {
      flex: 1; min-width: 112px;
      display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
      padding: 9px 12px;
      background: var(--s2); border: 1.5px solid transparent; border-radius: var(--r-lg);
      cursor: pointer; text-align: left; font-family: var(--ui);
      transition: all .16s var(--ease);
    }
    .dir-route-opt:hover { border-color: var(--gold) }
    .dir-route-opt-num {
      font-size: 9.5px; font-weight: 700; letter-spacing: .08em;
      text-transform: uppercase; color: var(--muted);
    }
    .dir-route-opt-meta { font-size: 12.5px; font-weight: 700; color: var(--ink) }
    .dir-route-opt.active {
      border-color: var(--gold); background: var(--gold-tint);
      box-shadow: 0 2px 10px var(--gold-glow);
    }
    .dir-route-opt.active .dir-route-opt-num,
    .dir-route-opt.active .dir-route-opt-meta { color: var(--gold-text) }

    /* Start Navigation CTA — compact, centered */
    .dir-start-nav {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      height: 40px;
      padding: 0 22px;
      background: var(--wine);
      color: #fff; border: none; border-radius: var(--r-full);
      font-family: var(--ui); font-size: 13px; font-weight: 700; letter-spacing: .02em;
      cursor: pointer; box-shadow: 0 4px 16px var(--wine-glow);
      transition: all .18s var(--ease); margin-bottom: 12px;
    }
    .dir-start-nav:hover { transform: translateY(-1px); box-shadow: 0 6px 22px var(--gold-glow); background: var(--gold); color: var(--ink) }
    .dir-start-nav:active { transform: none }
    .dir-start-nav svg { fill: none; stroke: currentColor; stroke-width: 2.5;
      stroke-linecap: round; width: 14px; height: 14px }

    /* Start nav row — centers the button. margin-top since this now
       follows the step list directly (see the renderSteps() assembly
       order in script.js) and needs clear separation from it, rather
       than sitting flush against the last step's thin border. */
    .dir-start-nav-row {
      display: flex;
      justify-content: center;
      margin-top: 18px;
    }

    /* Steps list */
    .dir-steps-title {
      font-size: 10px; font-weight: 700; letter-spacing: .1em;
      text-transform: uppercase; color: var(--muted);
      padding: 0 0 10px 0; margin-bottom: 0;
    }
    .dir-steps { display: flex; flex-direction: column; gap: 0 }
    .dir-step {
      display: flex; align-items: flex-start; gap: 12px;
      padding: 12px 0;
      border-bottom: 1px solid var(--s3);
      position: relative;
    }
    .dir-step:last-child { border-bottom: none; padding-bottom: 4px }

    /* Connector line between steps */
    .dir-step:not(:last-child)::after {
      content: '';
      position: absolute;
      left: 15px; top: 42px; bottom: 0;
      width: 2px;
      background: linear-gradient(to bottom, var(--gold-ring), transparent);
    }
    .dir-step-num {
      flex-shrink: 0;
      width: 30px; height: 30px;
      border-radius: 50%;
      background: var(--gold-xlt);
      border: 2px solid var(--gold-ring);
      color: var(--gold-text);
      font-size: 11px; font-weight: 600;
      display: flex; align-items: center; justify-content: center;
      font-family: var(--mono);
      z-index: 1;
    }
    .dir-step-body { flex: 1; min-width: 0; padding-top: 4px }
    .dir-step-action {
      font-size: 13px; font-weight: 700; color: var(--ink);
      line-height: 1.4; margin-bottom: 3px;
    }
    .dir-step-detail { font-size: 12px; color: var(--muted); line-height: 1.55 }
    .dir-step-chip {
      display: inline-flex; align-items: center; gap: 4px;
      margin-top: 6px; padding: 3px 10px; border-radius: var(--r-full);
      background: var(--gold-xlt); color: var(--gold-text);
      font-size: 10.5px; font-weight: 700; letter-spacing: .03em;
    }

    /* Active (current nav) step highlighting */
    .dir-step.nav-active {
      background: var(--wine-tint);
      border-radius: 10px;
      padding-left: 10px;
      padding-right: 10px;
      margin: 0 -10px;
      border-bottom-color: transparent;
    }
    .dir-step.nav-active .dir-step-num {
      background: var(--wine);
      border-color: var(--wine);
      color: #fff;
      box-shadow: 0 2px 8px var(--wine-glow);
    }
    .dir-step.nav-active .dir-step-action {
      color: var(--wine);
    }
    .dir-step.nav-done {
      opacity: 0.45;
    }
    .dir-step.nav-done .dir-step-num {
      background: var(--s3);
      border-color: var(--border);
      color: var(--muted);
    }

    /* AI response card — also reused for the weather advisory and the
       fallback/indirect-route warning in the results list (see
       renderSteps() in script.js), so it needs its own margin-bottom to
       match every other block in that stack rather than relying on
       whatever happened to render after it. */
    .dir-ai-card {
      padding: 16px; background: var(--s2);
      border-radius: var(--r-md);
      font-size: 13.5px; line-height: 1.75; color: var(--slate);
      margin-bottom: 14px;
    }
    .dir-ai-card strong { color: var(--gold-text) }

    /* Clear button — compact and centered */
    .dir-clear {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      height: 36px;
      padding: 0 18px;
      border: 1.5px solid var(--border); background: transparent;
      color: var(--muted); border-radius: var(--r-full);
      font-family: var(--ui); font-size: 12px; font-weight: 600;
      cursor: pointer; transition: all var(--fast); margin-top: 4px;
    }
    .dir-clear:hover {
      background: var(--s2); color: var(--slate); border-color: var(--border2)
    }
    /* Clear row — centers the button */
    .dir-clear-row {
      display: flex;
      justify-content: center;
    }

    /* OSRM routing spinner */
    .dir-routing-spinner {
      display: flex; align-items: center; justify-content: center;
      gap: 12px; padding: 32px 20px; color: var(--muted); font-size: 13px;
    }
    .dir-routing-spinner::before {
      content: ''; width: 20px; height: 20px;
      border: 2.5px solid var(--border); border-top-color: var(--gold);
      border-radius: 50%; animation: dir-spin .7s linear infinite; flex-shrink: 0;
    }

    /* ═══════════════════════════════════════════════════════════════════
       ONBOARDING TOUR — one-time, non-modal. A small card next to the real
       control it's introducing (see initOnboarding() in js/script.js), not
       a full-screen takeover — no backdrop dimming the rest of the app.
    ═══════════════════════════════════════════════════════════════════ */
    .onboard-card {
      position: fixed;
      z-index: var(--z-onboarding);
      width: 272px;
      max-width: calc(100vw - 24px);
      background: var(--surface);
      border: 1.5px solid var(--gold-ring);
      border-radius: var(--r-lg);
      box-shadow: var(--sh-xl);
      padding: 16px 18px;
      animation: onboardIn .35s var(--ease);
    }
    @keyframes onboardIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
    .onboard-step {
      font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
      color: var(--gold-text); margin-bottom: 6px;
    }
    .onboard-title { font-family: var(--display); font-size: 15px; font-weight: 600; color: var(--ink); margin-bottom: 4px; }
    .onboard-body { font-size: 12.5px; color: var(--slate); line-height: 1.5; }
    .onboard-actions { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }
    .onboard-skip {
      font-size: 12px; font-weight: 600; color: var(--muted); background: none; border: none; cursor: pointer; padding: 6px 4px;
    }
    .onboard-skip:hover { color: var(--ink); }
    .onboard-next {
      font-size: 12.5px; font-weight: 700; color: #fff; background: var(--wine);
      border: none; border-radius: var(--r-full); padding: 8px 18px; cursor: pointer;
      transition: background var(--fast);
    }
    .onboard-next:hover { background: var(--gold); color: var(--ink); }

    /* Highlight ring on whatever the current step is pointing at — real
       element, real position, not a fake spotlight overlay. */
    .onboard-target {
      position: relative;
      z-index: var(--z-onboarding);
      /* No forced border-radius — the ring follows whatever shape the
         target already has (box-shadow respects an element's own radius),
         so a pill FAB stays a pill and a rounded-rect button stays square. */
      box-shadow: 0 0 0 4px var(--gold-tint), 0 0 0 2px var(--gold) !important;
    }

    @media (max-width: 640px) {
      .onboard-card { left: 12px !important; right: 12px; width: auto; max-width: none; }
    }

    /* ═══════════════════════════════════════════════════════════════════
       REDUCED MOTION — respects OS-level "reduce motion" accessibility
       setting. Blanket-collapses every animation/transition to near-zero
       duration rather than deleting them outright, so state changes (open/
       closed, filled/empty, etc.) still resolve correctly — nothing gets
       stuck mid-transition — but nothing visibly moves, spins, pulses, or
       glides for anyone who's asked their OS not to show that motion.
    ═══════════════════════════════════════════════════════════════════ */
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
      }
    }
